mirror of
https://github.com/djhackersdev/minime.git
synced 2026-04-25 16:20:19 -05:00
Force explicit conversion of rows fetched from SQL DB
Everything is a scalar value now, and while this does involve more boilerplate it will also catch type conversion issues like "1" + 0 === "10" in the future.
This commit is contained in:
parent
30d42bd832
commit
f9970fa81c
|
|
@ -30,7 +30,7 @@ class CardRepositoryImpl implements CardRepository {
|
|||
|
||||
await this._txn.modify(touchSql);
|
||||
|
||||
return extId;
|
||||
return parseInt(extId, 10) as AimeId;
|
||||
}
|
||||
|
||||
async register(luid: string, now: Date): Promise<AimeId> {
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ export class SqlBackgroundsRepository
|
|||
const result = new Set<BackgroundCode>();
|
||||
|
||||
for (const row of rows) {
|
||||
result.add(row.background_no);
|
||||
result.add(parseInt(row.background_no) as BackgroundCode);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
|
|
|||
|
|
@ -3,25 +3,25 @@ import sql from "sql-bricks-postgres";
|
|||
import { Car, CarSelector } from "../model/car";
|
||||
import { Profile } from "../model/profile";
|
||||
import { CarRepository } from "../repo";
|
||||
import { Id, Transaction, generateId } from "../../sql";
|
||||
import { Id, Row, Transaction, generateId } from "../../sql";
|
||||
|
||||
function _extractRow(row: any): Car {
|
||||
function _extractRow(row: Row): Car {
|
||||
return {
|
||||
selector: row.selector,
|
||||
field_00: row.field_00,
|
||||
field_02: row.field_02,
|
||||
field_04: row.field_04.split(",").map((x: string) => parseInt(x, 10)),
|
||||
field_46: row.field_46,
|
||||
field_48: row.field_48,
|
||||
field_4a: row.field_4a,
|
||||
field_4c: row.field_4c,
|
||||
field_50_lo: row.field_50_lo,
|
||||
field_50_hi: row.field_50_hi,
|
||||
field_58: row.field_58,
|
||||
field_5a: row.field_5a,
|
||||
field_5b: row.field_5b,
|
||||
field_5c: row.field_5c,
|
||||
field_5e: row.field_5e,
|
||||
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),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -36,7 +36,7 @@ export class SqlCarRepository implements CarRepository {
|
|||
|
||||
const row = await this._txn.fetchRow(countSql);
|
||||
|
||||
return parseInt(row!.result, 10);
|
||||
return parseInt(row!.result);
|
||||
}
|
||||
|
||||
async loadAllCars(profileId: Id<Profile>): Promise<Car[]> {
|
||||
|
|
@ -59,6 +59,10 @@ export class SqlCarRepository implements CarRepository {
|
|||
|
||||
const row = await this._txn.fetchRow(loadSql);
|
||||
|
||||
if (row === undefined) {
|
||||
throw new Error(`Car selection not found, profileId=${profileId}`);
|
||||
}
|
||||
|
||||
return _extractRow(row);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,22 +1,23 @@
|
|||
import sql from "sql-bricks-postgres";
|
||||
|
||||
import { Chara } from "../model/chara";
|
||||
import { BackgroundCode, TitleCode } from "../model/base";
|
||||
import { Chara, Gender } from "../model/chara";
|
||||
import { Profile } from "../model/profile";
|
||||
import { FacetRepository } from "../repo";
|
||||
import { Id, Transaction } from "../../sql";
|
||||
import { Id, Row, Transaction } from "../../sql";
|
||||
|
||||
export function _extractChara(row: any): Chara {
|
||||
export function _extractChara(row: Row): Chara {
|
||||
return {
|
||||
gender: row.gender,
|
||||
field_02: row.field_02,
|
||||
field_04: row.field_04,
|
||||
field_06: row.field_06,
|
||||
field_08: row.field_08,
|
||||
field_0a: row.field_0a,
|
||||
field_0c: row.field_0c,
|
||||
field_0e: row.field_0e,
|
||||
title: row.title,
|
||||
background: row.background,
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -31,6 +32,10 @@ export class SqlCharaRepository implements FacetRepository<Chara> {
|
|||
|
||||
const row = await this._txn.fetchRow(loadSql);
|
||||
|
||||
if (row === undefined) {
|
||||
throw new Error(`Chara not found: profileId=${profileId}`);
|
||||
}
|
||||
|
||||
return _extractChara(row);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,10 +15,13 @@ export class SqlCoursePlaysRepository implements CoursePlaysRepository {
|
|||
.where("cp.profile_id", profileId);
|
||||
|
||||
const rows = await this._txn.fetchRows(loadSql);
|
||||
const result = new Map();
|
||||
const result = new Map<CourseNo, number>();
|
||||
|
||||
for (const row of rows) {
|
||||
result.set(row.course_no, row.count);
|
||||
const courseNo = parseInt(row.course_no) as CourseNo;
|
||||
const count = parseInt(row.count);
|
||||
|
||||
result.set(courseNo, count);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
|
|
|||
|
|
@ -35,7 +35,11 @@ export class SqlMissionsRepository implements FacetRepository<MissionState> {
|
|||
const rows = await this._txn.fetchRows(loadSoloSql);
|
||||
|
||||
for (const row of rows) {
|
||||
result.solo[row.grid_no].cells[row.cell_no] = row.value;
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -3,19 +3,19 @@ import sql from "sql-bricks-postgres";
|
|||
import { Profile } from "../model/profile";
|
||||
import { ProfileRepository } from "../repo";
|
||||
import { AimeId } from "../../model";
|
||||
import { Id, Transaction, generateId } from "../../sql";
|
||||
import { Id, Row, Transaction, generateId } from "../../sql";
|
||||
|
||||
export function _extractProfile(row: any): Profile {
|
||||
export function _extractProfile(row: Row): Profile {
|
||||
return {
|
||||
aimeId: row.aime_id,
|
||||
aimeId: parseInt(row.aime_id) as AimeId,
|
||||
name: row.name,
|
||||
lv: row.lv,
|
||||
exp: row.exp,
|
||||
fame: row.fame,
|
||||
dpoint: row.dpoint,
|
||||
mileage: row.mileage,
|
||||
accessTime: row.access_time,
|
||||
registerTime: row.register_time,
|
||||
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),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -45,7 +45,7 @@ export class SqlProfileRepository implements ProfileRepository {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
return row.id;
|
||||
return BigInt(row.id) as Id<Profile>;
|
||||
}
|
||||
|
||||
async load(id: Id<Profile>): Promise<Profile> {
|
||||
|
|
@ -57,6 +57,10 @@ export class SqlProfileRepository implements ProfileRepository {
|
|||
|
||||
const row = await this._txn.fetchRow(loadSql);
|
||||
|
||||
if (row === undefined) {
|
||||
throw new Error(`Profile not found, id=${id}`);
|
||||
}
|
||||
|
||||
return _extractProfile(row);
|
||||
}
|
||||
|
||||
|
|
@ -68,7 +72,7 @@ export class SqlProfileRepository implements ProfileRepository {
|
|||
fame: profile.fame,
|
||||
dpoint: profile.dpoint,
|
||||
mileage: profile.mileage,
|
||||
access_time: profile.accessTime,
|
||||
access_time: profile.accessTime.toISOString(),
|
||||
})
|
||||
.where("id", id);
|
||||
|
||||
|
|
@ -99,8 +103,8 @@ export class SqlProfileRepository implements ProfileRepository {
|
|||
fame: profile.fame,
|
||||
dpoint: profile.dpoint,
|
||||
mileage: profile.mileage,
|
||||
register_time: profile.registerTime,
|
||||
access_time: profile.accessTime,
|
||||
register_time: profile.registerTime.toISOString(),
|
||||
access_time: profile.accessTime.toISOString(),
|
||||
});
|
||||
|
||||
await this._txn.modify(createSql);
|
||||
|
|
|
|||
|
|
@ -21,10 +21,10 @@ export class SqlSettingsRepository implements FacetRepository<Settings> {
|
|||
}
|
||||
|
||||
return {
|
||||
music: row.music,
|
||||
pack: parseInt(row.pack, 10),
|
||||
paperCup: row.paper_cup,
|
||||
gauges: row.gauges,
|
||||
music: parseInt(row.music),
|
||||
pack: parseInt(row.pack),
|
||||
paperCup: parseInt(row.paper_cup),
|
||||
gauges: parseInt(row.gauges),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ export class SqlStoryRepository implements FacetRepository<Story> {
|
|||
// Must succeed even if nonexistent (required by save method below)
|
||||
|
||||
const result = {
|
||||
x: header !== undefined ? header.x : 0,
|
||||
y: header !== undefined ? header.y : 0,
|
||||
x: header !== undefined ? parseInt(header.x) : 0,
|
||||
y: header !== undefined ? parseInt(header.y) : 0,
|
||||
rows: new Array<StoryRow>(),
|
||||
};
|
||||
|
||||
|
|
@ -42,10 +42,12 @@ export class SqlStoryRepository implements FacetRepository<Story> {
|
|||
const rows = await this._txn.fetchRows(loadCellSql);
|
||||
|
||||
for (const row of rows) {
|
||||
const cell = result.rows[row.row_no].cells[row.col_no];
|
||||
const rowNo = parseInt(row.row_no);
|
||||
const colNo = parseInt(row.col_no);
|
||||
const cell = result.rows[rowNo].cells[colNo];
|
||||
|
||||
cell.a = row.a;
|
||||
cell.b = row.b;
|
||||
cell.a = parseInt(row.a);
|
||||
cell.b = parseInt(row.b);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ export class SqlTeamRepository implements TeamRepository {
|
|||
throw new Error(`Team not found for ExtID ${extId}`);
|
||||
}
|
||||
|
||||
return row.id;
|
||||
return BigInt(row.id) as Id<Team>;
|
||||
}
|
||||
|
||||
async load(id: Id<Team>): Promise<Team> {
|
||||
|
|
@ -37,10 +37,10 @@ export class SqlTeamRepository implements TeamRepository {
|
|||
}
|
||||
|
||||
return {
|
||||
extId: row.ext_id,
|
||||
extId: parseInt(row.ext_id) as ExtId<Team>,
|
||||
name: row.name,
|
||||
nameBg: row.name_bg,
|
||||
nameFx: row.name_fx,
|
||||
nameBg: parseInt(row.name_bg),
|
||||
nameFx: parseInt(row.name_fx),
|
||||
registerTime: new Date(row.register_time),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,10 +19,10 @@ export class SqlTeamAutoRepository implements TeamAutoRepository {
|
|||
return (
|
||||
row && [
|
||||
{
|
||||
serialNo: row.serial_no,
|
||||
nameIdx: row.name_idx,
|
||||
serialNo: parseInt(row.serial_no),
|
||||
nameIdx: parseInt(row.name_idx),
|
||||
},
|
||||
row.id,
|
||||
BigInt(row.id) as Id<Team>,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ export class SqlTeamMemberRepository implements TeamMemberRepository {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
return row.team_id;
|
||||
return BigInt(row.team_id) as Id<Team>;
|
||||
}
|
||||
|
||||
async findLeader(teamId: Id<Team>): Promise<Id<Profile> | undefined> {
|
||||
|
|
@ -38,7 +38,7 @@ export class SqlTeamMemberRepository implements TeamMemberRepository {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
return row.id;
|
||||
return BigInt(row.id) as Id<Profile>;
|
||||
}
|
||||
|
||||
async loadRoster(teamId: Id<Team>): Promise<TeamMember[]> {
|
||||
|
|
@ -52,10 +52,10 @@ export class SqlTeamMemberRepository implements TeamMemberRepository {
|
|||
|
||||
const rows = await this._txn.fetchRows(loadSql);
|
||||
|
||||
return rows.map((row: any) => ({
|
||||
return rows.map(row => ({
|
||||
profile: _extractProfile(row),
|
||||
chara: _extractChara(row),
|
||||
leader: row.leader,
|
||||
leader: !!row.leader,
|
||||
joinTime: new Date(row.join_time),
|
||||
}));
|
||||
}
|
||||
|
|
@ -91,7 +91,7 @@ export class SqlTeamMemberRepository implements TeamMemberRepository {
|
|||
|
||||
const row = await this._txn.fetchRow(countSql);
|
||||
|
||||
if (row!.count >= 6) {
|
||||
if (parseInt(row!.count) >= 6) {
|
||||
throw new Error(`Team ${teamId} is full`);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,16 +22,13 @@ export class SqlTeamReservationRepository
|
|||
async occupancyHack(teamId: Id<Team>): Promise<number> {
|
||||
await this._lockTeam(teamId);
|
||||
|
||||
// counts get returned as strings, so 1 + 0 = 10.
|
||||
// it hardly needs to be said but fuck javascript.
|
||||
|
||||
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, 10);
|
||||
const memberCount = parseInt(memberRes!.count);
|
||||
|
||||
const reservSql = sql
|
||||
.select("count(*) as count")
|
||||
|
|
@ -39,7 +36,7 @@ export class SqlTeamReservationRepository
|
|||
.where("tr.team_id", teamId);
|
||||
|
||||
const reservRes = await this._txn.fetchRow(reservSql);
|
||||
const reservCount = parseInt(reservRes!.count, 10);
|
||||
const reservCount = parseInt(reservRes!.count);
|
||||
|
||||
return memberCount + reservCount;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,23 @@
|
|||
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, Transaction, generateId } from "../../sql";
|
||||
import { Id, Row, Transaction, generateId } 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) {}
|
||||
|
|
@ -26,15 +39,7 @@ export class SqlTimeAttackRepository implements TimeAttackRepository {
|
|||
|
||||
return rows.map(row => ({
|
||||
driverName: row.name,
|
||||
ta: {
|
||||
routeNo: row.route_no,
|
||||
timestamp: new Date(row.timestamp),
|
||||
flags: row.flags,
|
||||
totalTime: row.total_time,
|
||||
sectionTimes: row.section_times.split(","),
|
||||
grade: row.grade,
|
||||
carSelector: row.car_selector,
|
||||
},
|
||||
ta: _extractRow(row),
|
||||
}));
|
||||
}
|
||||
|
||||
|
|
@ -46,15 +51,7 @@ export class SqlTimeAttackRepository implements TimeAttackRepository {
|
|||
|
||||
const rows = await this._txn.fetchRows(loadSql);
|
||||
|
||||
return rows.map(row => ({
|
||||
routeNo: row.route_no,
|
||||
timestamp: new Date(row.timestamp),
|
||||
flags: row.flags,
|
||||
totalTime: row.total_time,
|
||||
sectionTimes: row.section_times.split(",").map(parseFloat),
|
||||
grade: row.grade,
|
||||
carSelector: row.car_selector,
|
||||
}));
|
||||
return rows.map(_extractRow);
|
||||
}
|
||||
|
||||
async save(profileId: Id<Profile>, score: TimeAttackScore): Promise<void> {
|
||||
|
|
@ -94,20 +91,22 @@ export class SqlTimeAttackRepository implements TimeAttackRepository {
|
|||
});
|
||||
|
||||
await this._txn.modify(insertSql);
|
||||
} else if (score.totalTime < 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);
|
||||
} 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);
|
||||
await this._txn.modify(updateSql);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ export class SqlTitlesRepository implements FlagRepository<TitleCode> {
|
|||
const result = new Set<TitleCode>();
|
||||
|
||||
for (const row of rows) {
|
||||
result.add(row.title_no);
|
||||
result.add(parseInt(row.title_no) as TitleCode);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
|
|
|||
|
|
@ -21,10 +21,10 @@ export class SqlUnlocksRepository implements FacetRepository<Unlocks> {
|
|||
}
|
||||
|
||||
return {
|
||||
cup: row.cup,
|
||||
gauges: row.gauges,
|
||||
music: row.music,
|
||||
lastMileageReward: row.last_mileage_reward,
|
||||
cup: parseInt(row.cup),
|
||||
gauges: parseInt(row.gauges),
|
||||
music: parseInt(row.music),
|
||||
lastMileageReward: parseInt(row.last_mileage_reward),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import { BackgroundCode, TitleCode } from "./base";
|
||||
|
||||
export type Gender = "male" | "female";
|
||||
|
||||
export interface Chara {
|
||||
gender: "male" | "female";
|
||||
gender: Gender;
|
||||
field_02: number;
|
||||
field_04: number;
|
||||
field_06: number;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import * as sql from "sql-bricks-postgres";
|
|||
export type Id<T> = bigint & { __id: T };
|
||||
|
||||
export interface Row {
|
||||
[key: string]: any;
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
export interface Transaction {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user