From afa02c0fc75ffd625824eb986d701c78fd95cac9 Mon Sep 17 00:00:00 2001 From: NeumPhis <205-NeumPhis@users.noreply.dev.s-ul.eu> Date: Thu, 28 Nov 2019 02:00:44 +0900 Subject: [PATCH] chuni: save and load user course result --- schema/migrate/M0011-cm-user-course.sql | 22 +++++++++ src/chunithm/handler/getUserCourse.ts | 21 +++++++-- src/chunithm/handler/upsertUserAll.ts | 5 ++ src/chunithm/model/userCourse.ts | 17 +++++++ src/chunithm/proto/userCourse.ts | 28 +++++++++++ src/chunithm/repo/index.ts | 4 ++ src/chunithm/repo/userCourse.ts | 4 ++ src/chunithm/request/upsertUserAll.ts | 2 + src/chunithm/response/getUserCourse.ts | 5 +- src/chunithm/sql/_factory.ts | 6 +++ src/chunithm/sql/userCourse.ts | 62 +++++++++++++++++++++++++ 11 files changed, 169 insertions(+), 7 deletions(-) create mode 100644 schema/migrate/M0011-cm-user-course.sql create mode 100644 src/chunithm/model/userCourse.ts create mode 100644 src/chunithm/proto/userCourse.ts create mode 100644 src/chunithm/repo/userCourse.ts create mode 100644 src/chunithm/sql/userCourse.ts diff --git a/schema/migrate/M0011-cm-user-course.sql b/schema/migrate/M0011-cm-user-course.sql new file mode 100644 index 0000000..bdae3f6 --- /dev/null +++ b/schema/migrate/M0011-cm-user-course.sql @@ -0,0 +1,22 @@ +create table "cm_user_course" ( + "id" integer primary key not null, + "profile_id" integer not null + references "cm_user_data"("id") + on delete cascade, + "course_id" integer not null, + "class_id" integer not null, + "play_count" integer not null, + "score_max" integer not null, + "is_full_combo" text not null, + "is_all_justice" text not null, + "is_success" text not null, + "score_rank" integer not null, + "event_id" integer not null, + "last_play_date" text not null, + "param1" integer not null, + "param2" integer not null, + "param3" integer not null, + "param4" integer not null, + "is_clear" text not null, + constraint "cm_user_course_uq" unique ("profile_id", "course_id") +); diff --git a/src/chunithm/handler/getUserCourse.ts b/src/chunithm/handler/getUserCourse.ts index 8f6e210..49d2fc9 100644 --- a/src/chunithm/handler/getUserCourse.ts +++ b/src/chunithm/handler/getUserCourse.ts @@ -1,17 +1,28 @@ import { Repositories } from "../repo"; import { GetUserCourseRequest } from "../request/getUserCourse"; import { GetUserCourseResponse } from "../response/getUserCourse"; +import { readAimeId } from "../proto/base"; +import { paginationCookie } from "./_util"; +import { writeUserCourse } from "../proto/userCourse"; export default async function getUserCourse( rep: Repositories, req: GetUserCourseRequest ): Promise { + const aimeId = readAimeId(req.userId); + const maxCount = parseInt(req.maxCount); + const nextIndex = parseInt(req.nextIndex); + + const profileId = await rep.userData().lookup(aimeId); + const items = await rep + .userCourse() + .load(profileId, { limit: maxCount, offset: nextIndex }); + + return { userId: req.userId, - length: "0", - nextIndex: "-1", - userCourseList: [ - // This will get saved at some point I think - ], + length: items.length.toString(), + nextIndex: paginationCookie(items, { maxCount, nextIndex }), + userCourseList: items.map(writeUserCourse) }; } diff --git a/src/chunithm/handler/upsertUserAll.ts b/src/chunithm/handler/upsertUserAll.ts index e9d1642..37a243a 100644 --- a/src/chunithm/handler/upsertUserAll.ts +++ b/src/chunithm/handler/upsertUserAll.ts @@ -13,6 +13,7 @@ import { readUserActivity } from "../proto/userActivity"; import { readUserDataEx } from "../proto/userDataEx"; import { readUserDuelList } from "../proto/userDuelList"; import { readUserPlaylog } from "../proto/userPlaylog"; +import { readUserCourse } from "../proto/userCourse"; // It shouldn't need to be said really, but seeing as this message (A) requires // fairly lengthy processing and (B) is only sent right at the end of a credit, @@ -66,6 +67,10 @@ export default async function upsertUserAll( await rep.userPlaylog().save(profileId, readUserPlaylog(item)); } + for (const item of payload.userCourseList || []) { + await rep.userCourse().save(profileId, readUserCourse(item)); + } + for (const item of payload.userDataEx || []) { await rep.userDataEx().save(profileId, readUserDataEx(item)); } diff --git a/src/chunithm/model/userCourse.ts b/src/chunithm/model/userCourse.ts new file mode 100644 index 0000000..11acbbe --- /dev/null +++ b/src/chunithm/model/userCourse.ts @@ -0,0 +1,17 @@ +export interface UserCourseItem { + courseId: number; + classId: number; + playCount: number; + scoreMax: number; + isFullCombo: boolean; + isAllJustice: boolean; + isSuccess: boolean; + scoreRank: number; + eventId: number; + lastPlayDate: Date; + param1: number; + param2: number; + param3: number; + param4: number; + isClear: boolean; +} diff --git a/src/chunithm/proto/userCourse.ts b/src/chunithm/proto/userCourse.ts new file mode 100644 index 0000000..8fba63f --- /dev/null +++ b/src/chunithm/proto/userCourse.ts @@ -0,0 +1,28 @@ +import { UserCourseItem } from "../model/userCourse"; +import { Crush, readBoolean, readDate, writeObject } from "./base"; + +export type UserCourseJson = Crush; + +export function readUserCourse(json: UserCourseJson): UserCourseItem { + return { + classId: parseInt(json.classId), + courseId: parseInt(json.courseId), + eventId: parseInt(json.eventId), + isAllJustice: readBoolean(json.isAllJustice), + isClear: readBoolean(json.isClear), + isFullCombo: readBoolean(json.isFullCombo), + isSuccess: readBoolean(json.isSuccess), + lastPlayDate: readDate(json.lastPlayDate), + param1: parseInt(json.param1), + param2: parseInt(json.param2), + param3: parseInt(json.param3), + param4: parseInt(json.param4), + playCount: parseInt(json.playCount), + scoreMax: parseInt(json.scoreMax), + scoreRank: parseInt(json.scoreRank) + } +} + +export function writeUserCourse(obj: UserCourseItem): UserCourseJson { + return writeObject(obj); +} diff --git a/src/chunithm/repo/index.ts b/src/chunithm/repo/index.ts index c162911..f5bab80 100644 --- a/src/chunithm/repo/index.ts +++ b/src/chunithm/repo/index.ts @@ -1,3 +1,5 @@ +import { UserCourseRepository } from "./userCourse"; + export { Page } from "./_defs"; import { UserActivityRepository } from "./userActivity"; @@ -34,4 +36,6 @@ export interface Repositories { userMusic(): UserMusicRepository; userPlaylog(): UserPlaylogRepository; + + userCourse(): UserCourseRepository; } diff --git a/src/chunithm/repo/userCourse.ts b/src/chunithm/repo/userCourse.ts new file mode 100644 index 0000000..f6ec03b --- /dev/null +++ b/src/chunithm/repo/userCourse.ts @@ -0,0 +1,4 @@ +import { UserCourseItem } from "../model/userCourse"; +import { RepositoryN } from "./_defs"; + +export type UserCourseRepository = RepositoryN; diff --git a/src/chunithm/request/upsertUserAll.ts b/src/chunithm/request/upsertUserAll.ts index 72a0a02..8253413 100644 --- a/src/chunithm/request/upsertUserAll.ts +++ b/src/chunithm/request/upsertUserAll.ts @@ -8,6 +8,7 @@ import { UserMusicDetailJson } from "../proto/userMusic"; import { UserActivityJson } from "../proto/userActivity"; import { UserRecentRatingJson } from "../proto/userRecentRating"; import { UserPlaylogJson } from "../proto/userPlaylog"; +import { UserCourseJson } from "../proto/userCourse"; import { UserDataExJson } from "../proto/userDataEx"; import { UserDuelListJson } from "../proto/userDuelList"; @@ -26,6 +27,7 @@ export interface UpsertUserAllRequest { userActivityList?: UserActivityJson[]; userRecentRatingList?: UserRecentRatingJson[]; userPlaylogList?: UserPlaylogJson[]; + userCourseList?: UserCourseJson[]; userDataEx?: UserDataExJson[]; userDuelList?: UserDuelListJson[]; diff --git a/src/chunithm/response/getUserCourse.ts b/src/chunithm/response/getUserCourse.ts index b9a9296..f50671d 100644 --- a/src/chunithm/response/getUserCourse.ts +++ b/src/chunithm/response/getUserCourse.ts @@ -1,3 +1,5 @@ +import { UserCourseJson } from "../proto/userCourse"; + export interface GetUserCourseResponse { /** Integer, AiMe ID */ userId: string; @@ -8,6 +10,5 @@ export interface GetUserCourseResponse { /** Integer, pagination cookie */ nextIndex: string; - /** TBD */ - userCourseList: []; + userCourseList: UserCourseJson[]; } diff --git a/src/chunithm/sql/_factory.ts b/src/chunithm/sql/_factory.ts index 8acab3b..6f7c4de 100644 --- a/src/chunithm/sql/_factory.ts +++ b/src/chunithm/sql/_factory.ts @@ -22,6 +22,8 @@ import { UserMapRepository } from "../repo/userMap"; import { UserMusicRepository } from "../repo/userMusic"; import { UserPlaylogRepository } from "../repo/userPlaylog"; import { Transaction } from "../../sql"; +import { UserCourseRepository } from "../repo/userCourse"; +import { SqlUserCourseRepository } from "./userCourse"; export class SqlRepositories implements Repositories { constructor(private readonly _txn: Transaction) {} @@ -69,4 +71,8 @@ export class SqlRepositories implements Repositories { userPlaylog(): UserPlaylogRepository { return new SqlUserPlaylogRepository(this._txn); } + + userCourse(): UserCourseRepository { + return new SqlUserCourseRepository(this._txn); + } } diff --git a/src/chunithm/sql/userCourse.ts b/src/chunithm/sql/userCourse.ts new file mode 100644 index 0000000..399c7ac --- /dev/null +++ b/src/chunithm/sql/userCourse.ts @@ -0,0 +1,62 @@ +import sql from "sql-bricks-postgres"; +import { createSqlMapper, T } from "../../sql/util"; +import { UserCourseRepository } from "../repo/userCourse"; +import { Id } from "../../model"; +import { Page } from "../repo"; +import { UserDataItem } from "../model/userData"; +import { UserCourseItem } from "../model/userCourse"; +import { Transaction } from "../../sql"; + +const { readRow, writeRow, colNames } = createSqlMapper({ + courseId: T.number, + classId: T.number, + playCount: T.number, + scoreMax: T.number, + isFullCombo: T.boolean, + isAllJustice: T.boolean, + isSuccess: T.boolean, + scoreRank: T.number, + eventId: T.number, + lastPlayDate: T.Date, + param1: T.number, + param2: T.number, + param3: T.number, + param4: T.number, + isClear: T.boolean +}); + +export class SqlUserCourseRepository implements UserCourseRepository { + constructor(private readonly _txn: Transaction) {} + + async load( + profileId: Id, + page?: Page + ): Promise { + const stmt = sql + .select("*") + .from("cm_user_course") + .where("profile_id", profileId); + + if (page) { + stmt.limit(page.limit).offset(page.offset); + } + + const rows = await this._txn.fetchRows(stmt); + + return rows.map(readRow); + } + + save(profileId: Id, obj: UserCourseItem): Promise { + const stmt = sql + .insert("cm_user_course", { + id: this._txn.generateId(), + profile_id: profileId, + ...writeRow(obj), + }) + .onConflict("profile_id", "course_id") + .doUpdate(colNames); + + return this._txn.modify(stmt); + } + +}