Additional assorted layering cleanups
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
import sql from "sql-bricks-postgres";
|
||||
|
||||
import { BackgroundCode } from "../model/base";
|
||||
import { Profile } from "../model/profile";
|
||||
import { FlagRepository } from "../repo";
|
||||
import { Id } from "../../model";
|
||||
import { Transaction } from "../../sql";
|
||||
|
||||
export class SqlBackgroundsRepository
|
||||
implements FlagRepository<BackgroundCode> {
|
||||
constructor(private readonly _txn: Transaction) {}
|
||||
|
||||
async loadAll(id: Id<Profile>): Promise<Set<BackgroundCode>> {
|
||||
const loadSql = sql
|
||||
.select("bg.background_no")
|
||||
.from("idz_background_unlock bg")
|
||||
.join("idz_profile p", { "bg.profile_id": "p.id" })
|
||||
.where("p.id", id);
|
||||
|
||||
const rows = await this._txn.fetchRows(loadSql);
|
||||
const result = new Set<BackgroundCode>();
|
||||
|
||||
for (const row of rows) {
|
||||
result.add(parseInt(row.background_no) as BackgroundCode);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async saveAll(
|
||||
profileId: Id<Profile>,
|
||||
flags: Set<BackgroundCode>
|
||||
): Promise<void> {
|
||||
const existing = await this.loadAll(profileId);
|
||||
|
||||
for (const flag of flags) {
|
||||
if (existing.has(flag)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const saveSql = sql.insert("idz_background_unlock", {
|
||||
id: this._txn.generateId(),
|
||||
profile_id: profileId,
|
||||
background_no: flag,
|
||||
});
|
||||
|
||||
await this._txn.modify(saveSql);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
import sql from "sql-bricks-postgres";
|
||||
|
||||
import { Car, CarSelector } from "../model/car";
|
||||
import { Profile } from "../model/profile";
|
||||
import { CarRepository } from "../repo";
|
||||
import { Id } from "../../model";
|
||||
import { Row, Transaction } from "../../sql";
|
||||
|
||||
function _extractRow(row: Row): Car {
|
||||
return {
|
||||
selector: parseInt(row.selector) as CarSelector,
|
||||
field_00: parseInt(row.field_00),
|
||||
field_02: parseInt(row.field_02),
|
||||
field_04: row.field_04.split(",").map((x: string) => parseInt(x)),
|
||||
field_46: parseInt(row.field_46),
|
||||
field_48: parseInt(row.field_48),
|
||||
field_4a: parseInt(row.field_4a),
|
||||
field_4c: parseInt(row.field_4c),
|
||||
field_50_lo: parseInt(row.field_50_lo),
|
||||
field_50_hi: parseInt(row.field_50_hi),
|
||||
field_58: parseInt(row.field_58),
|
||||
field_5a: parseInt(row.field_5a),
|
||||
field_5b: parseInt(row.field_5b),
|
||||
field_5c: parseInt(row.field_5c),
|
||||
field_5e: parseInt(row.field_5e),
|
||||
};
|
||||
}
|
||||
|
||||
export class SqlCarRepository implements CarRepository {
|
||||
constructor(private readonly _txn: Transaction) {}
|
||||
|
||||
async countCars(profileId: Id<Profile>): Promise<number> {
|
||||
const countSql = sql
|
||||
.select("count(*) result")
|
||||
.from("idz_car c")
|
||||
.where("c.profile_id", profileId);
|
||||
|
||||
const row = await this._txn.fetchRow(countSql);
|
||||
|
||||
return parseInt(row!.result);
|
||||
}
|
||||
|
||||
async loadAllCars(profileId: Id<Profile>): Promise<Car[]> {
|
||||
const loadSql = sql
|
||||
.select("c.*")
|
||||
.from("idz_car c")
|
||||
.where("c.profile_id", profileId);
|
||||
|
||||
const rows = await this._txn.fetchRows(loadSql);
|
||||
|
||||
return rows.map(_extractRow);
|
||||
}
|
||||
|
||||
async loadSelectedCar(profileId: Id<Profile>): Promise<Car> {
|
||||
const loadSql = sql
|
||||
.select("c.*")
|
||||
.from("idz_car c")
|
||||
.join("idz_car_selection s", { "c.id": "s.car_id" })
|
||||
.where("s.id", profileId);
|
||||
|
||||
const row = await this._txn.fetchRow(loadSql);
|
||||
|
||||
if (row === undefined) {
|
||||
throw new Error(`Car selection not found, profileId=${profileId}`);
|
||||
}
|
||||
|
||||
return _extractRow(row);
|
||||
}
|
||||
|
||||
async saveCar(profileId: Id<Profile>, car: Car): Promise<void> {
|
||||
const saveSql = sql
|
||||
.insert("idz_car", {
|
||||
id: this._txn.generateId(),
|
||||
profile_id: profileId,
|
||||
selector: car.selector,
|
||||
field_00: car.field_00,
|
||||
field_02: car.field_02,
|
||||
field_04: car.field_04.join(","),
|
||||
field_46: car.field_46,
|
||||
field_48: car.field_48,
|
||||
field_4a: car.field_4a,
|
||||
field_4c: car.field_4c,
|
||||
field_50_lo: car.field_50_lo,
|
||||
field_50_hi: car.field_50_hi,
|
||||
field_58: car.field_58,
|
||||
field_5a: car.field_5a,
|
||||
field_5b: car.field_5b,
|
||||
field_5c: car.field_5c,
|
||||
field_5e: car.field_5e,
|
||||
})
|
||||
.onConflict("profile_id", "selector")
|
||||
.doUpdate([
|
||||
"field_00",
|
||||
"field_02",
|
||||
"field_04",
|
||||
"field_46",
|
||||
"field_48",
|
||||
"field_4a",
|
||||
"field_4c",
|
||||
"field_50_lo",
|
||||
"field_50_hi",
|
||||
"field_58",
|
||||
"field_5a",
|
||||
"field_5b",
|
||||
"field_5c",
|
||||
"field_5e",
|
||||
]);
|
||||
|
||||
await this._txn.modify(saveSql);
|
||||
}
|
||||
|
||||
async saveSelection(
|
||||
profileId: Id<Profile>,
|
||||
selector: CarSelector
|
||||
): Promise<void> {
|
||||
const findSql = sql
|
||||
.select("c.id")
|
||||
.from("idz_car c")
|
||||
.where("c.profile_id", profileId)
|
||||
.where("c.selector", selector);
|
||||
|
||||
const row = await this._txn.fetchRow(findSql);
|
||||
|
||||
if (row === undefined) {
|
||||
throw new Error("Selected car not found");
|
||||
}
|
||||
|
||||
const saveSql = sql
|
||||
.insert("idz_car_selection", {
|
||||
id: profileId,
|
||||
car_id: row.id,
|
||||
})
|
||||
.onConflict("id")
|
||||
.doUpdate(["car_id"]);
|
||||
|
||||
await this._txn.modify(saveSql);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import sql from "sql-bricks-postgres";
|
||||
|
||||
import { BackgroundCode, TitleCode } from "../model/base";
|
||||
import { Chara, Gender } from "../model/chara";
|
||||
import { Profile } from "../model/profile";
|
||||
import { FacetRepository } from "../repo";
|
||||
import { Id } from "../../model";
|
||||
import { Row, Transaction } from "../../sql";
|
||||
|
||||
export function _extractChara(row: Row): Chara {
|
||||
return {
|
||||
gender: row.gender as Gender,
|
||||
field_02: parseInt(row.field_02),
|
||||
field_04: parseInt(row.field_04),
|
||||
field_06: parseInt(row.field_06),
|
||||
field_08: parseInt(row.field_08),
|
||||
field_0a: parseInt(row.field_0a),
|
||||
field_0c: parseInt(row.field_0c),
|
||||
field_0e: parseInt(row.field_0e),
|
||||
title: parseInt(row.title) as TitleCode,
|
||||
background: parseInt(row.background) as BackgroundCode,
|
||||
};
|
||||
}
|
||||
|
||||
export class SqlCharaRepository implements FacetRepository<Chara> {
|
||||
constructor(private readonly _txn: Transaction) {}
|
||||
|
||||
async load(profileId: Id<Profile>): Promise<Chara> {
|
||||
const loadSql = sql
|
||||
.select("c.*")
|
||||
.from("idz_chara c")
|
||||
.where("c.id", profileId);
|
||||
|
||||
const row = await this._txn.fetchRow(loadSql);
|
||||
|
||||
if (row === undefined) {
|
||||
throw new Error(`Chara not found: profileId=${profileId}`);
|
||||
}
|
||||
|
||||
return _extractChara(row);
|
||||
}
|
||||
|
||||
async save(profileId: Id<Profile>, chara: Chara): Promise<void> {
|
||||
const saveSql = sql
|
||||
.insert("idz_chara", {
|
||||
id: profileId,
|
||||
gender: chara.gender,
|
||||
field_02: chara.field_02,
|
||||
field_04: chara.field_04,
|
||||
field_06: chara.field_06,
|
||||
field_08: chara.field_08,
|
||||
field_0a: chara.field_0a,
|
||||
field_0c: chara.field_0c,
|
||||
field_0e: chara.field_0e,
|
||||
title: chara.title,
|
||||
background: chara.background,
|
||||
})
|
||||
.onConflict("id")
|
||||
.doUpdate([
|
||||
"field_02",
|
||||
"field_04",
|
||||
"field_06",
|
||||
"field_08",
|
||||
"field_0a",
|
||||
"field_0c",
|
||||
"field_0e",
|
||||
"title",
|
||||
"background",
|
||||
]);
|
||||
|
||||
await this._txn.modify(saveSql);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import sql from "sql-bricks-postgres";
|
||||
|
||||
import { CourseNo } from "../model/base";
|
||||
import { Profile } from "../model/profile";
|
||||
import { CoursePlaysRepository } from "../repo";
|
||||
import { Id } from "../../model";
|
||||
import { Transaction } from "../../sql";
|
||||
|
||||
export class SqlCoursePlaysRepository implements CoursePlaysRepository {
|
||||
constructor(private readonly _txn: Transaction) {}
|
||||
|
||||
async loadAll(profileId: Id<Profile>): Promise<Map<CourseNo, number>> {
|
||||
const loadSql = sql
|
||||
.select("cp.course_no", "cp.count")
|
||||
.from("idz_course_plays cp")
|
||||
.where("cp.profile_id", profileId);
|
||||
|
||||
const rows = await this._txn.fetchRows(loadSql);
|
||||
const result = new Map<CourseNo, number>();
|
||||
|
||||
for (const row of rows) {
|
||||
const courseNo = parseInt(row.course_no) as CourseNo;
|
||||
const count = parseInt(row.count);
|
||||
|
||||
result.set(courseNo, count);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async saveAll(
|
||||
profileId: Id<Profile>,
|
||||
plays: Map<CourseNo, number>
|
||||
): Promise<void> {
|
||||
for (const [k, v] of plays) {
|
||||
const saveSql = sql
|
||||
.insert("idz_course_plays", {
|
||||
id: this._txn.generateId(),
|
||||
profile_id: profileId,
|
||||
course_no: k,
|
||||
count: v,
|
||||
})
|
||||
.onConflict("profile_id", "course_no")
|
||||
.doUpdate(["count"]);
|
||||
|
||||
await this._txn.modify(saveSql);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import { SqlBackgroundsRepository } from "./backgrounds";
|
||||
import { SqlCarRepository } from "./car";
|
||||
import { SqlCharaRepository } from "./chara";
|
||||
import { SqlCoursePlaysRepository } from "./coursePlays";
|
||||
import { SqlMissionsRepository } from "./missions";
|
||||
import { SqlProfileRepository } from "./profile";
|
||||
import { SqlSettingsRepository } from "./settings";
|
||||
import { SqlStoryRepository } from "./story";
|
||||
import { SqlTeamRepository } from "./team";
|
||||
import { SqlTeamAutoRepository } from "./teamAuto";
|
||||
import { SqlTeamMemberRepository } from "./teamMember";
|
||||
import { SqlTeamReservationRepository } from "./teamReservation";
|
||||
import { SqlTicketsRepository } from "./tickets";
|
||||
import { SqlTimeAttackRepository } from "./timeAttack";
|
||||
import { SqlTitlesRepository } from "./titles";
|
||||
import { SqlUnlocksRepository } from "./unlocks";
|
||||
import * as Model from "../model";
|
||||
import * as Repo from "../repo";
|
||||
import { Transaction } from "../../sql";
|
||||
|
||||
export class SqlRepositories implements Repo.Repositories {
|
||||
constructor(private readonly _txn: Transaction) {}
|
||||
|
||||
backgrounds(): Repo.FlagRepository<Model.BackgroundCode> {
|
||||
return new SqlBackgroundsRepository(this._txn);
|
||||
}
|
||||
|
||||
car(): Repo.CarRepository {
|
||||
return new SqlCarRepository(this._txn);
|
||||
}
|
||||
|
||||
chara(): Repo.FacetRepository<Model.Chara> {
|
||||
return new SqlCharaRepository(this._txn);
|
||||
}
|
||||
|
||||
coursePlays(): Repo.CoursePlaysRepository {
|
||||
return new SqlCoursePlaysRepository(this._txn);
|
||||
}
|
||||
|
||||
missions(): Repo.FacetRepository<Model.MissionState> {
|
||||
return new SqlMissionsRepository(this._txn);
|
||||
}
|
||||
|
||||
profile(): Repo.ProfileRepository {
|
||||
return new SqlProfileRepository(this._txn);
|
||||
}
|
||||
|
||||
settings(): Repo.FacetRepository<Model.Settings> {
|
||||
return new SqlSettingsRepository(this._txn);
|
||||
}
|
||||
|
||||
story(): Repo.FacetRepository<Model.Story> {
|
||||
return new SqlStoryRepository(this._txn);
|
||||
}
|
||||
|
||||
teams(): Repo.TeamRepository {
|
||||
return new SqlTeamRepository(this._txn);
|
||||
}
|
||||
|
||||
teamAuto(): Repo.TeamAutoRepository {
|
||||
return new SqlTeamAutoRepository(this._txn);
|
||||
}
|
||||
|
||||
teamMembers(): Repo.TeamMemberRepository {
|
||||
return new SqlTeamMemberRepository(this._txn);
|
||||
}
|
||||
|
||||
teamReservations(): Repo.TeamReservationRepository {
|
||||
return new SqlTeamReservationRepository(this._txn);
|
||||
}
|
||||
|
||||
tickets(): Repo.FacetRepository<Model.Tickets> {
|
||||
return new SqlTicketsRepository(this._txn);
|
||||
}
|
||||
|
||||
timeAttack(): Repo.TimeAttackRepository {
|
||||
return new SqlTimeAttackRepository(this._txn);
|
||||
}
|
||||
|
||||
titles(): Repo.FlagRepository<Model.TitleCode> {
|
||||
return new SqlTitlesRepository(this._txn);
|
||||
}
|
||||
|
||||
unlocks(): Repo.FacetRepository<Model.Unlocks> {
|
||||
return new SqlUnlocksRepository(this._txn);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import sql from "sql-bricks-postgres";
|
||||
|
||||
import { MissionGrid, MissionState } from "../model/mission";
|
||||
import { Profile } from "../model/profile";
|
||||
import { FacetRepository } from "../repo";
|
||||
import { Id } from "../../model";
|
||||
import { Transaction } from "../../sql";
|
||||
|
||||
export class SqlMissionsRepository implements FacetRepository<MissionState> {
|
||||
constructor(private readonly _txn: Transaction) {}
|
||||
|
||||
async load(profileId: Id<Profile>): Promise<MissionState> {
|
||||
const result: MissionState = {
|
||||
solo: new Array<MissionGrid>(),
|
||||
team: new Array<MissionGrid>(),
|
||||
};
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const soloGrid: MissionGrid = { cells: new Array<number>() };
|
||||
const teamGrid: MissionGrid = { cells: new Array<number>() };
|
||||
|
||||
for (let j = 0; j < 9; j++) {
|
||||
soloGrid.cells.push(0);
|
||||
teamGrid.cells.push(0);
|
||||
}
|
||||
|
||||
result.solo.push(soloGrid);
|
||||
result.team.push(teamGrid);
|
||||
}
|
||||
|
||||
const loadSoloSql = sql
|
||||
.select("sm.*")
|
||||
.from("idz_solo_mission_state sm")
|
||||
.where("sm.profile_id", profileId);
|
||||
|
||||
const rows = await this._txn.fetchRows(loadSoloSql);
|
||||
|
||||
for (const row of rows) {
|
||||
const gridNo = parseInt(row.grid_no);
|
||||
const cellNo = parseInt(row.cell_no);
|
||||
const value = parseInt(row.value);
|
||||
|
||||
result.solo[gridNo].cells[cellNo] = value;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async save(profileId: Id<Profile>, mission: MissionState): Promise<void> {
|
||||
const existing = await this.load(profileId);
|
||||
|
||||
for (let i = 0; i < mission.solo.length; i++) {
|
||||
const exGrid = existing.solo[i].cells;
|
||||
const grid = mission.solo[i].cells;
|
||||
|
||||
for (let j = 0; j < grid.length; j++) {
|
||||
if (grid[j] === exGrid[j]) {
|
||||
continue; // Most if not all cells are unaffected on save
|
||||
}
|
||||
|
||||
const saveSql = sql
|
||||
.insert("idz_solo_mission_state", {
|
||||
id: this._txn.generateId(),
|
||||
profile_id: profileId,
|
||||
grid_no: i,
|
||||
cell_no: j,
|
||||
value: grid[j],
|
||||
})
|
||||
.onConflict("profile_id", "grid_no", "cell_no")
|
||||
.doUpdate(["value"]);
|
||||
|
||||
await this._txn.modify(saveSql);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
import sql from "sql-bricks-postgres";
|
||||
|
||||
import { Profile } from "../model/profile";
|
||||
import { ProfileRepository } from "../repo";
|
||||
import { AimeId, Id } from "../../model";
|
||||
import { Row, Transaction } from "../../sql";
|
||||
|
||||
export function _extractProfile(row: Row): Profile {
|
||||
return {
|
||||
aimeId: parseInt(row.aime_id) as AimeId,
|
||||
name: row.name,
|
||||
lv: parseInt(row.lv),
|
||||
exp: parseInt(row.exp),
|
||||
fame: parseInt(row.fame),
|
||||
dpoint: parseInt(row.dpoint),
|
||||
mileage: parseInt(row.mileage),
|
||||
accessTime: new Date(row.access_time),
|
||||
registerTime: new Date(row.register_time),
|
||||
};
|
||||
}
|
||||
|
||||
export class SqlProfileRepository implements ProfileRepository {
|
||||
constructor(private readonly _txn: Transaction) {}
|
||||
|
||||
async find(aimeId: AimeId): Promise<Id<Profile>> {
|
||||
const profileId = await this.peek(aimeId);
|
||||
|
||||
if (profileId === undefined) {
|
||||
throw new Error(`Profile not found for Aime ID ${aimeId}`);
|
||||
}
|
||||
|
||||
return profileId;
|
||||
}
|
||||
|
||||
async peek(aimeId: AimeId): Promise<Id<Profile> | undefined> {
|
||||
const lookupSql = sql
|
||||
.select("p.id")
|
||||
.from("idz_profile p")
|
||||
.join("aime_player r", { "p.player_id": "r.id" })
|
||||
.where("r.ext_id", aimeId);
|
||||
|
||||
const row = await this._txn.fetchRow(lookupSql);
|
||||
|
||||
if (row === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return row.id as Id<Profile>;
|
||||
}
|
||||
|
||||
async load(id: Id<Profile>): Promise<Profile> {
|
||||
const loadSql = sql
|
||||
.select("p.*", "r.ext_id as aime_id")
|
||||
.from("idz_profile p")
|
||||
.join("aime_player r", { "p.player_id": "r.id" })
|
||||
.where("p.id", id);
|
||||
|
||||
const row = await this._txn.fetchRow(loadSql);
|
||||
|
||||
if (row === undefined) {
|
||||
throw new Error(`Profile not found, id=${id}`);
|
||||
}
|
||||
|
||||
return _extractProfile(row);
|
||||
}
|
||||
|
||||
async save(id: Id<Profile>, profile: Profile): Promise<void> {
|
||||
const saveSql = sql
|
||||
.update("idz_profile", {
|
||||
lv: profile.lv,
|
||||
exp: profile.exp,
|
||||
fame: profile.fame,
|
||||
dpoint: profile.dpoint,
|
||||
mileage: profile.mileage,
|
||||
access_time: profile.accessTime.toISOString(),
|
||||
})
|
||||
.where("id", id);
|
||||
|
||||
await this._txn.modify(saveSql);
|
||||
}
|
||||
|
||||
async create(profile: Profile): Promise<Id<Profile>> {
|
||||
const findSql = sql
|
||||
.select("r.id")
|
||||
.from("aime_player r")
|
||||
.where("r.ext_id", profile.aimeId);
|
||||
|
||||
const row = await this._txn.fetchRow(findSql);
|
||||
|
||||
if (row === undefined) {
|
||||
throw new Error("Aime ID not found");
|
||||
}
|
||||
|
||||
const id = this._txn.generateId<Profile>();
|
||||
const playerId = row.id;
|
||||
|
||||
const createSql = sql.insert("idz_profile", {
|
||||
id: id,
|
||||
player_id: playerId,
|
||||
name: profile.name,
|
||||
lv: profile.lv,
|
||||
exp: profile.exp,
|
||||
fame: profile.fame,
|
||||
dpoint: profile.dpoint,
|
||||
mileage: profile.mileage,
|
||||
register_time: profile.registerTime.toISOString(),
|
||||
access_time: profile.accessTime.toISOString(),
|
||||
});
|
||||
|
||||
await this._txn.modify(createSql);
|
||||
|
||||
return id;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import sql from "sql-bricks-postgres";
|
||||
|
||||
import { Settings } from "../model/settings";
|
||||
import { Profile } from "../model/profile";
|
||||
import { FacetRepository } from "../repo";
|
||||
import { Id } from "../../model";
|
||||
import { Transaction } from "../../sql";
|
||||
|
||||
export class SqlSettingsRepository implements FacetRepository<Settings> {
|
||||
constructor(private readonly _txn: Transaction) {}
|
||||
|
||||
async load(profileId: Id<Profile>): Promise<Settings> {
|
||||
const loadSql = sql
|
||||
.select("s.*")
|
||||
.from("idz_settings s")
|
||||
.where("s.id", profileId);
|
||||
|
||||
const row = await this._txn.fetchRow(loadSql);
|
||||
|
||||
if (row === undefined) {
|
||||
throw new Error(`Settings not found, profileId=${profileId}`);
|
||||
}
|
||||
|
||||
return {
|
||||
music: parseInt(row.music),
|
||||
pack: parseInt(row.pack),
|
||||
paperCup: parseInt(row.paper_cup),
|
||||
gauges: parseInt(row.gauges),
|
||||
};
|
||||
}
|
||||
|
||||
async save(profileId: Id<Profile>, settings: Settings): Promise<void> {
|
||||
const saveSql = sql
|
||||
.insert("idz_settings", {
|
||||
id: profileId,
|
||||
music: settings.music,
|
||||
pack: settings.pack,
|
||||
paper_cup: settings.paperCup,
|
||||
gauges: settings.gauges,
|
||||
})
|
||||
.onConflict("id")
|
||||
.doUpdate(["music", "pack", "paper_cup", "gauges"]);
|
||||
|
||||
await this._txn.modify(saveSql);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import sql from "sql-bricks-postgres";
|
||||
|
||||
import { Profile } from "../model/profile";
|
||||
import { Story, StoryRow, StoryCell } from "../model/story";
|
||||
import { FacetRepository } from "../repo";
|
||||
import { Id } from "../../model";
|
||||
import { Transaction } from "../../sql";
|
||||
|
||||
export class SqlStoryRepository implements FacetRepository<Story> {
|
||||
constructor(private readonly _txn: Transaction) {}
|
||||
|
||||
async load(profileId: Id<Profile>): Promise<Story> {
|
||||
const loadSql = sql
|
||||
.select("s.*")
|
||||
.from("idz_story_state s")
|
||||
.where("s.id", profileId);
|
||||
|
||||
const header = await this._txn.fetchRow(loadSql);
|
||||
|
||||
// Must succeed even if nonexistent (required by save method below)
|
||||
|
||||
const result = {
|
||||
x: header !== undefined ? parseInt(header.x) : 0,
|
||||
y: header !== undefined ? parseInt(header.y) : 0,
|
||||
rows: new Array<StoryRow>(),
|
||||
};
|
||||
|
||||
for (let i = 0; i < 27; i++) {
|
||||
const row: StoryRow = { cells: new Array<StoryCell>() };
|
||||
|
||||
for (let j = 0; j < 9; j++) {
|
||||
row.cells.push({ a: 0, b: 0 });
|
||||
}
|
||||
|
||||
result.rows.push(row);
|
||||
}
|
||||
|
||||
const loadCellSql = sql
|
||||
.select("sc.*")
|
||||
.from("idz_story_cell_state sc")
|
||||
.where("sc.profile_id", profileId);
|
||||
|
||||
const rows = await this._txn.fetchRows(loadCellSql);
|
||||
|
||||
for (const row of rows) {
|
||||
const rowNo = parseInt(row.row_no);
|
||||
const colNo = parseInt(row.col_no);
|
||||
const cell = result.rows[rowNo].cells[colNo];
|
||||
|
||||
cell.a = parseInt(row.a);
|
||||
cell.b = parseInt(row.b);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async save(profileId: Id<Profile>, story: Story): Promise<void> {
|
||||
const existing = await this.load(profileId);
|
||||
|
||||
const headSql = sql
|
||||
.insert("idz_story_state", {
|
||||
id: profileId,
|
||||
x: story.x,
|
||||
y: story.y,
|
||||
})
|
||||
.onConflict("id")
|
||||
.doUpdate(["x", "y"]);
|
||||
|
||||
await this._txn.modify(headSql);
|
||||
|
||||
for (let i = 0; i < story.rows.length; i++) {
|
||||
const exRow = existing.rows[i];
|
||||
const row = story.rows[i];
|
||||
|
||||
for (let j = 0; j < row.cells.length; j++) {
|
||||
const exCell = exRow.cells[j];
|
||||
const cell = row.cells[j];
|
||||
|
||||
if (cell.a === exCell.a && cell.b === exCell.b) {
|
||||
continue; // Most if not all cells are unchanged on profile save.
|
||||
}
|
||||
|
||||
const cellSql = sql
|
||||
.insert("idz_story_cell_state", {
|
||||
id: this._txn.generateId(),
|
||||
profile_id: profileId,
|
||||
row_no: i,
|
||||
col_no: j,
|
||||
a: cell.a,
|
||||
b: cell.b,
|
||||
})
|
||||
.onConflict("profile_id", "row_no", "col_no")
|
||||
.doUpdate(["a", "b"]);
|
||||
|
||||
await this._txn.modify(cellSql);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
import sql from "sql-bricks-postgres";
|
||||
|
||||
import { ExtId } from "../model/base";
|
||||
import { Team } from "../model/team";
|
||||
import { TeamSpec, TeamRepository } from "../repo";
|
||||
import { Id, generateExtId } from "../../model";
|
||||
import { Transaction } from "../../sql";
|
||||
|
||||
export class SqlTeamRepository implements TeamRepository {
|
||||
constructor(private readonly _txn: Transaction) {}
|
||||
|
||||
async find(extId: ExtId<Team>): Promise<Id<Team>> {
|
||||
const findSql = sql
|
||||
.select("t.id")
|
||||
.from("idz_team t")
|
||||
.where("t.ext_id", extId);
|
||||
|
||||
const row = await this._txn.fetchRow(findSql);
|
||||
|
||||
if (row === undefined) {
|
||||
throw new Error(`Team not found for ExtID ${extId}`);
|
||||
}
|
||||
|
||||
return row.id as Id<Team>;
|
||||
}
|
||||
|
||||
async load(id: Id<Team>): Promise<Team> {
|
||||
const loadSql = sql
|
||||
.select("t.*")
|
||||
.from("idz_team t")
|
||||
.where("t.id", id);
|
||||
|
||||
const row = await this._txn.fetchRow(loadSql);
|
||||
|
||||
if (row === undefined) {
|
||||
throw new Error("Team not found");
|
||||
}
|
||||
|
||||
return {
|
||||
extId: parseInt(row.ext_id) as ExtId<Team>,
|
||||
name: row.name,
|
||||
nameBg: parseInt(row.name_bg),
|
||||
nameFx: parseInt(row.name_fx),
|
||||
registerTime: new Date(row.register_time),
|
||||
};
|
||||
}
|
||||
|
||||
async save(id: Id<Team>, team: Team): Promise<void> {
|
||||
const saveSql = sql
|
||||
.update("idz_team", {
|
||||
name_bg: team.nameBg,
|
||||
name_fx: team.nameFx,
|
||||
})
|
||||
.where("id", id);
|
||||
|
||||
await this._txn.modify(saveSql);
|
||||
}
|
||||
|
||||
async create(team: TeamSpec): Promise<[Id<Team>, ExtId<Team>]> {
|
||||
const id = this._txn.generateId<Team>();
|
||||
const extId = generateExtId() as ExtId<Team>;
|
||||
|
||||
const createSql = sql.insert("idz_team", {
|
||||
id: id,
|
||||
ext_id: extId,
|
||||
name: team.name,
|
||||
name_bg: team.nameBg,
|
||||
name_fx: team.nameFx,
|
||||
register_time: team.registerTime,
|
||||
});
|
||||
|
||||
await this._txn.modify(createSql);
|
||||
|
||||
return [id, extId];
|
||||
}
|
||||
|
||||
async delete(id: Id<Team>): Promise<void> {
|
||||
const deleteSql = sql.delete("idz_team").where("id", id);
|
||||
|
||||
await this._txn.modify(deleteSql);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import sql from "sql-bricks-postgres";
|
||||
|
||||
import { Team, TeamAuto } from "../model/team";
|
||||
import { TeamAutoRepository } from "../repo";
|
||||
import { Id } from "../../model";
|
||||
import { Transaction } from "../../sql";
|
||||
|
||||
export class SqlTeamAutoRepository implements TeamAutoRepository {
|
||||
constructor(private readonly _txn: Transaction) {}
|
||||
|
||||
async peek(): Promise<[TeamAuto, Id<Team>] | undefined> {
|
||||
const peekSql = sql
|
||||
.select("tt.*")
|
||||
.from("idz_team_auto tt")
|
||||
.orderBy("serial_no desc", "name_idx desc")
|
||||
.limit(1);
|
||||
|
||||
const row = await this._txn.fetchRow(peekSql);
|
||||
|
||||
return (
|
||||
row && [
|
||||
{
|
||||
serialNo: parseInt(row.serial_no),
|
||||
nameIdx: parseInt(row.name_idx),
|
||||
},
|
||||
row.id as Id<Team>,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
async push(teamId: Id<Team>, auto: TeamAuto): Promise<void> {
|
||||
const pushSql = sql.insert("idz_team_auto", {
|
||||
id: teamId,
|
||||
serial_no: auto.serialNo,
|
||||
name_idx: auto.nameIdx,
|
||||
});
|
||||
|
||||
await this._txn.modify(pushSql);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
import sql from "sql-bricks-postgres";
|
||||
|
||||
import { Profile } from "../model/profile";
|
||||
import { Team, TeamMember } from "../model/team";
|
||||
import { TeamMemberRepository } from "../repo";
|
||||
import { _extractProfile } from "./profile";
|
||||
import { _extractChara } from "./chara";
|
||||
import { Id } from "../../model";
|
||||
import { Transaction } from "../../sql";
|
||||
|
||||
export class SqlTeamMemberRepository implements TeamMemberRepository {
|
||||
constructor(private readonly _txn: Transaction) {}
|
||||
|
||||
async findTeam(profileId: Id<Profile>): Promise<Id<Team> | undefined> {
|
||||
const findSql = sql
|
||||
.select("tm.team_id")
|
||||
.from("idz_team_member tm")
|
||||
.where("tm.id", profileId);
|
||||
|
||||
const row = await this._txn.fetchRow(findSql);
|
||||
|
||||
if (row === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return row.team_id as Id<Team>;
|
||||
}
|
||||
|
||||
async findLeader(teamId: Id<Team>): Promise<Id<Profile> | undefined> {
|
||||
const findSql = sql
|
||||
.select("tm.id")
|
||||
.from("idz_team_member tm")
|
||||
.where("tm.team_id", teamId)
|
||||
.where("tm.leader", true);
|
||||
|
||||
const row = await this._txn.fetchRow(findSql);
|
||||
|
||||
if (row === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return row.id as Id<Profile>;
|
||||
}
|
||||
|
||||
async loadRoster(teamId: Id<Team>): Promise<TeamMember[]> {
|
||||
const loadSql = sql
|
||||
.select("tm.*", "p.*", "c.*", "r.ext_id as aime_id")
|
||||
.from("idz_team_member tm")
|
||||
.join("idz_profile p", { "tm.id": "p.id" })
|
||||
.join("idz_chara c", { "tm.id": "c.id" })
|
||||
.join("aime_player r", { "p.player_id": "r.id" })
|
||||
.where("tm.team_id", teamId);
|
||||
|
||||
const rows = await this._txn.fetchRows(loadSql);
|
||||
|
||||
return rows.map(row => ({
|
||||
profile: _extractProfile(row),
|
||||
chara: _extractChara(row),
|
||||
leader: !!row.leader,
|
||||
joinTime: new Date(row.join_time),
|
||||
}));
|
||||
}
|
||||
|
||||
async join(
|
||||
teamId: Id<Team>,
|
||||
profileId: Id<Profile>,
|
||||
timestamp: Date
|
||||
): Promise<void> {
|
||||
// Lock the team record to avoid race conditions.
|
||||
|
||||
const lockSql = sql
|
||||
.select("id")
|
||||
.from("idz_team")
|
||||
.where("id", teamId)
|
||||
.forUpdate();
|
||||
|
||||
await this._txn.modify(lockSql);
|
||||
|
||||
// Double-check (with lock held) that there is room to join this team.
|
||||
// If this fails then the error will propagate to the client and it will
|
||||
// retry, and, assuming we have a race between two new registrations to
|
||||
// take up the last slot in the current auto-team, hopefully succeed.
|
||||
//
|
||||
// There is arguably some business logic pollution here, since we have a
|
||||
// hard-coded maximum team size imposed by the protocol. This is why a
|
||||
// three-layered server would be better than our two-layered server.
|
||||
|
||||
const countSql = sql
|
||||
.select("count(*) as count")
|
||||
.from("idz_team_member")
|
||||
.where("team_id", teamId);
|
||||
|
||||
const row = await this._txn.fetchRow(countSql);
|
||||
|
||||
if (parseInt(row!.count) >= 6) {
|
||||
throw new Error(`Team ${teamId} is full`);
|
||||
}
|
||||
|
||||
// Do upsert
|
||||
|
||||
const joinSql = sql
|
||||
.insert("idz_team_member", {
|
||||
id: profileId,
|
||||
team_id: teamId,
|
||||
leader: false,
|
||||
join_time: timestamp,
|
||||
})
|
||||
.onConflict("id")
|
||||
.doUpdate(["team_id", "leader", "join_time"]);
|
||||
|
||||
await this._txn.modify(joinSql);
|
||||
}
|
||||
|
||||
async leave(teamId: Id<Team>, profileId: Id<Profile>): Promise<void> {
|
||||
const leaveSql = sql
|
||||
.delete("idz_team_member")
|
||||
.where("team_id", teamId)
|
||||
.where("id", profileId);
|
||||
|
||||
await this._txn.modify(leaveSql);
|
||||
}
|
||||
|
||||
async makeLeader(teamId: Id<Team>, profileId: Id<Profile>): Promise<void> {
|
||||
const clearSql = sql
|
||||
.update("idz_team_member", {
|
||||
leader: false,
|
||||
})
|
||||
.where("team_id", teamId);
|
||||
|
||||
await this._txn.modify(clearSql);
|
||||
|
||||
const setSql = sql
|
||||
.update("idz_team_member", {
|
||||
leader: true,
|
||||
})
|
||||
.where("id", profileId)
|
||||
.where("team_id", teamId);
|
||||
|
||||
await this._txn.modify(setSql);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
import sql from "sql-bricks-postgres";
|
||||
|
||||
import { Team } from "../model/team";
|
||||
import { TeamReservationRepository } from "../repo";
|
||||
import { AimeId, Id } from "../../model";
|
||||
import { Transaction } from "../../sql";
|
||||
|
||||
export class SqlTeamReservationRepository
|
||||
implements TeamReservationRepository {
|
||||
constructor(private readonly _txn: Transaction) {}
|
||||
|
||||
private async _lockTeam(teamId: Id<Team>): Promise<void> {
|
||||
const lockSql = sql
|
||||
.select("t.id")
|
||||
.from("idz_team t")
|
||||
.where("t.id", teamId)
|
||||
.forUpdate();
|
||||
|
||||
await this._txn.modify(lockSql);
|
||||
}
|
||||
|
||||
async occupancyHack(teamId: Id<Team>): Promise<number> {
|
||||
await this._lockTeam(teamId);
|
||||
|
||||
const memberSql = sql
|
||||
.select("count(*) as count")
|
||||
.from("idz_team_member tm")
|
||||
.where("tm.team_id", teamId);
|
||||
|
||||
const memberRes = await this._txn.fetchRow(memberSql);
|
||||
const memberCount = parseInt(memberRes!.count);
|
||||
|
||||
const reservSql = sql
|
||||
.select("count(*) as count")
|
||||
.from("idz_team_reservation tr")
|
||||
.where("tr.team_id", teamId);
|
||||
|
||||
const reservRes = await this._txn.fetchRow(reservSql);
|
||||
const reservCount = parseInt(reservRes!.count);
|
||||
|
||||
return memberCount + reservCount;
|
||||
}
|
||||
|
||||
async reserveHack(
|
||||
teamId: Id<Team>,
|
||||
aimeId: AimeId,
|
||||
timestamp: Date,
|
||||
leader?: "leader"
|
||||
): Promise<void> {
|
||||
const lookupSql = sql
|
||||
.select("r.id")
|
||||
.from("aime_player r")
|
||||
.where("r.ext_id", aimeId);
|
||||
|
||||
const row = await this._txn.fetchRow(lookupSql);
|
||||
|
||||
if (row === undefined) {
|
||||
throw new Error(`Unknown Aime ID ${aimeId}`);
|
||||
}
|
||||
|
||||
const playerId = row.id;
|
||||
|
||||
const insertSql = sql
|
||||
.insert("idz_team_reservation", {
|
||||
id: playerId,
|
||||
team_id: teamId,
|
||||
join_time: timestamp,
|
||||
leader: leader === "leader",
|
||||
})
|
||||
.onConflict("id")
|
||||
.doUpdate(["team_id"]);
|
||||
|
||||
await this._txn.modify(insertSql);
|
||||
}
|
||||
|
||||
async commitHack(aimeId: AimeId): Promise<void> {
|
||||
const lookupSql = sql
|
||||
.select("p.id as profile_id", "tr.*")
|
||||
.from("idz_profile p")
|
||||
.join("aime_player r", { "p.player_id": "r.id" })
|
||||
.join("idz_team_reservation tr", { "r.id": "tr.id" })
|
||||
.where("r.ext_id", aimeId);
|
||||
|
||||
const row = await this._txn.fetchRow(lookupSql);
|
||||
|
||||
if (row === undefined) {
|
||||
throw new Error(`Reservation not found for Aime ID ${aimeId}`);
|
||||
}
|
||||
|
||||
const insertSql = sql.insert("idz_team_member", {
|
||||
id: row.profile_id,
|
||||
team_id: row.team_id,
|
||||
join_time: row.join_time,
|
||||
leader: row.leader,
|
||||
});
|
||||
|
||||
await this._txn.modify(insertSql);
|
||||
|
||||
const cleanupSql = sql.delete("idz_team_reservation").where("id", row.id);
|
||||
|
||||
await this._txn.modify(cleanupSql);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import sql from "sql-bricks-postgres";
|
||||
|
||||
import { Profile } from "../model/profile";
|
||||
import { Tickets } from "../model/tickets";
|
||||
import { FacetRepository } from "../repo";
|
||||
import { Id } from "../../model";
|
||||
import { Transaction } from "../../sql";
|
||||
|
||||
// TODO free continue
|
||||
|
||||
export class SqlTicketsRepository implements FacetRepository<Tickets> {
|
||||
constructor(private readonly _txn: Transaction) {}
|
||||
|
||||
async load(profileId: Id<Profile>): Promise<Tickets> {
|
||||
const loadSql = sql
|
||||
.select("fc.*")
|
||||
.from("idz_free_car fc")
|
||||
.where("fc.id", profileId);
|
||||
|
||||
const row = await this._txn.fetchRow(loadSql);
|
||||
|
||||
return {
|
||||
freeCar: row && {
|
||||
validFrom: new Date(row.valid_from),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async save(profileId: Id<Profile>, tickets: Tickets): Promise<void> {
|
||||
const { freeCar } = tickets;
|
||||
|
||||
if (!freeCar) {
|
||||
const delSql = sql.delete("idz_free_car").where("id", profileId);
|
||||
|
||||
await this._txn.modify(delSql);
|
||||
} else {
|
||||
const saveSql = sql
|
||||
.insert("idz_free_car", {
|
||||
id: profileId,
|
||||
valid_from: freeCar.validFrom,
|
||||
})
|
||||
.onConflict("id")
|
||||
.doUpdate(["valid_from"]);
|
||||
|
||||
await this._txn.modify(saveSql);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
import sql from "sql-bricks-postgres";
|
||||
|
||||
import { RouteNo } from "../model/base";
|
||||
import { CarSelector } from "../model/car";
|
||||
import { Profile } from "../model/profile";
|
||||
import { TimeAttackScore } from "../model/timeAttack";
|
||||
import { TimeAttackRepository, TopTenResult } from "../repo";
|
||||
import { Id } from "../../model";
|
||||
import { Row, Transaction } from "../../sql";
|
||||
|
||||
function _extractRow(row: Row): TimeAttackScore {
|
||||
return {
|
||||
routeNo: parseInt(row.route_no) as RouteNo,
|
||||
timestamp: new Date(row.timestamp),
|
||||
flags: parseInt(row.flags),
|
||||
totalTime: parseFloat(row.total_time),
|
||||
sectionTimes: row.section_times.split(",").map(parseFloat),
|
||||
grade: parseInt(row.grade),
|
||||
carSelector: parseInt(row.car_selector) as CarSelector,
|
||||
};
|
||||
}
|
||||
|
||||
export class SqlTimeAttackRepository implements TimeAttackRepository {
|
||||
constructor(private readonly _txn: Transaction) {}
|
||||
|
||||
async loadTopTen(
|
||||
routeNo: RouteNo,
|
||||
minTimestamp: Date
|
||||
): Promise<TopTenResult[]> {
|
||||
const loadSql = sql
|
||||
.select("p.name", "ta.*")
|
||||
.from("idz_ta_best ta")
|
||||
.join("idz_profile p", { "ta.profile_id": "p.id" })
|
||||
.where("ta.route_no", routeNo)
|
||||
.where(sql.gt("ta.timestamp", minTimestamp))
|
||||
.orderBy(["ta.total_time asc", "ta.timestamp asc"])
|
||||
.limit(10);
|
||||
|
||||
const rows = await this._txn.fetchRows(loadSql);
|
||||
|
||||
return rows.map(row => ({
|
||||
driverName: row.name,
|
||||
ta: _extractRow(row),
|
||||
}));
|
||||
}
|
||||
|
||||
async loadAll(profileId: Id<Profile>): Promise<TimeAttackScore[]> {
|
||||
const loadSql = sql
|
||||
.select("ta.*")
|
||||
.from("idz_ta_best ta")
|
||||
.where("ta.profile_id", profileId);
|
||||
|
||||
const rows = await this._txn.fetchRows(loadSql);
|
||||
|
||||
return rows.map(_extractRow);
|
||||
}
|
||||
|
||||
async save(profileId: Id<Profile>, score: TimeAttackScore): Promise<void> {
|
||||
const logSql = sql.insert("idz_ta_result", {
|
||||
id: this._txn.generateId(),
|
||||
profile_id: profileId,
|
||||
route_no: score.routeNo,
|
||||
total_time: score.totalTime,
|
||||
section_times: score.sectionTimes.join(","),
|
||||
flags: score.flags,
|
||||
grade: score.grade,
|
||||
car_selector: score.carSelector,
|
||||
timestamp: score.timestamp,
|
||||
});
|
||||
|
||||
await this._txn.modify(logSql);
|
||||
|
||||
const existSql = sql
|
||||
.select("ta.total_time")
|
||||
.from("idz_ta_best ta")
|
||||
.where("ta.profile_id", profileId)
|
||||
.where("ta.route_no", score.routeNo);
|
||||
|
||||
const row = await this._txn.fetchRow(existSql);
|
||||
|
||||
if (row === undefined) {
|
||||
const insertSql = sql.insert("idz_ta_best", {
|
||||
id: this._txn.generateId(),
|
||||
profile_id: profileId,
|
||||
route_no: score.routeNo,
|
||||
total_time: score.totalTime,
|
||||
section_times: score.sectionTimes.join(","),
|
||||
flags: score.flags,
|
||||
grade: score.grade,
|
||||
car_selector: score.carSelector,
|
||||
timestamp: score.timestamp,
|
||||
});
|
||||
|
||||
await this._txn.modify(insertSql);
|
||||
} else {
|
||||
if (score.totalTime < parseFloat(row.total_time)) {
|
||||
const updateSql = sql
|
||||
.update("idz_ta_best", {
|
||||
total_time: score.totalTime,
|
||||
section_times: score.sectionTimes.join(","),
|
||||
flags: score.flags,
|
||||
grade: score.grade,
|
||||
car_selector: score.carSelector,
|
||||
timestamp: score.timestamp,
|
||||
})
|
||||
.where("profile_id", profileId)
|
||||
.where("route_no", score.routeNo);
|
||||
|
||||
await this._txn.modify(updateSql);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import sql from "sql-bricks-postgres";
|
||||
|
||||
import { TitleCode } from "../model/base";
|
||||
import { Profile } from "../model/profile";
|
||||
import { FlagRepository } from "../repo";
|
||||
import { Id } from "../../model";
|
||||
import { Transaction } from "../../sql";
|
||||
|
||||
export class SqlTitlesRepository implements FlagRepository<TitleCode> {
|
||||
constructor(private readonly _txn: Transaction) {}
|
||||
|
||||
async loadAll(profileId: Id<Profile>): Promise<Set<TitleCode>> {
|
||||
const loadSql = sql
|
||||
.select("t.title_no")
|
||||
.from("idz_title_unlock t")
|
||||
.where("t.profile_id", profileId);
|
||||
|
||||
const rows = await this._txn.fetchRows(loadSql);
|
||||
const result = new Set<TitleCode>();
|
||||
|
||||
for (const row of rows) {
|
||||
result.add(parseInt(row.title_no) as TitleCode);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async saveAll(profileId: Id<Profile>, flags: Set<TitleCode>): Promise<void> {
|
||||
const existing = await this.loadAll(profileId);
|
||||
|
||||
for (const flag of flags) {
|
||||
if (existing.has(flag)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const saveSql = sql.insert("idz_title_unlock", {
|
||||
id: this._txn.generateId(),
|
||||
profile_id: profileId,
|
||||
title_no: flag,
|
||||
});
|
||||
|
||||
await this._txn.modify(saveSql);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import sql from "sql-bricks-postgres";
|
||||
|
||||
import { Profile } from "../model/profile";
|
||||
import { Unlocks } from "../model/unlocks";
|
||||
import { FacetRepository } from "../repo";
|
||||
import { Id } from "../../model";
|
||||
import { Transaction } from "../../sql";
|
||||
|
||||
export class SqlUnlocksRepository implements FacetRepository<Unlocks> {
|
||||
constructor(private readonly _txn: Transaction) {}
|
||||
|
||||
async load(profileId: Id<Profile>): Promise<Unlocks> {
|
||||
const loadSql = sql
|
||||
.select("u.*")
|
||||
.from("idz_unlocks u")
|
||||
.where("u.id", profileId);
|
||||
|
||||
const row = await this._txn.fetchRow(loadSql);
|
||||
|
||||
if (row === undefined) {
|
||||
throw new Error(`Unlocks not found, profileId=${profileId}`);
|
||||
}
|
||||
|
||||
return {
|
||||
cup: parseInt(row.cup),
|
||||
gauges: parseInt(row.gauges),
|
||||
music: parseInt(row.music),
|
||||
lastMileageReward: parseInt(row.last_mileage_reward),
|
||||
};
|
||||
}
|
||||
|
||||
async save(profileId: Id<Profile>, unlocks: Unlocks): Promise<void> {
|
||||
const saveSql = sql
|
||||
.insert("idz_unlocks", {
|
||||
id: profileId,
|
||||
cup: unlocks.cup,
|
||||
gauges: unlocks.gauges,
|
||||
music: unlocks.music,
|
||||
last_mileage_reward: unlocks.lastMileageReward,
|
||||
})
|
||||
.onConflict("id")
|
||||
.doUpdate(["cup", "gauges", "music", "last_mileage_reward"]);
|
||||
|
||||
await this._txn.modify(saveSql);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user