chuni: save and load user course result

This commit is contained in:
NeumPhis 2019-11-28 02:00:44 +09:00 committed by Tau
parent 0f2811197a
commit afa02c0fc7
11 changed files with 169 additions and 7 deletions

View File

@ -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")
);

View File

@ -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<GetUserCourseResponse> {
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)
};
}

View File

@ -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));
}

View File

@ -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;
}

View File

@ -0,0 +1,28 @@
import { UserCourseItem } from "../model/userCourse";
import { Crush, readBoolean, readDate, writeObject } from "./base";
export type UserCourseJson = Crush<UserCourseItem>;
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);
}

View File

@ -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;
}

View File

@ -0,0 +1,4 @@
import { UserCourseItem } from "../model/userCourse";
import { RepositoryN } from "./_defs";
export type UserCourseRepository = RepositoryN<UserCourseItem>;

View File

@ -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[];

View File

@ -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[];
}

View File

@ -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);
}
}

View File

@ -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<UserDataItem>,
page?: Page
): Promise<UserCourseItem[]> {
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<UserDataItem>, obj: UserCourseItem): Promise<void> {
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);
}
}