diff --git a/schema/init/idz.sql b/schema/init/idz.sql index bcf7a7e..5911826 100644 --- a/schema/init/idz.sql +++ b/schema/init/idz.sql @@ -108,6 +108,25 @@ create table "idz_settings" ( "driving_style" integer not null ); +create table "idz_stamp_selections" ( + "id" integer primary key not null + references "idz_profile"("id") + on delete cascade, + "stamp_01" integer not null, + "stamp_02" integer not null, + "stamp_03" integer not null, + "stamp_04" integer not null +); + +create table "idz_stamp_unlock" ( + "id" integer primary key not null, + "profile_id" integer not null + references "idz_profile"("id") + on delete cascade, + "stamp_no", + constraint "idz_stamp_unlock_uq" unique ("profile_id", "stamp_no") +); + create table "idz_story_state" ( "id" integer primary key not null references "idz_profile"("id") diff --git a/schema/migrate/M0017-idz-v2-support.sql b/schema/migrate/M0017-idz-v2-support.sql index e159c37..4e5e622 100644 --- a/schema/migrate/M0017-idz-v2-support.sql +++ b/schema/migrate/M0017-idz-v2-support.sql @@ -10,6 +10,25 @@ create table "new_idz_settings" ( "driving_style" integer not null ); +create table "idz_stamp_selections" ( + "id" integer primary key not null + references "idz_profile"("id") + on delete cascade, + "stamp_01" integer not null, + "stamp_02" integer not null, + "stamp_03" integer not null, + "stamp_04" integer not null +); + +create table "idz_stamp_unlock" ( + "id" integer primary key not null, + "profile_id" integer not null + references "idz_profile"("id") + on delete cascade, + "stamp_no", + constraint "idz_stamp_unlock_uq" unique ("profile_id", "stamp_no") +); + insert into "new_idz_settings" ( "id", "music", diff --git a/src/idz/userdb/handler/loadProfile.ts b/src/idz/userdb/handler/loadProfile.ts index 17e89e9..409570a 100644 --- a/src/idz/userdb/handler/loadProfile.ts +++ b/src/idz/userdb/handler/loadProfile.ts @@ -28,6 +28,8 @@ export async function loadProfile( const unlocks = await w.unlocks().load(profileId); const tickets = await w.tickets().load(profileId); const team = teamId && (await w.teams().load(teamId)); + const stamps = await w.stamps().loadAll(profileId); + const selectedStamps = await w.stamps().loadSelection(profileId); return { type: "load_profile_res", @@ -51,5 +53,7 @@ export async function loadProfile( story, unlocks, tickets, + stamps, + selectedStamps, }; } diff --git a/src/idz/userdb/handler/saveProfile.ts b/src/idz/userdb/handler/saveProfile.ts index 8ae4ec6..53838cd 100644 --- a/src/idz/userdb/handler/saveProfile.ts +++ b/src/idz/userdb/handler/saveProfile.ts @@ -36,6 +36,14 @@ export async function saveProfile( await w.settings().save(profileId, req.settings); await w.tickets().save(profileId, req.tickets); + if (req.selectedStamps) { + await w.stamps().saveSelection(profileId, req.selectedStamps); + } + + if (req.stamps) { + await w.stamps().saveAll(profileId, req.stamps); + } + return { type: "generic_res", status: 1, diff --git a/src/idz/userdb/model/base.ts b/src/idz/userdb/model/base.ts index 63314f0..21e0b94 100644 --- a/src/idz/userdb/model/base.ts +++ b/src/idz/userdb/model/base.ts @@ -5,4 +5,5 @@ export type BackgroundCode = number & { __brand: "backgroundCode" }; export type CourseNo = number & { __brand: "courseNo" }; export type ExtId = number & { __extId: T }; export type RouteNo = number & { __brand: "routeNo" }; +export type StampCode = number & { __brand: "stampCode" }; export type TitleCode = number & { __brand: "titleCode" }; diff --git a/src/idz/userdb/model/index.ts b/src/idz/userdb/model/index.ts index fbd83b8..3505586 100644 --- a/src/idz/userdb/model/index.ts +++ b/src/idz/userdb/model/index.ts @@ -1,9 +1,17 @@ -export { BackgroundCode, CourseNo, ExtId, RouteNo, TitleCode } from "./base"; +export { + BackgroundCode, + CourseNo, + ExtId, + RouteNo, + StampCode, + TitleCode, +} from "./base"; export { Car, CarSelector } from "./car"; export { Chara } from "./chara"; export { MissionState, MissionGrid } from "./mission"; export { Profile } from "./profile"; export { Settings } from "./settings"; +export { SelectedStamps } from "./stamps"; export { Story } from "./story"; export { Team, TeamAuto, TeamMember } from "./team"; export { Tickets } from "./tickets"; diff --git a/src/idz/userdb/model/stamps.ts b/src/idz/userdb/model/stamps.ts new file mode 100644 index 0000000..9452216 --- /dev/null +++ b/src/idz/userdb/model/stamps.ts @@ -0,0 +1,8 @@ +import { StampCode } from "./base"; + +export interface SelectedStamps { + stamp01: StampCode; + stamp02: StampCode; + stamp03: StampCode; + stamp04: StampCode; +} diff --git a/src/idz/userdb/repo.ts b/src/idz/userdb/repo.ts index 5079b88..4281103 100644 --- a/src/idz/userdb/repo.ts +++ b/src/idz/userdb/repo.ts @@ -63,6 +63,22 @@ export interface ProfileRepository { create(profile: Model.Profile): Promise>; } +export interface StampsRepository { + loadSelection(profileId: Id): Promise; + + loadAll(profileId: Id): Promise>; + + saveAll( + profileId: Id, + items: Set + ): Promise; + + saveSelection( + profileId: Id, + selection: Model.SelectedStamps + ): Promise; +} + export interface TeamRepository { find( extId: Model.ExtId, @@ -156,6 +172,8 @@ export interface Repositories { settings(): FacetRepository; + stamps(): StampsRepository; + // Also not a facet. w/e one step at a time. story(): FacetRepository; diff --git a/src/idz/userdb/request/saveProfile.ts b/src/idz/userdb/request/saveProfile.ts index de12e47..78aa860 100644 --- a/src/idz/userdb/request/saveProfile.ts +++ b/src/idz/userdb/request/saveProfile.ts @@ -1,7 +1,8 @@ -import { BackgroundCode, CourseNo, TitleCode } from "../model/base"; +import { BackgroundCode, CourseNo, StampCode, TitleCode } from "../model/base"; import { Car } from "../model/car"; import { MissionState } from "../model/mission"; import { Settings } from "../model/settings"; +import { SelectedStamps } from "../model/stamps"; import { Story } from "../model/story"; import { Tickets } from "../model/tickets"; import { Unlocks } from "../model/unlocks"; @@ -26,4 +27,6 @@ export interface SaveProfileRequest { unlocks: Unlocks; tickets: Tickets; settings: Settings; + selectedStamps?: SelectedStamps; + stamps?: Set; } diff --git a/src/idz/userdb/response/loadProfile.ts b/src/idz/userdb/response/loadProfile.ts index d214391..4c6c69f 100644 --- a/src/idz/userdb/response/loadProfile.ts +++ b/src/idz/userdb/response/loadProfile.ts @@ -1,8 +1,9 @@ -import { TitleCode } from "../model/base"; +import { StampCode, TitleCode } from "../model/base"; import { Car } from "../model/car"; import { Chara } from "../model/chara"; import { MissionState } from "../model/mission"; import { Settings } from "../model/settings"; +import { SelectedStamps } from "../model/stamps"; import { Story } from "../model/story"; import { Tickets } from "../model/tickets"; import { TimeAttackScore } from "../model/timeAttack"; @@ -31,5 +32,7 @@ export interface LoadProfileResponse { story: Story; unlocks: Unlocks; tickets: Tickets; + stamps: Set; + selectedStamps: SelectedStamps; // giga TODO } diff --git a/src/idz/userdb/sql/index.ts b/src/idz/userdb/sql/index.ts index 07f3ebc..ea28283 100644 --- a/src/idz/userdb/sql/index.ts +++ b/src/idz/userdb/sql/index.ts @@ -5,6 +5,7 @@ import { SqlCoursePlaysRepository } from "./coursePlays"; import { SqlMissionsRepository } from "./missions"; import { SqlProfileRepository } from "./profile"; import { SqlSettingsRepository } from "./settings"; +import { SqlStampsRepository } from "./stamps"; import { SqlStoryRepository } from "./story"; import { SqlTeamRepository } from "./team"; import { SqlTeamAutoRepository } from "./teamAuto"; @@ -49,6 +50,10 @@ export class SqlRepositories implements Repo.Repositories { return new SqlSettingsRepository(this._txn); } + stamps(): Repo.StampsRepository { + return new SqlStampsRepository(this._txn); + } + story(): Repo.FacetRepository { return new SqlStoryRepository(this._txn); } diff --git a/src/idz/userdb/sql/stamps.ts b/src/idz/userdb/sql/stamps.ts new file mode 100644 index 0000000..97d402a --- /dev/null +++ b/src/idz/userdb/sql/stamps.ts @@ -0,0 +1,97 @@ +import sql from "sql-bricks-postgres"; + +import { StampCode } from "../model/base"; +import { SelectedStamps } from "../model/stamps"; +import { Profile } from "../model/profile"; +import { StampsRepository } from "../repo"; +import { Id } from "../../../model"; +import { Transaction } from "../../../sql"; + +const defaultSelection: SelectedStamps = { + stamp01: 0 as StampCode, + stamp02: 0 as StampCode, + stamp03: 0 as StampCode, + stamp04: 0 as StampCode, +}; + +export class SqlStampsRepository implements StampsRepository { + constructor(private readonly _txn: Transaction) {} + + async loadAll(id: Id): Promise> { + const loadSql = sql + .select("stamp.stamp_no") + .from("idz_stamp_unlock stamp") + .join("idz_profile p", { "stamp.profile_id": "p.id" }) + .where("p.id", id); + + const rows = await this._txn.fetchRows(loadSql); + const result = new Set(); + + for (const row of rows) { + result.add(parseInt(row.stamp_no!) as StampCode); + } + + return result; + } + + async loadSelection(id: Id): Promise { + const loadSql = sql + .select("stamp.*") + .from("idz_stamp_selection stamp") + .join("idz_profile p", { "stamp.profile_id": "p.id" }) + .where("p.id", id); + + const row = await this._txn.fetchRow(loadSql); + + if (row === undefined) { + return { ...defaultSelection }; + } + + return { + stamp01: parseInt(row.stamp_01!) as StampCode, + stamp02: parseInt(row.stamp_02!) as StampCode, + stamp03: parseInt(row.stamp_03!) as StampCode, + stamp04: parseInt(row.stamp_04!) as StampCode, + }; + } + + async saveSelection( + profileId: Id, + selection: SelectedStamps + ): Promise { + const saveSql = sql + .insert("idz_stamp_selection", { + id: this._txn.generateId(), + profile_id: profileId, + stamp_01: selection.stamp01, + stamp_02: selection.stamp02, + stamp_03: selection.stamp03, + stamp_04: selection.stamp04, + }) + .onConflict("profile_id") + .doUpdate(["stamp_01", "stamp_02", "stamp_03", "stamp_04"]); + + await this._txn.modify(saveSql); + } + + async saveAll(profileId: Id, flags: Set): Promise { + const existing = await this.loadAll(profileId); + + for (const flag of flags) { + if (existing.has(flag)) { + continue; + } + + const saveSql = sql + .insert("idz_stamp_unlock", { + id: this._txn.generateId(), + profile_id: profileId, + stamp_no: flag, + }) + .onConflict("profile_id", "stamp_no") + .doNothing(); + + await this._txn.modify(saveSql); + } + } +}