diff --git a/schema/migrate/M0010-cm-user-duel-list.sql b/schema/migrate/M0010-cm-user-duel-list.sql new file mode 100644 index 0000000..ff561cd --- /dev/null +++ b/schema/migrate/M0010-cm-user-duel-list.sql @@ -0,0 +1,16 @@ +create table "cm_user_duel_list" ( + "id" integer primary key not null, + "profile_id" integer not null + references "cm_user_data"("id") + on delete cascade, + "duel_id" integer not null, + "progress" integer not null, + "point" integer not null, + "is_clear" boolean not null, + "last_play_date" text not null, + "param1" integer not null, + "param2" integer not null, + "param3" integer not null, + "param4" integer not null, + constraint "cm_user_duel_list_uq" unique ("profile_id", "duel_id") +); diff --git a/src/chunithm/handler/getUserDuel.ts b/src/chunithm/handler/getUserDuel.ts index 8a05453..045b3f5 100644 --- a/src/chunithm/handler/getUserDuel.ts +++ b/src/chunithm/handler/getUserDuel.ts @@ -1,14 +1,23 @@ import { Repositories } from "../repo"; import { GetUserDuelRequest } from "../request/getUserDuel"; import { GetUserDuelResponse } from "../response/getUserDuel"; +import { readAimeId } from "../proto/base"; +import { writeUserDuelList } from "../proto/userDuelList"; export default async function getUserDuel( rep: Repositories, req: GetUserDuelRequest ): Promise { + const aimeId = readAimeId(req.userId); + // Get all duel entities if the client specifies to. + const duelId = req.isAllDuel ? undefined : parseInt(req.duelId); + + const profileId = await rep.userData().lookup(aimeId); + const items = await rep.userDuelList().load(profileId, duelId); + return { userId: req.userId, - length: "0", - userDuelList: [], + length: items.length.toString(), + userDuelList: items.map(writeUserDuelList), }; } diff --git a/src/chunithm/handler/upsertUserAll.ts b/src/chunithm/handler/upsertUserAll.ts index d0836a9..e9d1642 100644 --- a/src/chunithm/handler/upsertUserAll.ts +++ b/src/chunithm/handler/upsertUserAll.ts @@ -11,6 +11,7 @@ import { readUserItem } from "../proto/userItem"; import { readUserMusicDetail } from "../proto/userMusic"; import { readUserActivity } from "../proto/userActivity"; import { readUserDataEx } from "../proto/userDataEx"; +import { readUserDuelList } from "../proto/userDuelList"; import { readUserPlaylog } from "../proto/userPlaylog"; // It shouldn't need to be said really, but seeing as this message (A) requires @@ -69,5 +70,9 @@ export default async function upsertUserAll( await rep.userDataEx().save(profileId, readUserDataEx(item)); } + for (const item of payload.userDuelList || []) { + await rep.userDuelList().save(profileId, readUserDuelList(item)); + } + return { returnCode: "1" }; } diff --git a/src/chunithm/model/userDuelList.ts b/src/chunithm/model/userDuelList.ts new file mode 100644 index 0000000..70cc5bc --- /dev/null +++ b/src/chunithm/model/userDuelList.ts @@ -0,0 +1,11 @@ +export interface UserDuelListItem { + duelId: number; + progress: number; + point: number; + isClear: boolean; + lastPlayDate: Date; + param1: number; + param2: number; + param3: number; + param4: number; +} diff --git a/src/chunithm/proto/userDuelList.ts b/src/chunithm/proto/userDuelList.ts new file mode 100644 index 0000000..6c0c072 --- /dev/null +++ b/src/chunithm/proto/userDuelList.ts @@ -0,0 +1,26 @@ +import { Crush, readBoolean, readDate, writeObject } from "./base"; +import { UserDuelListItem } from "../model/userDuelList"; + +export type UserDuelListJson = Crush; + +export function readUserDuelList( + json: UserDuelListJson +): UserDuelListItem { + return { + duelId: parseInt(json.duelId), + progress: parseInt(json.progress), + point: parseInt(json.point), + isClear: readBoolean(json.isClear), + lastPlayDate: readDate(json.lastPlayDate), + param1: parseInt(json.param1), + param2: parseInt(json.param2), + param3: parseInt(json.param3), + param4: parseInt(json.param4), + }; +} + +export function writeUserDuelList( + obj: UserDuelListItem +): UserDuelListJson { + return writeObject(obj); +} diff --git a/src/chunithm/repo/index.ts b/src/chunithm/repo/index.ts index 6486a21..c162911 100644 --- a/src/chunithm/repo/index.ts +++ b/src/chunithm/repo/index.ts @@ -4,6 +4,7 @@ import { UserActivityRepository } from "./userActivity"; import { UserCharacterRepository } from "./userCharacter"; import { UserDataRepository } from "./userData"; import { UserDataExRepository } from "./userDataEx"; +import { UserDuelListRepository } from "./userDuelList"; import { UserGameOptionRepository } from "./userGameOption"; import { UserGameOptionExRepository } from "./userGameOptionEx"; import { UserItemRepository } from "./userItem"; @@ -20,6 +21,8 @@ export interface Repositories { userDataEx(): UserDataExRepository; + userDuelList(): UserDuelListRepository; + userGameOption(): UserGameOptionRepository; userGameOptionEx(): UserGameOptionExRepository; diff --git a/src/chunithm/repo/userDuelList.ts b/src/chunithm/repo/userDuelList.ts new file mode 100644 index 0000000..da673bd --- /dev/null +++ b/src/chunithm/repo/userDuelList.ts @@ -0,0 +1,13 @@ +import { Page } from "./_defs"; +import { UserDataItem } from "../model/userData"; +import { UserDuelListItem } from "../model/userDuelList"; +import { Id } from "../../model"; + +export interface UserDuelListRepository { + load( + profileId: Id, + duelId?: number, + ): Promise; + + save(profileId: Id, obj: UserDuelListItem): Promise; +} diff --git a/src/chunithm/request/upsertUserAll.ts b/src/chunithm/request/upsertUserAll.ts index b7ee95b..72a0a02 100644 --- a/src/chunithm/request/upsertUserAll.ts +++ b/src/chunithm/request/upsertUserAll.ts @@ -9,6 +9,7 @@ import { UserActivityJson } from "../proto/userActivity"; import { UserRecentRatingJson } from "../proto/userRecentRating"; import { UserPlaylogJson } from "../proto/userPlaylog"; import { UserDataExJson } from "../proto/userDataEx"; +import { UserDuelListJson } from "../proto/userDuelList"; export interface UpsertUserAllRequest { /** Integer, AiMe ID */ @@ -26,6 +27,7 @@ export interface UpsertUserAllRequest { userRecentRatingList?: UserRecentRatingJson[]; userPlaylogList?: UserPlaylogJson[]; userDataEx?: UserDataExJson[]; + userDuelList?: UserDuelListJson[]; /** String of binary digits */ isNewMapList: string; diff --git a/src/chunithm/response/getUserDuel.ts b/src/chunithm/response/getUserDuel.ts index e419ce5..40deb90 100644 --- a/src/chunithm/response/getUserDuel.ts +++ b/src/chunithm/response/getUserDuel.ts @@ -1,3 +1,5 @@ +import { UserDuelListJson } from "../proto/userDuelList"; + export interface GetUserDuelResponse { /** Integer, AiMe ID */ userId: string; @@ -5,6 +7,9 @@ export interface GetUserDuelResponse { /** Integer, number of results returned */ length: string; - /** TBD */ - userDuelList: []; + /** + * List of duel entities, either all duel entities associated with `userId` + * or single requested by `duelId` in request. + */ + userDuelList: UserDuelListJson[]; } diff --git a/src/chunithm/sql/_factory.ts b/src/chunithm/sql/_factory.ts index d6d85d4..8acab3b 100644 --- a/src/chunithm/sql/_factory.ts +++ b/src/chunithm/sql/_factory.ts @@ -2,6 +2,7 @@ import { SqlUserActivityRepository } from "./userActivity"; import { SqlUserCharacterRepository } from "./userCharacter"; import { SqlUserDataRepository } from "./userData"; import { SqlUserDataExRepository } from "./userDataEx"; +import { SqlUserDuelListRepository } from "./userDuelList"; import { SqlUserGameOptionRepository } from "./userGameOption"; import { SqlUserGameOptionExRepository } from "./userGameOptionEx"; import { SqlUserItemRepository } from "./userItem"; @@ -13,6 +14,7 @@ import { UserActivityRepository } from "../repo/userActivity"; import { UserCharacterRepository } from "../repo/userCharacter"; import { UserDataRepository } from "../repo/userData"; import { UserDataExRepository } from "../repo/userDataEx"; +import { UserDuelListRepository } from "../repo/userDuelList"; import { UserGameOptionRepository } from "../repo/userGameOption"; import { UserGameOptionExRepository } from "../repo/userGameOptionEx"; import { UserItemRepository } from "../repo/userItem"; @@ -40,6 +42,10 @@ export class SqlRepositories implements Repositories { return new SqlUserDataExRepository(this._txn); } + userDuelList(): UserDuelListRepository { + return new SqlUserDuelListRepository(this._txn); + } + userGameOption(): UserGameOptionRepository { return new SqlUserGameOptionRepository(this._txn); } diff --git a/src/chunithm/sql/userDuelList.ts b/src/chunithm/sql/userDuelList.ts new file mode 100644 index 0000000..db2b074 --- /dev/null +++ b/src/chunithm/sql/userDuelList.ts @@ -0,0 +1,56 @@ +import sql from "sql-bricks-postgres"; + +import { UserDataItem } from "../model/userData"; +import { UserDuelListItem } from "../model/userDuelList"; +import { Page } from "../repo"; +import { UserDuelListRepository } from "../repo/userDuelList"; +import { Id } from "../../model"; +import { Transaction } from "../../sql"; +import { T, createSqlMapper } from "../../sql/util"; + +const { readRow, writeRow, colNames } = createSqlMapper({ + duelId: T.number, + progress: T.number, + point: T.number, + isClear: T.boolean, + lastPlayDate: T.Date, + param1: T.number, + param2: T.number, + param3: T.number, + param4: T.number, +}); + +export class SqlUserDuelListRepository implements UserDuelListRepository { + constructor(private readonly _txn: Transaction) {} + + async load( + profileId: Id, + duelId?: number + ): Promise { + const stmt = sql + .select("*") + .from("cm_user_duel_list") + .where("profile_id", profileId); + + if (duelId !== undefined) { + stmt.where("duel_id", duelId); + } + + const rows = await this._txn.fetchRows(stmt); + + return rows.map(readRow); + } + + save(profileId: Id, obj: UserDuelListItem): Promise { + const stmt = sql + .insert("cm_user_duel_list", { + id: this._txn.generateId(), + profile_id: profileId, + ...writeRow(obj), + }) + .onConflict("profile_id", "duel_id") + .doUpdate(colNames); + + return this._txn.modify(stmt); + } +}