idz: Support multiple major versions

Doesn't actually add support for any additional versions, but it
does lay the groundwork.
This commit is contained in:
Tau
2021-02-03 23:43:39 +00:00
committed by Tau
parent 4f7c0145a1
commit 93db3831be
26 changed files with 152 additions and 48 deletions
+7 -2
View File
@@ -7,6 +7,10 @@ create table "idz_profile" (
"player_id" integer not null "player_id" integer not null
references "aime_player"("id") references "aime_player"("id")
on delete cascade, on delete cascade,
-- Major version of Initial D Zero, either 1 or 2.
-- The two major versions are incompatible with each other and do not
-- permit the player to carry progress over from one to the other.
"version" integer not null,
-- TODO shop_id -- TODO shop_id
"name" text not null, "name" text not null,
"lv" integer not null, "lv" integer not null,
@@ -16,7 +20,7 @@ create table "idz_profile" (
"mileage" integer not null, "mileage" integer not null,
"register_time" timestamp not null, "register_time" timestamp not null,
"access_time" timestamp not null, "access_time" timestamp not null,
constraint "idz_profile_player_uq" unique ("player_id") constraint "idz_profile_player_uq" unique ("player_id", "version")
); );
create table "idz_chara" ( create table "idz_chara" (
@@ -202,12 +206,13 @@ create table "idz_unlocks" (
create table "idz_team" ( create table "idz_team" (
"id" integer primary key not null, "id" integer primary key not null,
"version" integer not null,
"ext_id" integer not null, "ext_id" integer not null,
"name" text not null, "name" text not null,
"name_bg" integer not null, "name_bg" integer not null,
"name_fx" integer not null, "name_fx" integer not null,
"register_time" timestamp not null, "register_time" timestamp not null,
constraint "idz_team_uq" unique ("ext_id") constraint "idz_team_uq" unique ("version", "ext_id")
); );
create table "idz_team_auto" ( create table "idz_team_auto" (
@@ -0,0 +1,77 @@
create table "new_idz_profile" (
"id" integer primary key not null,
"player_id" integer not null
references "aime_player"("id")
on delete cascade,
"version" integer not null,
"name" text not null,
"lv" integer not null,
"exp" integer not null,
"fame" integer not null,
"dpoint" integer not null,
"mileage" integer not null,
"register_time" timestamp not null,
"access_time" timestamp not null,
constraint "idz_profile_player_uq" unique ("player_id", "version")
);
create table "new_idz_team" (
"id" integer primary key not null,
"version" integer not null,
"ext_id" integer not null,
"name" text not null,
"name_bg" integer not null,
"name_fx" integer not null,
"register_time" timestamp not null,
constraint "idz_team_uq" unique ("version", "ext_id")
);
insert into "new_idz_profile" (
"id",
"player_id",
"version",
"name",
"lv",
"exp",
"fame",
"dpoint",
"mileage",
"register_time",
"access_time"
) select
x."id",
x."player_id",
1,
x."name",
x."lv",
x."exp",
x."fame",
x."dpoint",
x."mileage",
x."register_time",
x."access_time"
from "idz_profile" as "x";
insert into "new_idz_team" (
"id",
"version",
"ext_id",
"name",
"name_bg",
"name_fx",
"register_time"
) select
x."id",
1,
x."ext_id",
x."name",
x."name_bg",
x."name_fx",
x."register_time"
from "idz_team" as "x";
drop table "idz_profile";
drop table "idz_team";
alter table "new_idz_profile" rename to "idz_profile";
alter table "new_idz_team" rename to "idz_team";
+3 -1
View File
@@ -26,7 +26,9 @@ export async function _fixupPrevTeam(
// (need to look up new leader's db id from aime id. ick) // (need to look up new leader's db id from aime id. ick)
const newLeader = remaining[remaining.length - 1]; const newLeader = remaining[remaining.length - 1];
const newLeaderId = await w.profile().find(newLeader.profile.aimeId); const newLeaderId = await w
.profile()
.find(newLeader.profile.aimeId, newLeader.profile.version);
await w.teamMembers().makeLeader(prevTeamId, newLeaderId); await w.teamMembers().makeLeader(prevTeamId, newLeaderId);
} }
+3 -2
View File
@@ -44,9 +44,9 @@ export async function createAutoTeam(
req: CreateAutoTeamRequest req: CreateAutoTeamRequest
): Promise<CreateAutoTeamResponse> { ): Promise<CreateAutoTeamResponse> {
const now = new Date(); const now = new Date();
const { aimeId } = req; const { aimeId, version } = req;
const peek = await w.teamAuto().peek(); const peek = await w.teamAuto().peek(version);
let nextAuto: TeamAuto; let nextAuto: TeamAuto;
// //
@@ -100,6 +100,7 @@ export async function createAutoTeam(
// Register the new team, make the requestor its leader // Register the new team, make the requestor its leader
const spec = { const spec = {
version,
name, name,
nameBg: autoTeams[nameIdx].nameBg, nameBg: autoTeams[nameIdx].nameBg,
nameFx: 0, nameFx: 0,
+2 -1
View File
@@ -11,11 +11,12 @@ export async function createProfile(
w: Repositories, w: Repositories,
req: CreateProfileRequest req: CreateProfileRequest
): Promise<GenericResponse> { ): Promise<GenericResponse> {
const { aimeId, name } = req; const { aimeId, version, name } = req;
const now = new Date(); const now = new Date();
const profile: Profile = { const profile: Profile = {
aimeId, aimeId,
version,
name, name,
lv: 1, lv: 1,
exp: 0, exp: 0,
+2 -1
View File
@@ -7,7 +7,7 @@ export async function createTeam(
w: Repositories, w: Repositories,
req: CreateTeamRequest req: CreateTeamRequest
): Promise<CreateTeamResponse> { ): Promise<CreateTeamResponse> {
const profileId = await w.profile().find(req.aimeId); const profileId = await w.profile().find(req.aimeId, req.version);
const prevTeamId = await w.teamMembers().findTeam(profileId); const prevTeamId = await w.teamMembers().findTeam(profileId);
const now = new Date(); const now = new Date();
@@ -15,6 +15,7 @@ export async function createTeam(
const teamSpec = { const teamSpec = {
name: req.teamName, name: req.teamName,
version: req.version,
nameBg: req.nameBg, nameBg: req.nameBg,
nameFx: 0, nameFx: 0,
registerTime: now, registerTime: now,
+1 -1
View File
@@ -6,7 +6,7 @@ export async function discoverProfile(
w: Repositories, w: Repositories,
req: DiscoverProfileRequest req: DiscoverProfileRequest
): Promise<DiscoverProfileResponse> { ): Promise<DiscoverProfileResponse> {
const profileId = await w.profile().peek(req.aimeId); const profileId = await w.profile().peek(req.aimeId, req.version);
return { return {
type: "discover_profile_res", type: "discover_profile_res",
+1 -1
View File
@@ -6,7 +6,7 @@ export async function loadGarage(
w: Repositories, w: Repositories,
req: LoadGarageRequest req: LoadGarageRequest
): Promise<LoadGarageResponse> { ): Promise<LoadGarageResponse> {
const profileId = await w.profile().find(req.aimeId); const profileId = await w.profile().find(req.aimeId, req.version);
return { return {
type: "load_garage_res", type: "load_garage_res",
+2 -2
View File
@@ -6,9 +6,9 @@ export async function loadProfile(
w: Repositories, w: Repositories,
req: LoadProfileRequest req: LoadProfileRequest
): Promise<LoadProfileResponse> { ): Promise<LoadProfileResponse> {
const { aimeId } = req; const { aimeId, version } = req;
const profileId = await w.profile().find(aimeId); const profileId = await w.profile().find(aimeId, version);
const teamId = await w.teamMembers().findTeam(profileId); const teamId = await w.teamMembers().findTeam(profileId);
const leaderId = teamId && (await w.teamMembers().findLeader(teamId)); const leaderId = teamId && (await w.teamMembers().findLeader(teamId));
+1 -1
View File
@@ -6,7 +6,7 @@ export async function loadStocker(
w: Repositories, w: Repositories,
req: LoadStockerRequest req: LoadStockerRequest
): Promise<LoadStockerResponse> { ): Promise<LoadStockerResponse> {
const profileId = await w.profile().find(req.aimeId); const profileId = await w.profile().find(req.aimeId, req.version);
const backgrounds = await w.backgrounds().loadAll(profileId); const backgrounds = await w.backgrounds().loadAll(profileId);
return { return {
+16 -17
View File
@@ -4,30 +4,29 @@ import { LoadTeamRequest } from "../request/loadTeam";
import { LoadTeamResponse } from "../response/loadTeam"; import { LoadTeamResponse } from "../response/loadTeam";
import { Repositories } from "../repo"; import { Repositories } from "../repo";
// Even if a profile does not belong to a team, a team must still be loaded
// (and then ignored by the client).
const dummyResp: LoadTeamResponse = {
type: "load_team_res",
team: {
extId: 0 as ExtId<Team>,
name: "",
nameBg: 0,
nameFx: 0,
registerTime: new Date(0),
},
members: [],
};
export async function loadTeam( export async function loadTeam(
w: Repositories, w: Repositories,
req: LoadTeamRequest req: LoadTeamRequest
): Promise<LoadTeamResponse> { ): Promise<LoadTeamResponse> {
if (req.teamExtId === undefined) { if (req.teamExtId === undefined) {
return dummyResp; // Even if a profile does not belong to a team, a team must still be loaded
// (and then ignored by the client).
return {
type: "load_team_res",
team: {
extId: 0 as ExtId<Team>,
version: req.version,
name: "",
nameBg: 0,
nameFx: 0,
registerTime: new Date(0),
},
members: [],
};
} }
const teamId = await w.teams().find(req.teamExtId); const teamId = await w.teams().find(req.teamExtId, req.version);
return { return {
type: "load_team_res", type: "load_team_res",
+1 -1
View File
@@ -6,7 +6,7 @@ export async function saveNewCar(
w: Repositories, w: Repositories,
req: SaveNewCarRequest req: SaveNewCarRequest
): Promise<SaveNewCarResponse> { ): Promise<SaveNewCarResponse> {
const profileId = await w.profile().find(req.aimeId); const profileId = await w.profile().find(req.aimeId, req.version);
await w.car().saveCar(profileId, req.car); await w.car().saveCar(profileId, req.car);
+1 -1
View File
@@ -7,7 +7,7 @@ export async function saveProfile(
req: SaveProfileRequest req: SaveProfileRequest
): Promise<GenericResponse> { ): Promise<GenericResponse> {
const now = new Date(); const now = new Date();
const profileId = await w.profile().find(req.aimeId); const profileId = await w.profile().find(req.aimeId, req.version);
const profile = await w.profile().load(profileId); const profile = await w.profile().load(profileId);
const chara = await w.chara().load(profileId); const chara = await w.chara().load(profileId);
+1 -1
View File
@@ -6,7 +6,7 @@ export async function saveSettings(
w: Repositories, w: Repositories,
req: SaveSettingsRequest req: SaveSettingsRequest
): Promise<GenericResponse> { ): Promise<GenericResponse> {
const profileId = await w.profile().find(req.aimeId); const profileId = await w.profile().find(req.aimeId, req.version);
await w.settings().save(profileId, req.settings); await w.settings().save(profileId, req.settings);
+1 -1
View File
@@ -6,7 +6,7 @@ export async function saveStocker(
w: Repositories, w: Repositories,
req: SaveStockerRequest req: SaveStockerRequest
): Promise<GenericResponse> { ): Promise<GenericResponse> {
const profileId = await w.profile().find(req.aimeId); const profileId = await w.profile().find(req.aimeId, req.version);
await Promise.all([ await Promise.all([
w.backgrounds().saveAll(profileId, req.backgrounds), w.backgrounds().saveAll(profileId, req.backgrounds),
+1 -1
View File
@@ -6,7 +6,7 @@ export async function saveTeamBanner(
w: Repositories, w: Repositories,
req: SaveTeamBannerRequest req: SaveTeamBannerRequest
): Promise<GenericResponse> { ): Promise<GenericResponse> {
const teamId = await w.teams().find(req.teamExtId); const teamId = await w.teams().find(req.teamExtId, req.version);
const orig = await w.teams().load(teamId); const orig = await w.teams().load(teamId);
await w.teams().save(teamId, { await w.teams().save(teamId, {
+1 -1
View File
@@ -14,7 +14,7 @@ export async function saveTimeAttack(
// avoidance time warping stuff // avoidance time warping stuff
const now = new Date(); const now = new Date();
const profileId = await w.profile().find(req.aimeId); const profileId = await w.profile().find(req.aimeId, req.version);
await w.timeAttack().save(profileId, { ...req.payload, timestamp: now }); await w.timeAttack().save(profileId, { ...req.payload, timestamp: now });
} }
+2 -2
View File
@@ -6,8 +6,8 @@ export async function updateTeamLeader(
w: Repositories, w: Repositories,
req: UpdateTeamLeaderRequest req: UpdateTeamLeaderRequest
): Promise<UpdateTeamLeaderResponse> { ): Promise<UpdateTeamLeaderResponse> {
const profileId = await w.profile().find(req.aimeId); const profileId = await w.profile().find(req.aimeId, req.version);
const teamId = await w.teams().find(req.teamExtId); const teamId = await w.teams().find(req.teamExtId, req.version);
await w.teamMembers().makeLeader(teamId, profileId); await w.teamMembers().makeLeader(teamId, profileId);
+2 -2
View File
@@ -8,8 +8,8 @@ export async function updateTeamMember(
req: UpdateTeamMemberRequest req: UpdateTeamMemberRequest
): Promise<UpdateTeamMemberResponse> { ): Promise<UpdateTeamMemberResponse> {
const now = new Date(); const now = new Date();
const profileId = await w.profile().find(req.aimeId); const profileId = await w.profile().find(req.aimeId, req.version);
const teamId = await w.teams().find(req.teamExtId); const teamId = await w.teams().find(req.teamExtId, req.version);
switch (req.action) { switch (req.action) {
case "add": case "add":
+1
View File
@@ -2,6 +2,7 @@ import { AimeId } from "../../../model";
export interface Profile { export interface Profile {
aimeId: AimeId; aimeId: AimeId;
version: number;
name: string; name: string;
lv: number; lv: number;
exp: number; exp: number;
+1
View File
@@ -4,6 +4,7 @@ import { Profile } from "./profile";
export interface Team { export interface Team {
extId: ExtId<Team>; extId: ExtId<Team>;
version: number;
name: string; name: string;
nameBg: number; nameBg: number;
nameFx: number; nameFx: number;
+10 -4
View File
@@ -49,9 +49,12 @@ export interface FlagRepository<T extends number> {
} }
export interface ProfileRepository { export interface ProfileRepository {
find(aimeId: AimeId): Promise<Id<Model.Profile>>; find(aimeId: AimeId, version: number): Promise<Id<Model.Profile>>;
peek(aimeId: AimeId): Promise<Id<Model.Profile> | undefined>; peek(
aimeId: AimeId,
version: number
): Promise<Id<Model.Profile> | undefined>;
load(id: Id<Model.Profile>): Promise<Model.Profile>; load(id: Id<Model.Profile>): Promise<Model.Profile>;
@@ -61,7 +64,10 @@ export interface ProfileRepository {
} }
export interface TeamRepository { export interface TeamRepository {
find(extId: Model.ExtId<Model.Team>): Promise<Id<Model.Team>>; find(
extId: Model.ExtId<Model.Team>,
version: number
): Promise<Id<Model.Team>>;
load(id: Id<Model.Team>): Promise<Model.Team>; load(id: Id<Model.Team>): Promise<Model.Team>;
@@ -73,7 +79,7 @@ export interface TeamRepository {
} }
export interface TeamAutoRepository { export interface TeamAutoRepository {
peek(): Promise<[Model.TeamAuto, Id<Model.Team>] | undefined>; peek(version: number): Promise<[Model.TeamAuto, Id<Model.Team>] | undefined>;
push(teamId: Id<Model.Team>, auto: Model.TeamAuto): Promise<void>; push(teamId: Id<Model.Team>, auto: Model.TeamAuto): Promise<void>;
} }
+9 -4
View File
@@ -8,6 +8,7 @@ import { Row, Transaction } from "../../../sql";
export function _extractProfile(row: Row): Profile { export function _extractProfile(row: Row): Profile {
return { return {
aimeId: parseInt(row.aime_id!) as AimeId, aimeId: parseInt(row.aime_id!) as AimeId,
version: parseInt(row.version!),
name: row.name!, name: row.name!,
lv: parseInt(row.lv!), lv: parseInt(row.lv!),
exp: parseInt(row.exp!), exp: parseInt(row.exp!),
@@ -22,8 +23,8 @@ export function _extractProfile(row: Row): Profile {
export class SqlProfileRepository implements ProfileRepository { export class SqlProfileRepository implements ProfileRepository {
constructor(private readonly _txn: Transaction) {} constructor(private readonly _txn: Transaction) {}
async find(aimeId: AimeId): Promise<Id<Profile>> { async find(aimeId: AimeId, version: number): Promise<Id<Profile>> {
const profileId = await this.peek(aimeId); const profileId = await this.peek(aimeId, version);
if (profileId === undefined) { if (profileId === undefined) {
throw new Error(`Profile not found for Aime ID ${aimeId}`); throw new Error(`Profile not found for Aime ID ${aimeId}`);
@@ -32,12 +33,16 @@ export class SqlProfileRepository implements ProfileRepository {
return profileId; return profileId;
} }
async peek(aimeId: AimeId): Promise<Id<Profile> | undefined> { async peek(
aimeId: AimeId,
version: number
): Promise<Id<Profile> | undefined> {
const lookupSql = sql const lookupSql = sql
.select("p.id") .select("p.id")
.from("idz_profile p") .from("idz_profile p")
.join("aime_player r", { "p.player_id": "r.id" }) .join("aime_player r", { "p.player_id": "r.id" })
.where("r.ext_id", aimeId); .where("r.ext_id", aimeId)
.and("r.version", version);
const row = await this._txn.fetchRow(lookupSql); const row = await this._txn.fetchRow(lookupSql);
+1
View File
@@ -37,6 +37,7 @@ export class SqlTeamRepository implements TeamRepository {
} }
return { return {
version: parseInt(row.version!),
extId: parseInt(row.ext_id!) as ExtId<Team>, extId: parseInt(row.ext_id!) as ExtId<Team>,
name: row.name!, name: row.name!,
nameBg: parseInt(row.name_bg!), nameBg: parseInt(row.name_bg!),
+3 -1
View File
@@ -8,10 +8,12 @@ import { Transaction } from "../../../sql";
export class SqlTeamAutoRepository implements TeamAutoRepository { export class SqlTeamAutoRepository implements TeamAutoRepository {
constructor(private readonly _txn: Transaction) {} constructor(private readonly _txn: Transaction) {}
async peek(): Promise<[TeamAuto, Id<Team>] | undefined> { async peek(version: number): Promise<[TeamAuto, Id<Team>] | undefined> {
const peekSql = sql const peekSql = sql
.select("tt.*") .select("tt.*")
.from("idz_team_auto tt") .from("idz_team_auto tt")
.join("idz_team t", { "tt.id": "t.id" })
.where("t.version", version)
.orderBy("serial_no desc", "name_idx desc") .orderBy("serial_no desc", "name_idx desc")
.limit(1); .limit(1);
+2
View File
@@ -37,6 +37,7 @@ export class SqlTimeAttackRepository implements TimeAttackRepository {
// Profile // Profile
"p.name as profile_name", "p.name as profile_name",
// Team // Team
"t.version as team_version",
"t.ext_id as team_ext_id", "t.ext_id as team_ext_id",
"t.name as team_name", "t.name as team_name",
"t.name_bg as team_name_bg", "t.name_bg as team_name_bg",
@@ -64,6 +65,7 @@ export class SqlTimeAttackRepository implements TimeAttackRepository {
driverName: row.profile_name!, driverName: row.profile_name!,
team: { team: {
extId: parseInt(row.team_ext_id!) as ExtId<Team>, extId: parseInt(row.team_ext_id!) as ExtId<Team>,
version: parseInt(row.team_version!),
name: row.team_name!, name: row.team_name!,
nameBg: parseInt(row.team_name_bg!), nameBg: parseInt(row.team_name_bg!),
nameFx: parseInt(row.team_name_fx!), nameFx: parseInt(row.team_name_fx!),