mirror of
https://github.com/djhackersdev/minime.git
synced 2026-07-16 08:01:09 -05:00
Add an SQL DB abstraction layer
Because what self-respecting Enterprise(R) project doesn't have one of those?
This commit is contained in:
parent
2ccd9dc382
commit
3e6b4f4798
|
|
@ -1,37 +1,34 @@
|
|||
import { PoolClient } from "pg";
|
||||
import sql from "sql-bricks";
|
||||
import sql from "sql-bricks-postgres";
|
||||
|
||||
import { CardRepository, Transaction } from "./repo";
|
||||
import { connect, generateId, generateExtId } from "../db";
|
||||
import { AimeId } from "../model";
|
||||
import { CardRepository, Repositories } from "./repo";
|
||||
import { AimeId, generateExtId } from "../model";
|
||||
import { Transaction, generateId } from "../sql";
|
||||
|
||||
class CardRepositoryImpl implements CardRepository {
|
||||
constructor(private readonly _conn: PoolClient) {}
|
||||
constructor(private readonly _txn: Transaction) {}
|
||||
|
||||
async lookup(luid: string, now: Date): Promise<AimeId | undefined> {
|
||||
const fetchSql = sql
|
||||
.select("c.id", "p.ext_id")
|
||||
.from("aime_card c")
|
||||
.join("aime_player p", { "c.player_id": "p.id" })
|
||||
.where("c.nfc_id", luid)
|
||||
.toParams();
|
||||
.where("c.nfc_id", luid);
|
||||
|
||||
const { rows } = await this._conn.query(fetchSql);
|
||||
const row = await this._txn.fetchRow(fetchSql);
|
||||
|
||||
if (rows.length === 0) {
|
||||
if (row === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const id = rows[0].id;
|
||||
const extId = rows[0].ext_id;
|
||||
const id = row.id;
|
||||
const extId = row.ext_id;
|
||||
|
||||
const touchSql = sql
|
||||
.update("aime_card")
|
||||
.set({ access_time: now })
|
||||
.where("id", id)
|
||||
.toParams();
|
||||
.where("id", id);
|
||||
|
||||
await this._conn.query(touchSql);
|
||||
await this._txn.modify(touchSql);
|
||||
|
||||
return extId;
|
||||
}
|
||||
|
|
@ -41,54 +38,32 @@ class CardRepositoryImpl implements CardRepository {
|
|||
const cardId = generateId();
|
||||
const aimeId = generateExtId() as AimeId;
|
||||
|
||||
const playerSql = sql
|
||||
.insert("aime_player", {
|
||||
id: playerId,
|
||||
ext_id: aimeId,
|
||||
register_time: now,
|
||||
})
|
||||
.toParams();
|
||||
const playerSql = sql.insert("aime_player", {
|
||||
id: playerId,
|
||||
ext_id: aimeId,
|
||||
register_time: now,
|
||||
});
|
||||
|
||||
await this._conn.query(playerSql);
|
||||
await this._txn.modify(playerSql);
|
||||
|
||||
const cardSql = sql
|
||||
.insert("aime_card", {
|
||||
id: cardId,
|
||||
player_id: playerId,
|
||||
nfc_id: luid,
|
||||
register_time: now,
|
||||
access_time: now,
|
||||
})
|
||||
.toParams();
|
||||
const cardSql = sql.insert("aime_card", {
|
||||
id: cardId,
|
||||
player_id: playerId,
|
||||
nfc_id: luid,
|
||||
register_time: now,
|
||||
access_time: now,
|
||||
});
|
||||
|
||||
await this._conn.query(cardSql);
|
||||
await this._txn.modify(cardSql);
|
||||
|
||||
return aimeId;
|
||||
}
|
||||
}
|
||||
|
||||
class TransactionImpl implements Transaction {
|
||||
constructor(private readonly _conn: PoolClient) {}
|
||||
export class SqlRepositories implements Repositories {
|
||||
constructor(private readonly _txn: Transaction) {}
|
||||
|
||||
cards(): CardRepository {
|
||||
return new CardRepositoryImpl(this._conn);
|
||||
}
|
||||
|
||||
async commit(): Promise<void> {
|
||||
await this._conn.query("commit");
|
||||
await this._conn.release();
|
||||
}
|
||||
|
||||
async rollback(): Promise<void> {
|
||||
await this._conn.query("rollback");
|
||||
await this._conn.release();
|
||||
return new CardRepositoryImpl(this._txn);
|
||||
}
|
||||
}
|
||||
|
||||
export async function beginDbSession(): Promise<Transaction> {
|
||||
const conn = await connect();
|
||||
|
||||
await conn.query("begin");
|
||||
|
||||
return new TransactionImpl(conn);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ import logger from "debug";
|
|||
import { Repositories } from "./repo";
|
||||
import * as Req from "./request";
|
||||
import * as Res from "./response";
|
||||
import { Transaction } from "../sql";
|
||||
import { SqlRepositories } from "./db";
|
||||
|
||||
const debug = logger("app:aimedb:ops");
|
||||
|
||||
|
|
@ -101,10 +103,12 @@ function log(
|
|||
}
|
||||
|
||||
export async function dispatch(
|
||||
rep: Repositories,
|
||||
txn: Transaction,
|
||||
req: Req.AimeRequest,
|
||||
now: Date
|
||||
): Promise<Res.AimeResponse | undefined> {
|
||||
const rep = new SqlRepositories(txn);
|
||||
|
||||
switch (req.type) {
|
||||
case "hello":
|
||||
return hello(rep, req, now);
|
||||
|
|
|
|||
|
|
@ -4,37 +4,37 @@ import { Socket } from "net";
|
|||
import { dispatch } from "./handler";
|
||||
import { AimeRequest } from "./request";
|
||||
import { setup } from "./pipeline";
|
||||
import { beginDbSession } from "./db";
|
||||
import { DataSource } from "../sql/api";
|
||||
|
||||
const debug = logger("app:aimedb:session");
|
||||
|
||||
export default async function aimedb(socket: Socket) {
|
||||
debug("Connection opened");
|
||||
export default function aimedb(db: DataSource) {
|
||||
return async function(socket: Socket) {
|
||||
debug("Connection opened");
|
||||
|
||||
const { input, output } = setup(socket);
|
||||
const txn = await beginDbSession();
|
||||
const { input, output } = setup(socket);
|
||||
|
||||
try {
|
||||
for await (const obj of input) {
|
||||
const now = new Date();
|
||||
const req = obj as AimeRequest;
|
||||
const res = await dispatch(txn, req, now);
|
||||
try {
|
||||
const now = new Date();
|
||||
const req = obj as AimeRequest;
|
||||
const res = await db.transaction(txn => dispatch(txn, req, now));
|
||||
|
||||
if (res === undefined) {
|
||||
debug("Closing connection");
|
||||
if (res === undefined) {
|
||||
debug("Closing connection");
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
output.write(res);
|
||||
} catch (e) {
|
||||
debug(`Connection error:\n${e.toString()}\n`);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
output.write(res);
|
||||
}
|
||||
|
||||
await txn.commit();
|
||||
} catch (e) {
|
||||
debug(`Connection error:\n${e.toString()}\n`);
|
||||
await txn.rollback();
|
||||
}
|
||||
|
||||
debug("Connection closed");
|
||||
socket.end();
|
||||
debug("Connection closed");
|
||||
socket.end();
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,9 +9,3 @@ export interface CardRepository {
|
|||
export interface Repositories {
|
||||
cards(): CardRepository;
|
||||
}
|
||||
|
||||
export interface Transaction extends Repositories {
|
||||
commit(): Promise<void>;
|
||||
|
||||
rollback(): Promise<void>;
|
||||
}
|
||||
|
|
|
|||
58
src/db.ts
58
src/db.ts
|
|
@ -1,58 +0,0 @@
|
|||
import { randomBytes } from "crypto";
|
||||
import logger from "debug";
|
||||
import { Pool, PoolClient } from "pg";
|
||||
|
||||
export type Id<T> = bigint & { __id: T };
|
||||
|
||||
const debug = logger("app:sql");
|
||||
const currentSchemaVer = 5;
|
||||
|
||||
const pool = new Pool();
|
||||
const fence = testConnection();
|
||||
|
||||
export async function connect(): Promise<PoolClient> {
|
||||
await fence;
|
||||
|
||||
return pool.connect();
|
||||
}
|
||||
|
||||
export function generateId(): bigint {
|
||||
const buf = randomBytes(8);
|
||||
|
||||
buf[0] &= 0x7f; // Force number to be non-negative
|
||||
|
||||
// Let's not depend on Node v12 for the sake of 3 LoC just yet.
|
||||
|
||||
const hi = buf.readUInt32BE(0);
|
||||
const lo = buf.readUInt32BE(4);
|
||||
|
||||
return (BigInt(hi) << 32n) | BigInt(lo);
|
||||
}
|
||||
|
||||
/** Generate a random 32-bit ID for use in external protocol messages */
|
||||
export function generateExtId(): number {
|
||||
const buf = randomBytes(4);
|
||||
|
||||
buf[0] &= 0x7f; // Force number to be non-negative
|
||||
|
||||
return buf.readUInt32BE(0);
|
||||
}
|
||||
|
||||
async function testConnection(): Promise<void> {
|
||||
const conn = await pool.connect();
|
||||
|
||||
try {
|
||||
const { rows } = await conn.query("select schemaver from meta");
|
||||
const { schemaver } = rows[0];
|
||||
|
||||
if (schemaver !== currentSchemaVer) {
|
||||
throw new Error(
|
||||
`Expected schema version ${currentSchemaVer}, db is on ${schemaver}`
|
||||
);
|
||||
}
|
||||
|
||||
debug("Connection established");
|
||||
} finally {
|
||||
conn.release();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,24 +1,22 @@
|
|||
import { ClientBase } from "pg";
|
||||
import sql from "sql-bricks";
|
||||
import sql from "sql-bricks-postgres";
|
||||
|
||||
import { BackgroundCode } from "../model/base";
|
||||
import { Profile } from "../model/profile";
|
||||
import { FlagRepository } from "../repo";
|
||||
import { generateId, Id } from "../../db";
|
||||
import { Id, Transaction, generateId } from "../../sql";
|
||||
|
||||
export class SqlBackgroundsRepository
|
||||
implements FlagRepository<BackgroundCode> {
|
||||
constructor(private readonly _conn: ClientBase) {}
|
||||
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)
|
||||
.toParams();
|
||||
.where("p.id", id);
|
||||
|
||||
const { rows } = await this._conn.query(loadSql);
|
||||
const rows = await this._txn.fetchRows(loadSql);
|
||||
const result = new Set<BackgroundCode>();
|
||||
|
||||
for (const row of rows) {
|
||||
|
|
@ -39,15 +37,13 @@ export class SqlBackgroundsRepository
|
|||
continue;
|
||||
}
|
||||
|
||||
const saveSql = sql
|
||||
.insert("idz_background_unlock", {
|
||||
id: generateId(),
|
||||
profile_id: profileId,
|
||||
background_no: flag,
|
||||
})
|
||||
.toParams();
|
||||
const saveSql = sql.insert("idz_background_unlock", {
|
||||
id: generateId(),
|
||||
profile_id: profileId,
|
||||
background_no: flag,
|
||||
});
|
||||
|
||||
await this._conn.query(saveSql);
|
||||
await this._txn.modify(saveSql);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
import { ClientBase } from "pg";
|
||||
import sql from "sql-bricks-postgres";
|
||||
|
||||
import { Car, CarSelector } from "../model/car";
|
||||
import { Profile } from "../model/profile";
|
||||
import { CarRepository } from "../repo";
|
||||
import { generateId, Id } from "../../db";
|
||||
import { Id, Transaction, generateId } from "../../sql";
|
||||
|
||||
function _extractRow(row: any): Car {
|
||||
return {
|
||||
|
|
@ -27,29 +26,26 @@ function _extractRow(row: any): Car {
|
|||
}
|
||||
|
||||
export class SqlCarRepository implements CarRepository {
|
||||
constructor(private readonly _conn: ClientBase) {}
|
||||
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)
|
||||
.toParams();
|
||||
.where("c.profile_id", profileId);
|
||||
|
||||
const { rows } = await this._conn.query(countSql);
|
||||
const row = rows[0];
|
||||
const row = await this._txn.fetchRow(countSql);
|
||||
|
||||
return parseInt(row.result, 10);
|
||||
return parseInt(row!.result, 10);
|
||||
}
|
||||
|
||||
async loadAllCars(profileId: Id<Profile>): Promise<Car[]> {
|
||||
const loadSql = sql
|
||||
.select("c.*")
|
||||
.from("idz_car c")
|
||||
.where("c.profile_id", profileId)
|
||||
.toParams();
|
||||
.where("c.profile_id", profileId);
|
||||
|
||||
const { rows } = await this._conn.query(loadSql);
|
||||
const rows = await this._txn.fetchRows(loadSql);
|
||||
|
||||
return rows.map(_extractRow);
|
||||
}
|
||||
|
|
@ -59,12 +55,11 @@ export class SqlCarRepository implements CarRepository {
|
|||
.select("c.*")
|
||||
.from("idz_car c")
|
||||
.join("idz_car_selection s", { "c.id": "s.car_id" })
|
||||
.where("s.id", profileId)
|
||||
.toParams();
|
||||
.where("s.id", profileId);
|
||||
|
||||
const { rows } = await this._conn.query(loadSql);
|
||||
const row = await this._txn.fetchRow(loadSql);
|
||||
|
||||
return _extractRow(rows[0]);
|
||||
return _extractRow(row);
|
||||
}
|
||||
|
||||
async saveCar(profileId: Id<Profile>, car: Car): Promise<void> {
|
||||
|
|
@ -104,10 +99,9 @@ export class SqlCarRepository implements CarRepository {
|
|||
"field_5b",
|
||||
"field_5c",
|
||||
"field_5e",
|
||||
])
|
||||
.toParams();
|
||||
]);
|
||||
|
||||
await this._conn.query(saveSql);
|
||||
await this._txn.modify(saveSql);
|
||||
}
|
||||
|
||||
async saveSelection(
|
||||
|
|
@ -118,11 +112,9 @@ export class SqlCarRepository implements CarRepository {
|
|||
.select("c.id")
|
||||
.from("idz_car c")
|
||||
.where("c.profile_id", profileId)
|
||||
.where("c.selector", selector)
|
||||
.toParams();
|
||||
.where("c.selector", selector);
|
||||
|
||||
const { rows } = await this._conn.query(findSql);
|
||||
const row = rows[0];
|
||||
const row = await this._txn.fetchRow(findSql);
|
||||
|
||||
if (row === undefined) {
|
||||
throw new Error("Selected car not found");
|
||||
|
|
@ -134,9 +126,8 @@ export class SqlCarRepository implements CarRepository {
|
|||
car_id: row.id,
|
||||
})
|
||||
.onConflict("id")
|
||||
.doUpdate(["car_id"])
|
||||
.toParams();
|
||||
.doUpdate(["car_id"]);
|
||||
|
||||
await this._conn.query(saveSql);
|
||||
await this._txn.modify(saveSql);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
import { ClientBase } from "pg";
|
||||
import sql from "sql-bricks-postgres";
|
||||
|
||||
import { Chara } from "../model/chara";
|
||||
import { Profile } from "../model/profile";
|
||||
import { FacetRepository } from "../repo";
|
||||
import { Id } from "../../db";
|
||||
import { Id, Transaction } from "../../sql";
|
||||
|
||||
export function _extractChara(row: any): Chara {
|
||||
return {
|
||||
|
|
@ -22,17 +21,15 @@ export function _extractChara(row: any): Chara {
|
|||
}
|
||||
|
||||
export class SqlCharaRepository implements FacetRepository<Chara> {
|
||||
constructor(private readonly _conn: ClientBase) {}
|
||||
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)
|
||||
.toParams();
|
||||
.where("c.id", profileId);
|
||||
|
||||
const { rows } = await this._conn.query(loadSql);
|
||||
const row = rows[0];
|
||||
const row = await this._txn.fetchRow(loadSql);
|
||||
|
||||
return _extractChara(row);
|
||||
}
|
||||
|
|
@ -63,9 +60,8 @@ export class SqlCharaRepository implements FacetRepository<Chara> {
|
|||
"field_0e",
|
||||
"title",
|
||||
"background",
|
||||
])
|
||||
.toParams();
|
||||
]);
|
||||
|
||||
await this._conn.query(saveSql);
|
||||
await this._txn.modify(saveSql);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +1,20 @@
|
|||
import { ClientBase } from "pg";
|
||||
import sql from "sql-bricks-postgres";
|
||||
|
||||
import { CourseNo, ExtId } from "../model/base";
|
||||
import { CourseNo } from "../model/base";
|
||||
import { Profile } from "../model/profile";
|
||||
import { CoursePlaysRepository } from "../repo";
|
||||
import { generateId, Id } from "../../db";
|
||||
import { Id, Transaction, generateId } from "../../sql";
|
||||
|
||||
export class SqlCoursePlaysRepository implements CoursePlaysRepository {
|
||||
constructor(private readonly _conn: ClientBase) {}
|
||||
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)
|
||||
.toParams();
|
||||
.where("cp.profile_id", profileId);
|
||||
|
||||
const { rows } = await this._conn.query(loadSql);
|
||||
const rows = await this._txn.fetchRows(loadSql);
|
||||
const result = new Map();
|
||||
|
||||
for (const row of rows) {
|
||||
|
|
@ -39,10 +37,9 @@ export class SqlCoursePlaysRepository implements CoursePlaysRepository {
|
|||
count: v,
|
||||
})
|
||||
.onConflict("profile_id", "course_no")
|
||||
.doUpdate(["count"])
|
||||
.toParams();
|
||||
.doUpdate(["count"]);
|
||||
|
||||
await this._conn.query(saveSql);
|
||||
await this._txn.modify(saveSql);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import { PoolClient } from "pg";
|
||||
|
||||
import { SqlBackgroundsRepository } from "./backgrounds";
|
||||
import { SqlCarRepository } from "./car";
|
||||
import { SqlCharaRepository } from "./chara";
|
||||
|
|
@ -18,90 +16,72 @@ import { SqlTitlesRepository } from "./titles";
|
|||
import { SqlUnlocksRepository } from "./unlocks";
|
||||
import * as Model from "../model";
|
||||
import * as Repo from "../repo";
|
||||
import { connect } from "../../db";
|
||||
import { Transaction } from "../../sql";
|
||||
|
||||
class TransactionImpl implements Repo.Transaction {
|
||||
constructor(private readonly _conn: PoolClient) {}
|
||||
export class SqlRepositories implements Repo.Repositories {
|
||||
constructor(private readonly _txn: Transaction) {}
|
||||
|
||||
backgrounds(): Repo.FlagRepository<Model.BackgroundCode> {
|
||||
return new SqlBackgroundsRepository(this._conn);
|
||||
return new SqlBackgroundsRepository(this._txn);
|
||||
}
|
||||
|
||||
car(): Repo.CarRepository {
|
||||
return new SqlCarRepository(this._conn);
|
||||
return new SqlCarRepository(this._txn);
|
||||
}
|
||||
|
||||
chara(): Repo.FacetRepository<Model.Chara> {
|
||||
return new SqlCharaRepository(this._conn);
|
||||
return new SqlCharaRepository(this._txn);
|
||||
}
|
||||
|
||||
coursePlays(): Repo.CoursePlaysRepository {
|
||||
return new SqlCoursePlaysRepository(this._conn);
|
||||
return new SqlCoursePlaysRepository(this._txn);
|
||||
}
|
||||
|
||||
missions(): Repo.FacetRepository<Model.MissionState> {
|
||||
return new SqlMissionsRepository(this._conn);
|
||||
return new SqlMissionsRepository(this._txn);
|
||||
}
|
||||
|
||||
profile(): Repo.ProfileRepository {
|
||||
return new SqlProfileRepository(this._conn);
|
||||
return new SqlProfileRepository(this._txn);
|
||||
}
|
||||
|
||||
settings(): Repo.FacetRepository<Model.Settings> {
|
||||
return new SqlSettingsRepository(this._conn);
|
||||
return new SqlSettingsRepository(this._txn);
|
||||
}
|
||||
|
||||
story(): Repo.FacetRepository<Model.Story> {
|
||||
return new SqlStoryRepository(this._conn);
|
||||
return new SqlStoryRepository(this._txn);
|
||||
}
|
||||
|
||||
teams(): Repo.TeamRepository {
|
||||
return new SqlTeamRepository(this._conn);
|
||||
return new SqlTeamRepository(this._txn);
|
||||
}
|
||||
|
||||
teamAuto(): Repo.TeamAutoRepository {
|
||||
return new SqlTeamAutoRepository(this._conn);
|
||||
return new SqlTeamAutoRepository(this._txn);
|
||||
}
|
||||
|
||||
teamMembers(): Repo.TeamMemberRepository {
|
||||
return new SqlTeamMemberRepository(this._conn);
|
||||
return new SqlTeamMemberRepository(this._txn);
|
||||
}
|
||||
|
||||
teamReservations(): Repo.TeamReservationRepository {
|
||||
return new SqlTeamReservationRepository(this._conn);
|
||||
return new SqlTeamReservationRepository(this._txn);
|
||||
}
|
||||
|
||||
tickets(): Repo.FacetRepository<Model.Tickets> {
|
||||
return new SqlTicketsRepository(this._conn);
|
||||
return new SqlTicketsRepository(this._txn);
|
||||
}
|
||||
|
||||
timeAttack(): Repo.TimeAttackRepository {
|
||||
return new SqlTimeAttackRepository(this._conn);
|
||||
return new SqlTimeAttackRepository(this._txn);
|
||||
}
|
||||
|
||||
titles(): Repo.FlagRepository<Model.TitleCode> {
|
||||
return new SqlTitlesRepository(this._conn);
|
||||
return new SqlTitlesRepository(this._txn);
|
||||
}
|
||||
|
||||
unlocks(): Repo.FacetRepository<Model.Unlocks> {
|
||||
return new SqlUnlocksRepository(this._conn);
|
||||
}
|
||||
|
||||
async commit(): Promise<void> {
|
||||
await this._conn.query("commit");
|
||||
await this._conn.release();
|
||||
}
|
||||
|
||||
async rollback(): Promise<void> {
|
||||
await this._conn.query("rollback");
|
||||
await this._conn.release();
|
||||
return new SqlUnlocksRepository(this._txn);
|
||||
}
|
||||
}
|
||||
|
||||
export async function beginDbSession(): Promise<Repo.Transaction> {
|
||||
const conn = await connect();
|
||||
|
||||
await conn.query("begin");
|
||||
|
||||
return new TransactionImpl(conn);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
import { ClientBase } from "pg";
|
||||
import sql from "sql-bricks-postgres";
|
||||
|
||||
import { MissionGrid, MissionState } from "../model/mission";
|
||||
import { Profile } from "../model/profile";
|
||||
import { FacetRepository } from "../repo";
|
||||
import { generateId, Id } from "../../db";
|
||||
import { Id, Transaction, generateId } from "../../sql";
|
||||
|
||||
export class SqlMissionsRepository implements FacetRepository<MissionState> {
|
||||
constructor(private readonly _conn: ClientBase) {}
|
||||
constructor(private readonly _txn: Transaction) {}
|
||||
|
||||
async load(profileId: Id<Profile>): Promise<MissionState> {
|
||||
const result: MissionState = {
|
||||
|
|
@ -31,10 +30,9 @@ export class SqlMissionsRepository implements FacetRepository<MissionState> {
|
|||
const loadSoloSql = sql
|
||||
.select("sm.*")
|
||||
.from("idz_solo_mission_state sm")
|
||||
.where("sm.profile_id", profileId)
|
||||
.toParams();
|
||||
.where("sm.profile_id", profileId);
|
||||
|
||||
const { rows } = await this._conn.query(loadSoloSql);
|
||||
const rows = await this._txn.fetchRows(loadSoloSql);
|
||||
|
||||
for (const row of rows) {
|
||||
result.solo[row.grid_no].cells[row.cell_no] = row.value;
|
||||
|
|
@ -64,10 +62,9 @@ export class SqlMissionsRepository implements FacetRepository<MissionState> {
|
|||
value: grid[j],
|
||||
})
|
||||
.onConflict("profile_id", "grid_no", "cell_no")
|
||||
.doUpdate(["value"])
|
||||
.toParams();
|
||||
.doUpdate(["value"]);
|
||||
|
||||
await this._conn.query(saveSql);
|
||||
await this._txn.modify(saveSql);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
import sql from "sql-bricks";
|
||||
import { ClientBase } from "pg";
|
||||
import sql from "sql-bricks-postgres";
|
||||
|
||||
import { Profile } from "../model/profile";
|
||||
import { ProfileRepository } from "../repo";
|
||||
import { generateId, Id } from "../../db";
|
||||
import { AimeId } from "../../model";
|
||||
import { Id, Transaction, generateId } from "../../sql";
|
||||
|
||||
export function _extractProfile(row: any): Profile {
|
||||
return {
|
||||
|
|
@ -21,7 +20,7 @@ export function _extractProfile(row: any): Profile {
|
|||
}
|
||||
|
||||
export class SqlProfileRepository implements ProfileRepository {
|
||||
constructor(private readonly _conn: ClientBase) {}
|
||||
constructor(private readonly _txn: Transaction) {}
|
||||
|
||||
async find(aimeId: AimeId): Promise<Id<Profile>> {
|
||||
const profileId = await this.peek(aimeId);
|
||||
|
|
@ -38,11 +37,9 @@ export class SqlProfileRepository implements ProfileRepository {
|
|||
.select("p.id")
|
||||
.from("idz_profile p")
|
||||
.join("aime_player r", { "p.player_id": "r.id" })
|
||||
.where("r.ext_id", aimeId)
|
||||
.toParams();
|
||||
.where("r.ext_id", aimeId);
|
||||
|
||||
const { rows } = await this._conn.query(lookupSql);
|
||||
const row = rows[0];
|
||||
const row = await this._txn.fetchRow(lookupSql);
|
||||
|
||||
if (row === undefined) {
|
||||
return undefined;
|
||||
|
|
@ -56,12 +53,11 @@ export class SqlProfileRepository implements ProfileRepository {
|
|||
.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)
|
||||
.toParams();
|
||||
.where("p.id", id);
|
||||
|
||||
const { rows } = await this._conn.query(loadSql);
|
||||
const row = await this._txn.fetchRow(loadSql);
|
||||
|
||||
return _extractProfile(rows[0]);
|
||||
return _extractProfile(row);
|
||||
}
|
||||
|
||||
async save(id: Id<Profile>, profile: Profile): Promise<void> {
|
||||
|
|
@ -74,21 +70,18 @@ export class SqlProfileRepository implements ProfileRepository {
|
|||
mileage: profile.mileage,
|
||||
access_time: profile.accessTime,
|
||||
})
|
||||
.where("id", id)
|
||||
.toParams();
|
||||
.where("id", id);
|
||||
|
||||
await this._conn.query(saveSql);
|
||||
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)
|
||||
.toParams();
|
||||
.where("r.ext_id", profile.aimeId);
|
||||
|
||||
const { rows } = await this._conn.query(findSql);
|
||||
const row = rows[0];
|
||||
const row = await this._txn.fetchRow(findSql);
|
||||
|
||||
if (row === undefined) {
|
||||
throw new Error("Aime ID not found");
|
||||
|
|
@ -97,22 +90,20 @@ export class SqlProfileRepository implements ProfileRepository {
|
|||
const id = generateId();
|
||||
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,
|
||||
access_time: profile.accessTime,
|
||||
})
|
||||
.toParams();
|
||||
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,
|
||||
access_time: profile.accessTime,
|
||||
});
|
||||
|
||||
await this._conn.query(createSql);
|
||||
await this._txn.modify(createSql);
|
||||
|
||||
return id as Id<Profile>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,24 @@
|
|||
import { ClientBase } from "pg";
|
||||
import sql from "sql-bricks-postgres";
|
||||
|
||||
import { Settings } from "../model/settings";
|
||||
import { Profile } from "../model/profile";
|
||||
import { FacetRepository } from "../repo";
|
||||
import { Id } from "../../db";
|
||||
import { Id, Transaction } from "../../sql";
|
||||
|
||||
export class SqlSettingsRepository implements FacetRepository<Settings> {
|
||||
constructor(private readonly _conn: ClientBase) {}
|
||||
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)
|
||||
.toParams();
|
||||
.where("s.id", profileId);
|
||||
|
||||
const { rows } = await this._conn.query(loadSql);
|
||||
const row = rows[0];
|
||||
const row = await this._txn.fetchRow(loadSql);
|
||||
|
||||
if (row === undefined) {
|
||||
throw new Error(`Settings not found, profileId=${profileId}`);
|
||||
}
|
||||
|
||||
return {
|
||||
music: row.music,
|
||||
|
|
@ -37,9 +38,8 @@ export class SqlSettingsRepository implements FacetRepository<Settings> {
|
|||
gauges: settings.gauges,
|
||||
})
|
||||
.onConflict("id")
|
||||
.doUpdate(["music", "pack", "paper_cup", "gauges"])
|
||||
.toParams();
|
||||
.doUpdate(["music", "pack", "paper_cup", "gauges"]);
|
||||
|
||||
await this._conn.query(saveSql);
|
||||
await this._txn.modify(saveSql);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,20 @@
|
|||
import { ClientBase } from "pg";
|
||||
import sql from "sql-bricks-postgres";
|
||||
|
||||
import { Profile } from "../model/profile";
|
||||
import { Story, StoryRow, StoryCell } from "../model/story";
|
||||
import { FacetRepository } from "../repo";
|
||||
import { generateId, Id } from "../../db";
|
||||
import { Id, Transaction, generateId } from "../../sql";
|
||||
|
||||
export class SqlStoryRepository implements FacetRepository<Story> {
|
||||
constructor(private readonly _conn: ClientBase) {}
|
||||
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)
|
||||
.toParams();
|
||||
.where("s.id", profileId);
|
||||
|
||||
const headerResult = await this._conn.query(loadSql);
|
||||
const header = headerResult.rows[0];
|
||||
const header = await this._txn.fetchRow(loadSql);
|
||||
|
||||
// Must succeed even if nonexistent (required by save method below)
|
||||
|
||||
|
|
@ -40,10 +37,9 @@ export class SqlStoryRepository implements FacetRepository<Story> {
|
|||
const loadCellSql = sql
|
||||
.select("sc.*")
|
||||
.from("idz_story_cell_state sc")
|
||||
.where("sc.profile_id", profileId)
|
||||
.toParams();
|
||||
.where("sc.profile_id", profileId);
|
||||
|
||||
const { rows } = await this._conn.query(loadCellSql);
|
||||
const rows = await this._txn.fetchRows(loadCellSql);
|
||||
|
||||
for (const row of rows) {
|
||||
const cell = result.rows[row.row_no].cells[row.col_no];
|
||||
|
|
@ -65,10 +61,9 @@ export class SqlStoryRepository implements FacetRepository<Story> {
|
|||
y: story.y,
|
||||
})
|
||||
.onConflict("id")
|
||||
.doUpdate(["x", "y"])
|
||||
.toParams();
|
||||
.doUpdate(["x", "y"]);
|
||||
|
||||
await this._conn.query(headSql);
|
||||
await this._txn.modify(headSql);
|
||||
|
||||
for (let i = 0; i < story.rows.length; i++) {
|
||||
const exRow = existing.rows[i];
|
||||
|
|
@ -92,10 +87,9 @@ export class SqlStoryRepository implements FacetRepository<Story> {
|
|||
b: cell.b,
|
||||
})
|
||||
.onConflict("profile_id", "row_no", "col_no")
|
||||
.doUpdate(["a", "b"])
|
||||
.toParams();
|
||||
.doUpdate(["a", "b"]);
|
||||
|
||||
await this._conn.query(cellSql);
|
||||
await this._txn.modify(cellSql);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,21 @@
|
|||
import { ClientBase } from "pg";
|
||||
import sql from "sql-bricks";
|
||||
import sql from "sql-bricks-postgres";
|
||||
|
||||
import { ExtId } from "../model/base";
|
||||
import { Team } from "../model/team";
|
||||
import { TeamSpec, TeamRepository } from "../repo";
|
||||
import { Id, generateExtId, generateId } from "../../db";
|
||||
import { generateExtId } from "../../model";
|
||||
import { Id, Transaction, generateId } from "../../sql";
|
||||
|
||||
export class SqlTeamRepository implements TeamRepository {
|
||||
constructor(private readonly _conn: ClientBase) {}
|
||||
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)
|
||||
.toParams();
|
||||
.where("t.ext_id", extId);
|
||||
|
||||
const { rows } = await this._conn.query(findSql);
|
||||
const row = rows[0];
|
||||
const row = await this._txn.fetchRow(findSql);
|
||||
|
||||
if (row === undefined) {
|
||||
throw new Error(`Team not found for ExtID ${extId}`);
|
||||
|
|
@ -30,13 +28,11 @@ export class SqlTeamRepository implements TeamRepository {
|
|||
const loadSql = sql
|
||||
.select("t.*")
|
||||
.from("idz_team t")
|
||||
.where("t.id", id)
|
||||
.toParams();
|
||||
.where("t.id", id);
|
||||
|
||||
const { rows } = await this._conn.query(loadSql);
|
||||
const row = rows[0];
|
||||
const row = await this._txn.fetchRow(loadSql);
|
||||
|
||||
if (row == undefined) {
|
||||
if (row === undefined) {
|
||||
throw new Error("Team not found");
|
||||
}
|
||||
|
||||
|
|
@ -55,38 +51,32 @@ export class SqlTeamRepository implements TeamRepository {
|
|||
name_bg: team.nameBg,
|
||||
name_fx: team.nameFx,
|
||||
})
|
||||
.where("id", id)
|
||||
.toParams();
|
||||
.where("id", id);
|
||||
|
||||
await this._conn.query(saveSql);
|
||||
await this._txn.modify(saveSql);
|
||||
}
|
||||
|
||||
async create(team: TeamSpec): Promise<[Id<Team>, ExtId<Team>]> {
|
||||
const id = generateId() as Id<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,
|
||||
})
|
||||
.toParams();
|
||||
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._conn.query(createSql);
|
||||
await this._txn.modify(createSql);
|
||||
|
||||
return [id, extId];
|
||||
}
|
||||
|
||||
async delete(id: Id<Team>): Promise<void> {
|
||||
const deleteSql = sql
|
||||
.delete("idz_team")
|
||||
.where("id", id)
|
||||
.toParams();
|
||||
const deleteSql = sql.delete("idz_team").where("id", id);
|
||||
|
||||
await this._conn.query(deleteSql);
|
||||
await this._txn.modify(deleteSql);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,20 @@
|
|||
import sql from "sql-bricks-postgres";
|
||||
import { ClientBase } from "pg";
|
||||
|
||||
import { Team, TeamAuto } from "../model/team";
|
||||
import { TeamAutoRepository } from "../repo";
|
||||
import { Id } from "../../db";
|
||||
import { Id, Transaction } from "../../sql";
|
||||
|
||||
export class SqlTeamAutoRepository implements TeamAutoRepository {
|
||||
constructor(private readonly _conn: ClientBase) {}
|
||||
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)
|
||||
.toParams();
|
||||
.limit(1);
|
||||
|
||||
const { rows } = await this._conn.query(peekSql);
|
||||
const row = rows[0];
|
||||
const row = await this._txn.fetchRow(peekSql);
|
||||
|
||||
return (
|
||||
row && [
|
||||
|
|
@ -31,14 +28,12 @@ export class SqlTeamAutoRepository implements TeamAutoRepository {
|
|||
}
|
||||
|
||||
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,
|
||||
})
|
||||
.toParams();
|
||||
const pushSql = sql.insert("idz_team_auto", {
|
||||
id: teamId,
|
||||
serial_no: auto.serialNo,
|
||||
name_idx: auto.nameIdx,
|
||||
});
|
||||
|
||||
await this._conn.query(pushSql);
|
||||
await this._txn.modify(pushSql);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,25 +1,22 @@
|
|||
import sql from "sql-bricks-postgres";
|
||||
import { ClientBase } from "pg";
|
||||
|
||||
import { Profile } from "../model/profile";
|
||||
import { Team, TeamMember } from "../model/team";
|
||||
import { TeamMemberRepository } from "../repo";
|
||||
import { Id, generateId } from "../../db";
|
||||
import { _extractProfile } from "./profile";
|
||||
import { _extractChara } from "./chara";
|
||||
import { Id, Transaction } from "../../sql";
|
||||
|
||||
export class SqlTeamMemberRepository implements TeamMemberRepository {
|
||||
constructor(private readonly _conn: ClientBase) {}
|
||||
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)
|
||||
.toParams();
|
||||
.where("tm.id", profileId);
|
||||
|
||||
const { rows } = await this._conn.query(findSql);
|
||||
const row = rows[0];
|
||||
const row = await this._txn.fetchRow(findSql);
|
||||
|
||||
if (row === undefined) {
|
||||
return undefined;
|
||||
|
|
@ -33,11 +30,9 @@ export class SqlTeamMemberRepository implements TeamMemberRepository {
|
|||
.select("tm.id")
|
||||
.from("idz_team_member tm")
|
||||
.where("tm.team_id", teamId)
|
||||
.where("tm.leader", true)
|
||||
.toParams();
|
||||
.where("tm.leader", true);
|
||||
|
||||
const { rows } = await this._conn.query(findSql);
|
||||
const row = rows[0];
|
||||
const row = await this._txn.fetchRow(findSql);
|
||||
|
||||
if (row === undefined) {
|
||||
return undefined;
|
||||
|
|
@ -53,10 +48,9 @@ export class SqlTeamMemberRepository implements TeamMemberRepository {
|
|||
.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)
|
||||
.toParams();
|
||||
.where("tm.team_id", teamId);
|
||||
|
||||
const { rows } = await this._conn.query(loadSql);
|
||||
const rows = await this._txn.fetchRows(loadSql);
|
||||
|
||||
return rows.map((row: any) => ({
|
||||
profile: _extractProfile(row),
|
||||
|
|
@ -77,10 +71,9 @@ export class SqlTeamMemberRepository implements TeamMemberRepository {
|
|||
.select("id")
|
||||
.from("idz_team")
|
||||
.where("id", teamId)
|
||||
.forUpdate()
|
||||
.toParams();
|
||||
.forUpdate();
|
||||
|
||||
await this._conn.query(lockSql);
|
||||
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
|
||||
|
|
@ -94,13 +87,11 @@ export class SqlTeamMemberRepository implements TeamMemberRepository {
|
|||
const countSql = sql
|
||||
.select("count(*) as count")
|
||||
.from("idz_team_member")
|
||||
.where("team_id", teamId)
|
||||
.toParams();
|
||||
.where("team_id", teamId);
|
||||
|
||||
const { rows } = await this._conn.query(countSql);
|
||||
const row = rows[0];
|
||||
const row = await this._txn.fetchRow(countSql);
|
||||
|
||||
if (row.count >= 6) {
|
||||
if (row!.count >= 6) {
|
||||
throw new Error(`Team ${teamId} is full`);
|
||||
}
|
||||
|
||||
|
|
@ -114,20 +105,18 @@ export class SqlTeamMemberRepository implements TeamMemberRepository {
|
|||
join_time: timestamp,
|
||||
})
|
||||
.onConflict("id")
|
||||
.doUpdate(["team_id", "leader", "join_time"])
|
||||
.toParams();
|
||||
.doUpdate(["team_id", "leader", "join_time"]);
|
||||
|
||||
await this._conn.query(joinSql);
|
||||
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)
|
||||
.toParams();
|
||||
.where("id", profileId);
|
||||
|
||||
await this._conn.query(leaveSql);
|
||||
await this._txn.modify(leaveSql);
|
||||
}
|
||||
|
||||
async makeLeader(teamId: Id<Team>, profileId: Id<Profile>): Promise<void> {
|
||||
|
|
@ -135,19 +124,17 @@ export class SqlTeamMemberRepository implements TeamMemberRepository {
|
|||
.update("idz_team_member", {
|
||||
leader: false,
|
||||
})
|
||||
.where("team_id", teamId)
|
||||
.toParams();
|
||||
.where("team_id", teamId);
|
||||
|
||||
await this._conn.query(clearSql);
|
||||
await this._txn.modify(clearSql);
|
||||
|
||||
const setSql = sql
|
||||
.update("idz_team_member", {
|
||||
leader: true,
|
||||
})
|
||||
.where("id", profileId)
|
||||
.where("team_id", teamId)
|
||||
.toParams();
|
||||
.where("team_id", teamId);
|
||||
|
||||
await this._conn.query(setSql);
|
||||
await this._txn.modify(setSql);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,24 +1,22 @@
|
|||
import sql from "sql-bricks-postgres";
|
||||
import { ClientBase } from "pg";
|
||||
|
||||
import { Team } from "../model/team";
|
||||
import { TeamReservationRepository } from "../repo";
|
||||
import { Id } from "../../db";
|
||||
import { AimeId } from "../../model";
|
||||
import { Id, Transaction } from "../../sql";
|
||||
|
||||
export class SqlTeamReservationRepository
|
||||
implements TeamReservationRepository {
|
||||
constructor(private readonly _conn: ClientBase) {}
|
||||
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()
|
||||
.toParams();
|
||||
.forUpdate();
|
||||
|
||||
await this._conn.query(lockSql);
|
||||
await this._txn.modify(lockSql);
|
||||
}
|
||||
|
||||
async occupancyHack(teamId: Id<Team>): Promise<number> {
|
||||
|
|
@ -30,20 +28,18 @@ export class SqlTeamReservationRepository
|
|||
const memberSql = sql
|
||||
.select("count(*) as count")
|
||||
.from("idz_team_member tm")
|
||||
.where("tm.team_id", teamId)
|
||||
.toParams();
|
||||
.where("tm.team_id", teamId);
|
||||
|
||||
const memberRes = await this._conn.query(memberSql);
|
||||
const memberCount = parseInt(memberRes.rows[0].count, 10);
|
||||
const memberRes = await this._txn.fetchRow(memberSql);
|
||||
const memberCount = parseInt(memberRes!.count, 10);
|
||||
|
||||
const reservSql = sql
|
||||
.select("count(*) as count")
|
||||
.from("idz_team_reservation tr")
|
||||
.where("tr.team_id", teamId)
|
||||
.toParams();
|
||||
.where("tr.team_id", teamId);
|
||||
|
||||
const reservRes = await this._conn.query(reservSql);
|
||||
const reservCount = parseInt(reservRes.rows[0].count, 10);
|
||||
const reservRes = await this._txn.fetchRow(reservSql);
|
||||
const reservCount = parseInt(reservRes!.count, 10);
|
||||
|
||||
return memberCount + reservCount;
|
||||
}
|
||||
|
|
@ -57,11 +53,9 @@ export class SqlTeamReservationRepository
|
|||
const lookupSql = sql
|
||||
.select("r.id")
|
||||
.from("aime_player r")
|
||||
.where("r.ext_id", aimeId)
|
||||
.toParams();
|
||||
.where("r.ext_id", aimeId);
|
||||
|
||||
const { rows } = await this._conn.query(lookupSql);
|
||||
const row = rows[0];
|
||||
const row = await this._txn.fetchRow(lookupSql);
|
||||
|
||||
if (row === undefined) {
|
||||
throw new Error(`Unknown Aime ID ${aimeId}`);
|
||||
|
|
@ -77,10 +71,9 @@ export class SqlTeamReservationRepository
|
|||
leader: leader === "leader",
|
||||
})
|
||||
.onConflict("id")
|
||||
.doUpdate(["team_id"])
|
||||
.toParams();
|
||||
.doUpdate(["team_id"]);
|
||||
|
||||
await this._conn.query(insertSql);
|
||||
await this._txn.modify(insertSql);
|
||||
}
|
||||
|
||||
async commitHack(aimeId: AimeId): Promise<void> {
|
||||
|
|
@ -89,32 +82,25 @@ export class SqlTeamReservationRepository
|
|||
.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)
|
||||
.toParams();
|
||||
.where("r.ext_id", aimeId);
|
||||
|
||||
const { rows } = await this._conn.query(lookupSql);
|
||||
const row = rows[0];
|
||||
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,
|
||||
})
|
||||
.toParams();
|
||||
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._conn.query(insertSql);
|
||||
await this._txn.modify(insertSql);
|
||||
|
||||
const cleanupSql = sql
|
||||
.delete("idz_team_reservation")
|
||||
.where("id", row.id)
|
||||
.toParams();
|
||||
const cleanupSql = sql.delete("idz_team_reservation").where("id", row.id);
|
||||
|
||||
await this._conn.query(cleanupSql);
|
||||
await this._txn.modify(cleanupSql);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,25 +1,22 @@
|
|||
import { ClientBase } from "pg";
|
||||
import sql from "sql-bricks-postgres";
|
||||
|
||||
import { Profile } from "../model/profile";
|
||||
import { Tickets } from "../model/tickets";
|
||||
import { FacetRepository } from "../repo";
|
||||
import { Id } from "../../db";
|
||||
import { Id, Transaction } from "../../sql";
|
||||
|
||||
// TODO free continue
|
||||
|
||||
export class SqlTicketsRepository implements FacetRepository<Tickets> {
|
||||
constructor(private readonly _conn: ClientBase) {}
|
||||
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)
|
||||
.toParams();
|
||||
.where("fc.id", profileId);
|
||||
|
||||
const { rows } = await this._conn.query(loadSql);
|
||||
const row = rows[0];
|
||||
const row = await this._txn.fetchRow(loadSql);
|
||||
|
||||
return {
|
||||
freeCar: row && {
|
||||
|
|
@ -32,12 +29,9 @@ export class SqlTicketsRepository implements FacetRepository<Tickets> {
|
|||
const { freeCar } = tickets;
|
||||
|
||||
if (!freeCar) {
|
||||
const delSql = sql
|
||||
.delete("idz_free_car")
|
||||
.where("id", profileId)
|
||||
.toParams();
|
||||
const delSql = sql.delete("idz_free_car").where("id", profileId);
|
||||
|
||||
await this._conn.query(delSql);
|
||||
await this._txn.modify(delSql);
|
||||
} else {
|
||||
const saveSql = sql
|
||||
.insert("idz_free_car", {
|
||||
|
|
@ -45,10 +39,9 @@ export class SqlTicketsRepository implements FacetRepository<Tickets> {
|
|||
valid_from: freeCar.validFrom,
|
||||
})
|
||||
.onConflict("id")
|
||||
.doUpdate(["valid_from"])
|
||||
.toParams();
|
||||
.doUpdate(["valid_from"]);
|
||||
|
||||
await this._conn.query(saveSql);
|
||||
await this._txn.modify(saveSql);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,13 @@
|
|||
import { ClientBase } from "pg";
|
||||
import sql from "sql-bricks-postgres";
|
||||
|
||||
import { RouteNo } from "../model/base";
|
||||
import { Profile } from "../model/profile";
|
||||
import { TimeAttackScore } from "../model/timeAttack";
|
||||
import { TimeAttackRepository, TopTenResult } from "../repo";
|
||||
import { generateId, Id } from "../../db";
|
||||
import { Id, Transaction, generateId } from "../../sql";
|
||||
|
||||
export class SqlTimeAttackRepository implements TimeAttackRepository {
|
||||
constructor(private readonly _conn: ClientBase) {}
|
||||
constructor(private readonly _txn: Transaction) {}
|
||||
|
||||
async loadTopTen(
|
||||
routeNo: RouteNo,
|
||||
|
|
@ -21,10 +20,9 @@ export class SqlTimeAttackRepository implements TimeAttackRepository {
|
|||
.where("ta.route_no", routeNo)
|
||||
.where(sql.gt("ta.timestamp", minTimestamp))
|
||||
.orderBy(["ta.total_time asc", "ta.timestamp asc"])
|
||||
.limit(10)
|
||||
.toParams();
|
||||
.limit(10);
|
||||
|
||||
const { rows } = await this._conn.query(loadSql);
|
||||
const rows = await this._txn.fetchRows(loadSql);
|
||||
|
||||
return rows.map(row => ({
|
||||
driverName: row.name,
|
||||
|
|
@ -44,10 +42,9 @@ export class SqlTimeAttackRepository implements TimeAttackRepository {
|
|||
const loadSql = sql
|
||||
.select("ta.*")
|
||||
.from("idz_ta_best ta")
|
||||
.where("ta.profile_id", profileId)
|
||||
.toParams();
|
||||
.where("ta.profile_id", profileId);
|
||||
|
||||
const { rows } = await this._conn.query(loadSql);
|
||||
const rows = await this._txn.fetchRows(loadSql);
|
||||
|
||||
return rows.map(row => ({
|
||||
routeNo: row.route_no,
|
||||
|
|
@ -61,8 +58,30 @@ export class SqlTimeAttackRepository implements TimeAttackRepository {
|
|||
}
|
||||
|
||||
async save(profileId: Id<Profile>, score: TimeAttackScore): Promise<void> {
|
||||
const logSql = sql
|
||||
.insert("idz_ta_result", {
|
||||
const logSql = sql.insert("idz_ta_result", {
|
||||
id: generateId(),
|
||||
profile_id: profileId,
|
||||
route_no: score.routeNo,
|
||||
total_time: score.totalTime,
|
||||
section_times: score.sectionTimes,
|
||||
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: generateId(),
|
||||
profile_id: profileId,
|
||||
route_no: score.routeNo,
|
||||
|
|
@ -72,37 +91,9 @@ export class SqlTimeAttackRepository implements TimeAttackRepository {
|
|||
grade: score.grade,
|
||||
car_selector: score.carSelector,
|
||||
timestamp: score.timestamp,
|
||||
})
|
||||
.toParams();
|
||||
});
|
||||
|
||||
await this._conn.query(logSql);
|
||||
|
||||
const existSql = sql
|
||||
.select("ta.total_time")
|
||||
.from("idz_ta_best ta")
|
||||
.where("ta.profile_id", profileId)
|
||||
.where("ta.route_no", score.routeNo)
|
||||
.toParams();
|
||||
|
||||
const { rows } = await this._conn.query(existSql);
|
||||
const row = rows[0];
|
||||
|
||||
if (row === undefined) {
|
||||
const insertSql = sql
|
||||
.insert("idz_ta_best", {
|
||||
id: generateId(),
|
||||
profile_id: profileId,
|
||||
route_no: score.routeNo,
|
||||
total_time: score.totalTime,
|
||||
section_times: score.sectionTimes,
|
||||
flags: score.flags,
|
||||
grade: score.grade,
|
||||
car_selector: score.carSelector,
|
||||
timestamp: score.timestamp,
|
||||
})
|
||||
.toParams();
|
||||
|
||||
await this._conn.query(insertSql);
|
||||
await this._txn.modify(insertSql);
|
||||
} else if (score.totalTime < row.total_time) {
|
||||
const updateSql = sql
|
||||
.update("idz_ta_best", {
|
||||
|
|
@ -114,10 +105,9 @@ export class SqlTimeAttackRepository implements TimeAttackRepository {
|
|||
timestamp: score.timestamp,
|
||||
})
|
||||
.where("profile_id", profileId)
|
||||
.where("route_no", score.routeNo)
|
||||
.toParams();
|
||||
.where("route_no", score.routeNo);
|
||||
|
||||
await this._conn.query(updateSql);
|
||||
await this._txn.modify(updateSql);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +1,20 @@
|
|||
import { ClientBase } from "pg";
|
||||
import sql from "sql-bricks";
|
||||
import sql from "sql-bricks-postgres";
|
||||
|
||||
import { TitleCode, ExtId } from "../model/base";
|
||||
import { TitleCode } from "../model/base";
|
||||
import { Profile } from "../model/profile";
|
||||
import { FlagRepository } from "../repo";
|
||||
import { generateId, Id } from "../../db";
|
||||
import { Id, Transaction, generateId } from "../../sql";
|
||||
|
||||
export class SqlTitlesRepository implements FlagRepository<TitleCode> {
|
||||
constructor(private readonly _conn: ClientBase) {}
|
||||
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)
|
||||
.toParams();
|
||||
.where("t.profile_id", profileId);
|
||||
|
||||
const { rows } = await this._conn.query(loadSql);
|
||||
const rows = await this._txn.fetchRows(loadSql);
|
||||
const result = new Set<TitleCode>();
|
||||
|
||||
for (const row of rows) {
|
||||
|
|
@ -34,15 +32,13 @@ export class SqlTitlesRepository implements FlagRepository<TitleCode> {
|
|||
continue;
|
||||
}
|
||||
|
||||
const saveSql = sql
|
||||
.insert("idz_title_unlock", {
|
||||
id: generateId(),
|
||||
profile_id: profileId,
|
||||
title_no: flag,
|
||||
})
|
||||
.toParams();
|
||||
const saveSql = sql.insert("idz_title_unlock", {
|
||||
id: generateId(),
|
||||
profile_id: profileId,
|
||||
title_no: flag,
|
||||
});
|
||||
|
||||
await this._conn.query(saveSql);
|
||||
await this._txn.modify(saveSql);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,24 +1,24 @@
|
|||
import { ClientBase } from "pg";
|
||||
import sql from "sql-bricks-postgres";
|
||||
|
||||
import { ExtId } from "../model/base";
|
||||
import { Profile } from "../model/profile";
|
||||
import { Unlocks } from "../model/unlocks";
|
||||
import { FacetRepository } from "../repo";
|
||||
import { Id } from "../../db";
|
||||
import { Id, Transaction } from "../../sql";
|
||||
|
||||
export class SqlUnlocksRepository implements FacetRepository<Unlocks> {
|
||||
constructor(private readonly _conn: ClientBase) {}
|
||||
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)
|
||||
.toParams();
|
||||
.where("u.id", profileId);
|
||||
|
||||
const { rows } = await this._conn.query(loadSql);
|
||||
const row = rows[0];
|
||||
const row = await this._txn.fetchRow(loadSql);
|
||||
|
||||
if (row === undefined) {
|
||||
throw new Error(`Unlocks not found, profileId=${profileId}`);
|
||||
}
|
||||
|
||||
return {
|
||||
cup: row.cup,
|
||||
|
|
@ -38,9 +38,8 @@ export class SqlUnlocksRepository implements FacetRepository<Unlocks> {
|
|||
last_mileage_reward: unlocks.lastMileageReward,
|
||||
})
|
||||
.onConflict("id")
|
||||
.doUpdate(["cup", "gauges", "music", "last_mileage_reward"])
|
||||
.toParams();
|
||||
.doUpdate(["cup", "gauges", "music", "last_mileage_reward"]);
|
||||
|
||||
await this._conn.query(saveSql);
|
||||
await this._txn.modify(saveSql);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Team } from "../model/team";
|
||||
import { Repositories } from "../repo";
|
||||
import { Id } from "../../db";
|
||||
import { Id } from "../../sql";
|
||||
|
||||
// Bleh. This factorization is kind of messy.
|
||||
|
||||
|
|
|
|||
|
|
@ -43,11 +43,15 @@ import { updateUserLog } from "./updateUserLog";
|
|||
import { Request } from "../request";
|
||||
import { Response } from "../response";
|
||||
import { Repositories } from "../repo";
|
||||
import { Transaction } from "../../sql";
|
||||
import { SqlRepositories } from "../db";
|
||||
|
||||
export async function dispatch(
|
||||
w: Repositories,
|
||||
txn: Transaction,
|
||||
req: Request
|
||||
): Promise<Response> {
|
||||
const w = new SqlRepositories(txn);
|
||||
|
||||
switch (req.type) {
|
||||
case "check_team_name_req":
|
||||
return checkTeamName(w, req);
|
||||
|
|
|
|||
|
|
@ -1,30 +1,28 @@
|
|||
import logger from "debug";
|
||||
import { Socket } from "net";
|
||||
|
||||
import { beginDbSession } from "./db";
|
||||
import { dispatch } from "./handler";
|
||||
import { setup } from "./setup";
|
||||
import { DataSource } from "../sql";
|
||||
|
||||
const debug = logger("app:idz:session");
|
||||
|
||||
export default async function idz(socket: Socket) {
|
||||
const txn = await beginDbSession();
|
||||
const { input, output } = setup(socket);
|
||||
export default function idz(db: DataSource) {
|
||||
return async function(socket: Socket) {
|
||||
const { input, output } = setup(socket);
|
||||
|
||||
debug("Connection opened");
|
||||
debug("Connection opened");
|
||||
|
||||
try {
|
||||
for await (const req of input) {
|
||||
output.write(await dispatch(txn, req));
|
||||
try {
|
||||
for await (const req of input) {
|
||||
output.write(await db.transaction(txn => dispatch(txn, req)));
|
||||
}
|
||||
} catch (e) {
|
||||
debug(`Error:\n${e.toString()}`);
|
||||
}
|
||||
|
||||
await txn.commit();
|
||||
} catch (e) {
|
||||
debug(`Error:\n${e.toString()}`);
|
||||
await txn.rollback();
|
||||
}
|
||||
debug("Connection closed");
|
||||
|
||||
debug("Connection closed");
|
||||
|
||||
input.end();
|
||||
input.end();
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@ import { Subtract } from "utility-types";
|
|||
|
||||
import * as Model from "./model";
|
||||
import { AimeId } from "../model";
|
||||
import { Id } from "../db";
|
||||
import { Id } from "../sql";
|
||||
|
||||
// Id<> is a layer break here... need to find a better way to deal with this.
|
||||
|
||||
export type TeamSpec = Subtract<
|
||||
Model.Team,
|
||||
|
|
@ -164,9 +166,3 @@ export interface Repositories {
|
|||
|
||||
unlocks(): FacetRepository<Model.Unlocks>;
|
||||
}
|
||||
|
||||
export interface Transaction extends Repositories {
|
||||
commit(): Promise<void>;
|
||||
|
||||
rollback(): Promise<void>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import "dotenv/config";
|
||||
|
||||
import fs from "fs";
|
||||
import https from "https";
|
||||
import http from "http";
|
||||
|
|
@ -11,21 +12,24 @@ import chunithm from "./chunithm";
|
|||
import diva from "./diva";
|
||||
import idz from "./idz";
|
||||
import idzPing from "./idz/ping";
|
||||
import { openDataSource } from "./sql";
|
||||
import * as Swb from "./switchboard";
|
||||
|
||||
const db = openDataSource();
|
||||
|
||||
const tls = {
|
||||
cert: fs.readFileSync("pki/server.pem"),
|
||||
key: fs.readFileSync("pki/server.key"),
|
||||
};
|
||||
|
||||
net.createServer(aimedb).listen(Swb.PORT_AIMEDB, Swb.HOST_INT);
|
||||
net.createServer(aimedb(db)).listen(Swb.PORT_AIMEDB, Swb.HOST_INT);
|
||||
http.createServer(allnet).listen(Swb.PORT_ALLNET, Swb.HOST_INT);
|
||||
https.createServer(tls, billing).listen(Swb.PORT_BILLING, Swb.HOST_INT);
|
||||
|
||||
http.createServer(chunithm).listen(Swb.PORT_CHUNITHM, Swb.HOST_INT);
|
||||
http.createServer(diva).listen(Swb.PORT_DIVA, Swb.HOST_INT);
|
||||
|
||||
net.createServer(idz).listen(Swb.PORT_IDZ.USERDB.TCP, Swb.HOST_INT);
|
||||
net.createServer(idz(db)).listen(Swb.PORT_IDZ.USERDB.TCP, Swb.HOST_INT);
|
||||
idzPing(10001, Swb.HOST_INT); // ?? tbd
|
||||
idzPing(Swb.PORT_IDZ.MATCH.UDP_SEND, Swb.HOST_INT);
|
||||
idzPing(Swb.PORT_IDZ.ECHO1, Swb.HOST_INT);
|
||||
|
|
|
|||
11
src/model.ts
11
src/model.ts
|
|
@ -1 +1,12 @@
|
|||
import { randomBytes } from "crypto";
|
||||
|
||||
export type AimeId = number & { __aimeId: null };
|
||||
|
||||
/** Generate a random 32-bit ID for use in external protocol messages */
|
||||
export function generateExtId(): number {
|
||||
const buf = randomBytes(4);
|
||||
|
||||
buf[0] &= 0x7f; // Force number to be non-negative
|
||||
|
||||
return buf.readUInt32BE(0);
|
||||
}
|
||||
|
|
|
|||
19
src/sql/api.ts
Normal file
19
src/sql/api.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import * as sql from "sql-bricks-postgres";
|
||||
|
||||
export type Id<T> = bigint & { __id: T };
|
||||
|
||||
export interface Row {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface Transaction {
|
||||
modify(stmt: sql.Statement): Promise<void>;
|
||||
|
||||
fetchRow(stmt: sql.SelectStatement): Promise<Row | undefined>;
|
||||
|
||||
fetchRows(stmt: sql.SelectStatement): Promise<Row[]>;
|
||||
}
|
||||
|
||||
export interface DataSource {
|
||||
transaction<T>(callback: (txn: Transaction) => Promise<T>): Promise<T>;
|
||||
}
|
||||
3
src/sql/index.ts
Normal file
3
src/sql/index.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export * from "./api";
|
||||
export * from "./pg";
|
||||
export * from "./util";
|
||||
57
src/sql/pg.ts
Normal file
57
src/sql/pg.ts
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
import { Pool, PoolClient } from "pg";
|
||||
import * as sql from "sql-bricks-postgres";
|
||||
|
||||
import { DataSource, Row, Transaction } from "./api";
|
||||
|
||||
class PgTransaction implements Transaction {
|
||||
constructor(private readonly _conn: PoolClient) {}
|
||||
|
||||
async modify(stmt: sql.Statement): Promise<void> {
|
||||
await this._conn.query(stmt.toParams());
|
||||
}
|
||||
|
||||
async fetchRow(stmt: sql.SelectStatement): Promise<Row | undefined> {
|
||||
const { rows } = await this._conn.query(stmt.toParams());
|
||||
|
||||
return rows[0];
|
||||
}
|
||||
|
||||
async fetchRows(stmt: sql.SelectStatement): Promise<Row[]> {
|
||||
const { rows } = await this._conn.query(stmt.toParams());
|
||||
|
||||
return rows;
|
||||
}
|
||||
}
|
||||
|
||||
class PgDataSource implements DataSource {
|
||||
private readonly _pool: Pool;
|
||||
|
||||
constructor() {
|
||||
this._pool = new Pool();
|
||||
}
|
||||
|
||||
async transaction<T>(
|
||||
callback: (txn: Transaction) => Promise<T>
|
||||
): Promise<T> {
|
||||
const conn = await this._pool.connect();
|
||||
|
||||
await conn.query("begin");
|
||||
|
||||
try {
|
||||
const txn = new PgTransaction(conn);
|
||||
const result = await callback(txn);
|
||||
|
||||
await conn.query("commit");
|
||||
|
||||
return result;
|
||||
} catch (e) {
|
||||
await conn.query("rollback");
|
||||
|
||||
return Promise.reject(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function openDataSource(): DataSource {
|
||||
return new PgDataSource();
|
||||
}
|
||||
14
src/sql/util.ts
Normal file
14
src/sql/util.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { randomBytes } from "crypto";
|
||||
|
||||
export function generateId(): bigint {
|
||||
const buf = randomBytes(8);
|
||||
|
||||
buf[0] &= 0x7f; // Force number to be non-negative
|
||||
|
||||
// Let's not depend on Node v12 for the sake of 3 LoC just yet.
|
||||
|
||||
const hi = buf.readUInt32BE(0);
|
||||
const lo = buf.readUInt32BE(4);
|
||||
|
||||
return (BigInt(hi) << 32n) | BigInt(lo);
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user