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
2020-09-27 00:10:56 -04:00
committed by Tau
parent 4f7c0145a1
commit 93db3831be
26 changed files with 152 additions and 48 deletions

View File

@@ -7,6 +7,10 @@ create table "idz_profile" (
"player_id" integer not null
references "aime_player"("id")
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
"name" text not null,
"lv" integer not null,
@@ -16,7 +20,7 @@ create table "idz_profile" (
"mileage" integer not null,
"register_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" (
@@ -202,12 +206,13 @@ create table "idz_unlocks" (
create table "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 ("ext_id")
constraint "idz_team_uq" unique ("version", "ext_id")
);
create table "idz_team_auto" (

View File

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

View File

@@ -26,7 +26,9 @@ export async function _fixupPrevTeam(
// (need to look up new leader's db id from aime id. ick)
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);
}

View File

@@ -44,9 +44,9 @@ export async function createAutoTeam(
req: CreateAutoTeamRequest
): Promise<CreateAutoTeamResponse> {
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;
//
@@ -100,6 +100,7 @@ export async function createAutoTeam(
// Register the new team, make the requestor its leader
const spec = {
version,
name,
nameBg: autoTeams[nameIdx].nameBg,
nameFx: 0,

View File

@@ -11,11 +11,12 @@ export async function createProfile(
w: Repositories,
req: CreateProfileRequest
): Promise<GenericResponse> {
const { aimeId, name } = req;
const { aimeId, version, name } = req;
const now = new Date();
const profile: Profile = {
aimeId,
version,
name,
lv: 1,
exp: 0,

View File

@@ -7,7 +7,7 @@ export async function createTeam(
w: Repositories,
req: CreateTeamRequest
): 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 now = new Date();
@@ -15,6 +15,7 @@ export async function createTeam(
const teamSpec = {
name: req.teamName,
version: req.version,
nameBg: req.nameBg,
nameFx: 0,
registerTime: now,

View File

@@ -6,7 +6,7 @@ export async function discoverProfile(
w: Repositories,
req: DiscoverProfileRequest
): Promise<DiscoverProfileResponse> {
const profileId = await w.profile().peek(req.aimeId);
const profileId = await w.profile().peek(req.aimeId, req.version);
return {
type: "discover_profile_res",

View File

@@ -6,7 +6,7 @@ export async function loadGarage(
w: Repositories,
req: LoadGarageRequest
): Promise<LoadGarageResponse> {
const profileId = await w.profile().find(req.aimeId);
const profileId = await w.profile().find(req.aimeId, req.version);
return {
type: "load_garage_res",

View File

@@ -6,9 +6,9 @@ export async function loadProfile(
w: Repositories,
req: LoadProfileRequest
): 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 leaderId = teamId && (await w.teamMembers().findLeader(teamId));

View File

@@ -6,7 +6,7 @@ export async function loadStocker(
w: Repositories,
req: LoadStockerRequest
): 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);
return {

View File

@@ -4,30 +4,29 @@ import { LoadTeamRequest } from "../request/loadTeam";
import { LoadTeamResponse } from "../response/loadTeam";
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(
w: Repositories,
req: LoadTeamRequest
): Promise<LoadTeamResponse> {
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 {
type: "load_team_res",

View File

@@ -6,7 +6,7 @@ export async function saveNewCar(
w: Repositories,
req: SaveNewCarRequest
): 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);

View File

@@ -7,7 +7,7 @@ export async function saveProfile(
req: SaveProfileRequest
): Promise<GenericResponse> {
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 chara = await w.chara().load(profileId);

View File

@@ -6,7 +6,7 @@ export async function saveSettings(
w: Repositories,
req: SaveSettingsRequest
): 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);

View File

@@ -6,7 +6,7 @@ export async function saveStocker(
w: Repositories,
req: SaveStockerRequest
): Promise<GenericResponse> {
const profileId = await w.profile().find(req.aimeId);
const profileId = await w.profile().find(req.aimeId, req.version);
await Promise.all([
w.backgrounds().saveAll(profileId, req.backgrounds),

View File

@@ -6,7 +6,7 @@ export async function saveTeamBanner(
w: Repositories,
req: SaveTeamBannerRequest
): 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);
await w.teams().save(teamId, {

View File

@@ -14,7 +14,7 @@ export async function saveTimeAttack(
// avoidance time warping stuff
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 });
}

View File

@@ -6,8 +6,8 @@ export async function updateTeamLeader(
w: Repositories,
req: UpdateTeamLeaderRequest
): Promise<UpdateTeamLeaderResponse> {
const profileId = await w.profile().find(req.aimeId);
const teamId = await w.teams().find(req.teamExtId);
const profileId = await w.profile().find(req.aimeId, req.version);
const teamId = await w.teams().find(req.teamExtId, req.version);
await w.teamMembers().makeLeader(teamId, profileId);

View File

@@ -8,8 +8,8 @@ export async function updateTeamMember(
req: UpdateTeamMemberRequest
): Promise<UpdateTeamMemberResponse> {
const now = new Date();
const profileId = await w.profile().find(req.aimeId);
const teamId = await w.teams().find(req.teamExtId);
const profileId = await w.profile().find(req.aimeId, req.version);
const teamId = await w.teams().find(req.teamExtId, req.version);
switch (req.action) {
case "add":

View File

@@ -2,6 +2,7 @@ import { AimeId } from "../../../model";
export interface Profile {
aimeId: AimeId;
version: number;
name: string;
lv: number;
exp: number;

View File

@@ -4,6 +4,7 @@ import { Profile } from "./profile";
export interface Team {
extId: ExtId<Team>;
version: number;
name: string;
nameBg: number;
nameFx: number;

View File

@@ -49,9 +49,12 @@ export interface FlagRepository<T extends number> {
}
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>;
@@ -61,7 +64,10 @@ export interface ProfileRepository {
}
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>;
@@ -73,7 +79,7 @@ export interface TeamRepository {
}
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>;
}

View File

@@ -8,6 +8,7 @@ import { Row, Transaction } from "../../../sql";
export function _extractProfile(row: Row): Profile {
return {
aimeId: parseInt(row.aime_id!) as AimeId,
version: parseInt(row.version!),
name: row.name!,
lv: parseInt(row.lv!),
exp: parseInt(row.exp!),
@@ -22,8 +23,8 @@ export function _extractProfile(row: Row): Profile {
export class SqlProfileRepository implements ProfileRepository {
constructor(private readonly _txn: Transaction) {}
async find(aimeId: AimeId): Promise<Id<Profile>> {
const profileId = await this.peek(aimeId);
async find(aimeId: AimeId, version: number): Promise<Id<Profile>> {
const profileId = await this.peek(aimeId, version);
if (profileId === undefined) {
throw new Error(`Profile not found for Aime ID ${aimeId}`);
@@ -32,12 +33,16 @@ export class SqlProfileRepository implements ProfileRepository {
return profileId;
}
async peek(aimeId: AimeId): Promise<Id<Profile> | undefined> {
async peek(
aimeId: AimeId,
version: number
): 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);
.where("r.ext_id", aimeId)
.and("r.version", version);
const row = await this._txn.fetchRow(lookupSql);

View File

@@ -37,6 +37,7 @@ export class SqlTeamRepository implements TeamRepository {
}
return {
version: parseInt(row.version!),
extId: parseInt(row.ext_id!) as ExtId<Team>,
name: row.name!,
nameBg: parseInt(row.name_bg!),

View File

@@ -8,10 +8,12 @@ import { Transaction } from "../../../sql";
export class SqlTeamAutoRepository implements TeamAutoRepository {
constructor(private readonly _txn: Transaction) {}
async peek(): Promise<[TeamAuto, Id<Team>] | undefined> {
async peek(version: number): Promise<[TeamAuto, Id<Team>] | undefined> {
const peekSql = sql
.select("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")
.limit(1);

View File

@@ -37,6 +37,7 @@ export class SqlTimeAttackRepository implements TimeAttackRepository {
// Profile
"p.name as profile_name",
// Team
"t.version as team_version",
"t.ext_id as team_ext_id",
"t.name as team_name",
"t.name_bg as team_name_bg",
@@ -64,6 +65,7 @@ export class SqlTimeAttackRepository implements TimeAttackRepository {
driverName: row.profile_name!,
team: {
extId: parseInt(row.team_ext_id!) as ExtId<Team>,
version: parseInt(row.team_version!),
name: row.team_name!,
nameBg: parseInt(row.team_name_bg!),
nameFx: parseInt(row.team_name_fx!),