Compare commits
@@ -0,0 +1,16 @@
|
||||
image: node:12-buster
|
||||
|
||||
cache:
|
||||
paths:
|
||||
- node_modules/
|
||||
|
||||
stages:
|
||||
- build
|
||||
|
||||
do_build:
|
||||
stage: build
|
||||
script:
|
||||
- npm install
|
||||
- npm run build
|
||||
- npm test
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
# CHANGELOG
|
||||
|
||||
## v012
|
||||
|
||||
* Chunithm: Fix large data loads (NeumPhis)
|
||||
* Chunithm: Add some support for older versions (NeumPhis)
|
||||
* Chunithm: Fix large data loads (Matt Bilker)
|
||||
* Chunithm: Enable all events (Matt Bilker)
|
||||
* Chunithm: Remove server maintenance period (Matt Bilker)
|
||||
* Chunithm: Fix data loss (esterTion)
|
||||
* Chunithm: Add some support for older versions (esterTion)
|
||||
* IDZ: Team-related fixes (Emi Midnight)
|
||||
* Add support for newer AIME card services (cxm)
|
||||
* Chunithm: Enable ticket system (Rob)
|
||||
* Chunithm: Implement Recent Rating feature (seika1)
|
||||
* Chunithm: Fix User Activity feature (seika1)
|
||||
|
||||
## v011
|
||||
|
||||
* IDZ: Fix loading of >10 cars in garage (Tau)
|
||||
|
||||
## v010
|
||||
|
||||
* Chunithm: Bug fix: Fix new database creation and repair affected databases. Again. (Tau)
|
||||
Switch to a different SQLite native code extension. C compiler is no longer required. (Tau)
|
||||
|
||||
## v009
|
||||
|
||||
* IDZ: Display team banners in attract loop rankings (Tau)
|
||||
* Chunithm: Bug fix: Fix new database creation and repair affected databases (Tau)
|
||||
|
||||
## v008
|
||||
|
||||
* Implement auras in IDZ (BemaniWitch)
|
||||
* Fix user ratings (NeumPhis)
|
||||
* Load and save course results (NeumPhis)
|
||||
* Load and save user duel list (Felix)
|
||||
|
||||
## v007
|
||||
|
||||
* Fix IDZ team creation (Tau)
|
||||
* Fix startup script working directory (Tau)
|
||||
|
||||
## v006
|
||||
|
||||
* Fix Chunithm HTTP POST size limit (Tau)
|
||||
|
||||
## v005
|
||||
|
||||
* Initial public release (Tau)
|
||||
* Chunithm v1.30.00 Amazon
|
||||
* Initial D Zero v1.21.00
|
||||
* Initial D Zero v1.31.00
|
||||
@@ -82,6 +82,21 @@ create table "cm_user_activity" (
|
||||
)
|
||||
);
|
||||
|
||||
create table "cm_user_charge" (
|
||||
"id" integer primary key not null,
|
||||
"profile_id" integer not null
|
||||
references "cm_user_data"("id")
|
||||
on delete cascade,
|
||||
"charge_id" integer not null,
|
||||
"stock" integer not null,
|
||||
"purchase_date" text not null,
|
||||
"valid_date" text not null,
|
||||
"param1" integer not null,
|
||||
"param2" integer not null,
|
||||
"param_date" text not null,
|
||||
constraint "cm_user_charge_uq" unique ("profile_id", "charge_id")
|
||||
);
|
||||
|
||||
create table "cm_user_course" (
|
||||
"id" integer primary key not null,
|
||||
"profile_id" integer not null
|
||||
@@ -342,3 +357,16 @@ create table "cm_user_playlog" (
|
||||
"place_name" text not null,
|
||||
"is_maimai" text not null
|
||||
);
|
||||
|
||||
create table "cm_user_recent_rating" (
|
||||
"id" integer primary key not null,
|
||||
"profile_id" integer not null
|
||||
references "cm_user_data"("id")
|
||||
on delete cascade,
|
||||
"sort_order" integer not null,
|
||||
"music_id" integer not null,
|
||||
"difficult_id" integer not null,
|
||||
"rom_version_code" integer not null,
|
||||
"score" integer not null,
|
||||
constraint "cm_user_recent_rating_uq" unique ("profile_id", "sort_order")
|
||||
);
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
create table if not exists "cm_user_charge" (
|
||||
"id" integer primary key not null,
|
||||
"profile_id" integer not null
|
||||
references "cm_user_data"("id")
|
||||
on delete cascade,
|
||||
"charge_id" integer not null,
|
||||
"stock" integer not null,
|
||||
"purchase_date" text not null,
|
||||
"valid_date" text not null,
|
||||
"param1" integer not null,
|
||||
"param2" integer not null,
|
||||
"param_date" text not null,
|
||||
constraint "cm_user_charge_uq" unique ("profile_id", "charge_id")
|
||||
);
|
||||
@@ -0,0 +1,31 @@
|
||||
create table if not exists "cm_user_recent_rating" (
|
||||
"id" integer primary key not null,
|
||||
"profile_id" integer not null
|
||||
references "cm_user_data"("id")
|
||||
on delete cascade,
|
||||
"sort_order" integer not null,
|
||||
"music_id" integer not null,
|
||||
"difficult_id" integer not null,
|
||||
"rom_version_code" integer not null,
|
||||
"score" integer not null,
|
||||
constraint "cm_user_recent_rating_uq" unique ("profile_id", "sort_order")
|
||||
);
|
||||
|
||||
-- Prepopulate this table by backfilling the most recent 30 scores per user.
|
||||
-- This isn't exactly correct since not every recent score should go in here, but it's probably close enough.
|
||||
INSERT INTO cm_user_recent_rating (profile_id, sort_order, music_id, difficult_id, rom_version_code, score)
|
||||
SELECT * FROM (
|
||||
SELECT
|
||||
profile_id,
|
||||
row_number()
|
||||
OVER (PARTITION BY profile_id ORDER BY user_play_date DESC) AS sort_order,
|
||||
music_id,
|
||||
level AS difficult_id,
|
||||
'1030000' AS rom_version_code,
|
||||
score
|
||||
FROM cm_user_playlog
|
||||
WHERE
|
||||
difficult_id < 4 -- skip world's end
|
||||
ORDER BY profile_id ASC, user_play_date DESC
|
||||
)
|
||||
WHERE sort_order <= 30;
|
||||
@@ -105,7 +105,7 @@ test("decode lookup", () => {
|
||||
|
||||
const obj = decode(req);
|
||||
|
||||
expect(obj.type).toBe("lookup");
|
||||
expect(obj.type).toBe("lookup2");
|
||||
expect(obj.gameId).toBe("SDBT");
|
||||
expect(obj.keychipId).toBe("A69E01A9999");
|
||||
expect(obj.luid).toBe("01036495850523030676");
|
||||
|
||||
@@ -31,6 +31,15 @@ function readFeliCaLookupRequest(msg: Buffer): Request.FeliCaLookupRequest {
|
||||
};
|
||||
}
|
||||
|
||||
function readFeliCaLookupRequest2(msg: Buffer): Request.FeliCaLookup2Request {
|
||||
return {
|
||||
...begin(msg),
|
||||
type: "felica_lookup2",
|
||||
idm: msg.slice(0x0030, 0x0038).toString("hex"),
|
||||
pmm: msg.slice(0x0038, 0x0040).toString("hex"),
|
||||
};
|
||||
}
|
||||
|
||||
function readLogRequest(msg: Buffer): Request.LogRequest {
|
||||
// idk what any of this stuff means yet
|
||||
// field20 and field28 appear to be an aime id but that is all.
|
||||
@@ -98,6 +107,7 @@ readers.set(0x0009, readLogRequest);
|
||||
readers.set(0x000b, readCampaignRequest);
|
||||
readers.set(0x000d, readRegisterRequest);
|
||||
readers.set(0x000f, readLookupRequest2);
|
||||
readers.set(0x0011, readFeliCaLookupRequest2);
|
||||
readers.set(0x0064, readHelloRequest);
|
||||
readers.set(0x0066, readGoodbyeRequest);
|
||||
|
||||
|
||||
+13
-1
@@ -14,7 +14,7 @@ registerLevels.set("segaid", 2);
|
||||
function begin(length: number) {
|
||||
const buf = Buffer.alloc(length);
|
||||
|
||||
buf.writeUInt16LE(0xa13e, 0x0000); // Magic?
|
||||
buf.writeUInt16LE(0xa13e, 0x0000); // Magic: aime
|
||||
buf.writeUInt16LE(0x3087, 0x0002); // ???
|
||||
buf.writeUInt16LE(length, 0x0006);
|
||||
|
||||
@@ -43,6 +43,18 @@ export class Encoder extends Transform {
|
||||
|
||||
break;
|
||||
|
||||
case "felica_lookup2":
|
||||
buf = begin(0x0140);
|
||||
buf.writeUInt16LE(0x0012, 0x0004); // cmd code
|
||||
buf.writeUInt16LE(msg.status, 0x0008);
|
||||
buf.writeInt32LE(msg.aimeId || -1, 0x0020);
|
||||
buf.writeUInt32LE(0xffffffff, 0x0024); // FF
|
||||
buf.writeUInt32LE(0xffffffff, 0x0028); // FF
|
||||
buf.write(msg.accessCode, 0x002c, "hex");
|
||||
buf.writeUInt16LE(0x0001, 0x0037); // 00 01
|
||||
|
||||
break;
|
||||
|
||||
case "hello":
|
||||
buf = begin(0x0020);
|
||||
buf.writeUInt16LE(0x0065, 0x0004); // cmd code
|
||||
|
||||
@@ -46,6 +46,28 @@ function feliCaLookup(
|
||||
return { type: req.type, status: 1, accessCode };
|
||||
}
|
||||
|
||||
async function feliCaLookup2(
|
||||
rep: Repositories,
|
||||
req: Req.FeliCaLookup2Request,
|
||||
now: Date
|
||||
): Promise<Res.FeliCaLookup2Response> {
|
||||
debug("FeliCa access code lookup");
|
||||
|
||||
const num = BigInt("0x" + req.idm);
|
||||
let accessCode = num.toString();
|
||||
|
||||
while (accessCode.length < 20) {
|
||||
accessCode = "0" + accessCode;
|
||||
}
|
||||
|
||||
return {
|
||||
type: req.type,
|
||||
status: 1,
|
||||
accessCode,
|
||||
aimeId: await rep.cards().lookup(accessCode, now),
|
||||
};
|
||||
}
|
||||
|
||||
async function lookup(
|
||||
rep: Repositories,
|
||||
req: Req.LookupRequest,
|
||||
@@ -115,6 +137,9 @@ export async function dispatch(
|
||||
case "felica_lookup":
|
||||
return feliCaLookup(rep, req, now);
|
||||
|
||||
case "felica_lookup2":
|
||||
return feliCaLookup2(rep, req, now);
|
||||
|
||||
case "lookup":
|
||||
return lookup(rep, req, now);
|
||||
|
||||
|
||||
@@ -9,6 +9,12 @@ export interface FeliCaLookupRequest extends AimeRequestBase {
|
||||
pmm: string;
|
||||
}
|
||||
|
||||
export interface FeliCaLookup2Request extends AimeRequestBase {
|
||||
type: "felica_lookup2";
|
||||
idm: string;
|
||||
pmm: string;
|
||||
}
|
||||
|
||||
export interface RegisterRequest extends AimeRequestBase {
|
||||
type: "register";
|
||||
luid: string;
|
||||
@@ -50,6 +56,7 @@ export interface GoodbyeRequest {
|
||||
|
||||
export type AimeRequest =
|
||||
| FeliCaLookupRequest
|
||||
| FeliCaLookup2Request
|
||||
| CampaignRequest
|
||||
| GoodbyeRequest
|
||||
| HelloRequest
|
||||
|
||||
@@ -11,6 +11,12 @@ export interface FeliCaLookupResponse extends AimeResponseBase {
|
||||
accessCode: string;
|
||||
}
|
||||
|
||||
export interface FeliCaLookup2Response extends AimeResponseBase {
|
||||
type: "felica_lookup2";
|
||||
accessCode: string;
|
||||
aimeId?: AimeId;
|
||||
}
|
||||
|
||||
export interface CampaignResponse extends AimeResponseBase {
|
||||
type: "campaign";
|
||||
}
|
||||
@@ -44,6 +50,7 @@ export interface RegisterResponse extends AimeResponseBase {
|
||||
|
||||
export type AimeResponse =
|
||||
| FeliCaLookupResponse
|
||||
| FeliCaLookup2Response
|
||||
| CampaignResponse
|
||||
| HelloResponse
|
||||
| LogResponse
|
||||
|
||||
+2
-2
@@ -19,8 +19,8 @@ class CardRepositoryImpl implements CardRepository {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const id = row.id;
|
||||
const extId = row.ext_id;
|
||||
const id = row.id!;
|
||||
const extId = row.ext_id!;
|
||||
|
||||
const touchSql = sql
|
||||
.update("aime_player")
|
||||
|
||||
+1
-1
@@ -111,7 +111,7 @@ export default async function checkdb(db: DataSource): Promise<void> {
|
||||
const row = await db.transaction(txn => txn.fetchRow(stmt));
|
||||
|
||||
if (row !== undefined) {
|
||||
maybe = parseInt(row.schemaver);
|
||||
maybe = parseInt(row.schemaver!);
|
||||
}
|
||||
} catch (e) {
|
||||
return db.transaction(initdb);
|
||||
|
||||
@@ -1,13 +1,30 @@
|
||||
import { GameChargeJson } from "../proto/gameCharge";
|
||||
import { Repositories } from "../repo";
|
||||
import { GetGameChargeRequest } from "../request/getGameCharge";
|
||||
import { GetGameChargeResponse } from "../response/getGameCharge";
|
||||
import { CHARGE_IDS } from "../static/charge";
|
||||
|
||||
export default async function getGameCharge(
|
||||
rep: Repositories,
|
||||
req: GetGameChargeRequest
|
||||
): Promise<GetGameChargeResponse> {
|
||||
const gameChargeList: GameChargeJson[] = [];
|
||||
|
||||
for (const [i, charge] of CHARGE_IDS.entries()) {
|
||||
gameChargeList.push({
|
||||
chargeId: charge.id.toString(),
|
||||
orderId: (i+1).toString(),
|
||||
price: charge.price.toString(),
|
||||
salePrice: charge.salePrice.toString(),
|
||||
startDate: "2017-12-05 07:00:00.0",
|
||||
endDate: "2029-12-31 23:59:59.0",
|
||||
saleStartDate: "2017-12-05 07:00:00.0",
|
||||
saleEndDate: "2030-12-31 23:59:59.0"
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
length: "0",
|
||||
gameChargeList: [],
|
||||
length: gameChargeList.length.toString(),
|
||||
gameChargeList,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,14 +1,27 @@
|
||||
import { GameEventJson } from "../proto/gameEvent";
|
||||
import { Repositories } from "../repo";
|
||||
import { GetGameEventRequest } from "../request/getGameEvent";
|
||||
import { GetGameEventResponse } from "../response/getGameEvent";
|
||||
import { EVENT_IDS } from "../static/event";
|
||||
|
||||
export default async function getGameEvent(
|
||||
rep: Repositories,
|
||||
req: GetGameEventRequest
|
||||
): Promise<GetGameEventResponse> {
|
||||
const gameEventList: GameEventJson[] = [];
|
||||
|
||||
for (const id of EVENT_IDS) {
|
||||
gameEventList.push({
|
||||
type: req.type,
|
||||
id: id.toString(),
|
||||
startDate: "2017-12-05 07:00:00.0",
|
||||
endDate: "2099-12-31 00:00:00.0",
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
type: req.type,
|
||||
length: "0",
|
||||
gameEventList: [],
|
||||
length: gameEventList.length.toString(),
|
||||
gameEventList,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { writeDate } from "../proto/base";
|
||||
import { Repositories } from "../repo";
|
||||
import { GetGameSettingRequest } from "../request/getGameSetting";
|
||||
import { GetGameSettingResponse } from "../response/getGameSetting";
|
||||
@@ -6,19 +7,25 @@ export default async function getGameSetting(
|
||||
rep: Repositories,
|
||||
req: GetGameSettingRequest
|
||||
): Promise<GetGameSettingResponse> {
|
||||
const rebootStartTime = new Date();
|
||||
rebootStartTime.setHours(rebootStartTime.getHours() - 3);
|
||||
|
||||
const rebootEndTime = new Date();
|
||||
rebootEndTime.setHours(rebootEndTime.getHours() - 2);
|
||||
|
||||
return {
|
||||
gameSetting: {
|
||||
dataVersion: "1",
|
||||
isMaintenance: "false",
|
||||
requestInterval: "10",
|
||||
rebootStartTime: "0",
|
||||
rebootEndTime: "0",
|
||||
rebootStartTime: writeDate(rebootStartTime),
|
||||
rebootEndTime: writeDate(rebootEndTime),
|
||||
isBackgroundDistribute: "false",
|
||||
maxCountCharacter: "999",
|
||||
maxCountItem: "999",
|
||||
maxCountMusic: "999",
|
||||
maxCountCharacter: "300",
|
||||
maxCountItem: "300",
|
||||
maxCountMusic: "100",
|
||||
},
|
||||
isDumpUpload: "false",
|
||||
isAou: "false",
|
||||
isAou: "true",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { readAimeId } from "../proto/base";
|
||||
import { writeUserCharge } from "../proto/userCharge";
|
||||
import { Repositories } from "../repo";
|
||||
import { GetUserChargeRequest } from "../request/getUserCharge";
|
||||
import { GetUserChargeResponse } from "../response/getUserCharge";
|
||||
@@ -6,9 +8,14 @@ export default async function getUserCharge(
|
||||
rep: Repositories,
|
||||
req: GetUserChargeRequest
|
||||
): Promise<GetUserChargeResponse> {
|
||||
const aimeId = readAimeId(req.userId);
|
||||
|
||||
const profileId = await rep.userData().lookup(aimeId);
|
||||
const items = await rep.userCharge().load(profileId);
|
||||
|
||||
return {
|
||||
userId: req.userId,
|
||||
length: "0",
|
||||
userChargeList: [],
|
||||
length: items.length.toString(),
|
||||
userChargeList: items.map(writeUserCharge),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ export default async function getUserCourse(
|
||||
.userCourse()
|
||||
.load(profileId, { limit: maxCount, offset: nextIndex });
|
||||
|
||||
|
||||
return {
|
||||
userId: req.userId,
|
||||
length: items.length.toString(),
|
||||
|
||||
@@ -47,7 +47,7 @@ export default async function getUserItem(
|
||||
|
||||
// Pack the next pagination cookie into bigint compound form
|
||||
|
||||
const xout = itemKindN * itemKindMul + BigInt(maxCount + items.length);
|
||||
const xout = itemKindN * itemKindMul + nextIndexN + BigInt(items.length);
|
||||
|
||||
// Done:
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Repositories } from "../repo";
|
||||
import { GetUserRecentRatingRequest } from "../request/getUserRecentRating";
|
||||
import { GetUserRecentRatingResponse } from "../response/getUserRecentRating";
|
||||
import { readAimeId } from "../proto/base";
|
||||
import { writeUserRecentRatingFromLog } from "../proto/userRecentRating";
|
||||
import { writeUserRecentRating } from "../proto/userRecentRating";
|
||||
|
||||
export default async function getUserRecentRating(
|
||||
rep: Repositories,
|
||||
@@ -12,11 +12,11 @@ export default async function getUserRecentRating(
|
||||
|
||||
const profileId = await rep.userData().lookup(aimeId);
|
||||
// Return recent 30 plays to calculate rating
|
||||
const items = await rep.userPlaylog().loadLatest(profileId, 30);
|
||||
const items = await rep.userRecentRating().load(profileId);
|
||||
|
||||
return {
|
||||
userId: req.userId,
|
||||
length: items.length.toString(),
|
||||
userRecentRatingList: items.map(writeUserRecentRatingFromLog),
|
||||
userRecentRatingList: items.map(writeUserRecentRating),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ import upsertClientError from "./upsertClientError";
|
||||
import upsertClientSetting from "./upsertClientSetting";
|
||||
import upsertClientTestmode from "./upsertClientTestmode";
|
||||
import upsertUserAll from "./upsertUserAll";
|
||||
import upsertUserChargelogApi from "./upsertUserChargelogApi";
|
||||
import createSqlWrapper from "../sql";
|
||||
import { DataSource } from "../../sql";
|
||||
|
||||
@@ -104,6 +105,7 @@ export default function chunithm(db: DataSource) {
|
||||
wrapper.rpc("/GetUserOptionApi", getUserOption);
|
||||
wrapper.rpc("/GetUserOptionExApi", getUserOptionEx);
|
||||
wrapper.rpc("/GetUserPreviewApi", getUserPreview);
|
||||
wrapper.rpc("/GetUserRecentPlayerApi", getUserRecentRating);
|
||||
wrapper.rpc("/GetUserRecentRatingApi", getUserRecentRating);
|
||||
wrapper.rpc("/GetUserRegionApi", getUserRegion);
|
||||
wrapper.rpc("/UpsertClientBookkeepingApi", upsertClientBookkeeping);
|
||||
@@ -112,6 +114,7 @@ export default function chunithm(db: DataSource) {
|
||||
wrapper.rpc("/UpsertClientSettingApi", upsertClientSetting);
|
||||
wrapper.rpc("/UpsertClientTestmodeApi", upsertClientTestmode);
|
||||
wrapper.rpc("/UpsertUserAllApi", upsertUserAll);
|
||||
wrapper.rpc("/UpsertUserChargelogApi", upsertUserChargelogApi)
|
||||
|
||||
const app = express();
|
||||
|
||||
|
||||
@@ -13,6 +13,8 @@ import { readUserActivity } from "../proto/userActivity";
|
||||
import { readUserDataEx } from "../proto/userDataEx";
|
||||
import { readUserDuelList } from "../proto/userDuelList";
|
||||
import { readUserPlaylog } from "../proto/userPlaylog";
|
||||
import { readUserCharge } from "../proto/userCharge";
|
||||
import { readUserRecentRating } from "../proto/userRecentRating";
|
||||
import { readUserCourse } from "../proto/userCourse";
|
||||
|
||||
// It shouldn't need to be said really, but seeing as this message (A) requires
|
||||
@@ -67,6 +69,10 @@ export default async function upsertUserAll(
|
||||
await rep.userPlaylog().save(profileId, readUserPlaylog(item));
|
||||
}
|
||||
|
||||
for (const item of payload.userChargeList || []) {
|
||||
await rep.userCharge().save(profileId, readUserCharge(item));
|
||||
}
|
||||
|
||||
for (const item of payload.userCourseList || []) {
|
||||
await rep.userCourse().save(profileId, readUserCourse(item));
|
||||
}
|
||||
@@ -79,5 +85,12 @@ export default async function upsertUserAll(
|
||||
await rep.userDuelList().save(profileId, readUserDuelList(item));
|
||||
}
|
||||
|
||||
await rep
|
||||
.userRecentRating()
|
||||
.save(
|
||||
profileId,
|
||||
(payload.userRecentRatingList || []).map(readUserRecentRating)
|
||||
);
|
||||
|
||||
return { returnCode: "1" };
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Repositories } from "../repo";
|
||||
import { UpsertUserChargelogApiRequest } from "../request/upsertUserChargelogApi";
|
||||
import { UpsertUserChargelogApiResponse } from "../response/upsertUserChargelogApi";
|
||||
|
||||
export default async function UpsertUserChargelogApi(
|
||||
rep: Repositories,
|
||||
req: UpsertUserChargelogApiRequest
|
||||
): Promise<UpsertUserChargelogApiResponse> {
|
||||
return {
|
||||
returnCode: "1",
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
export interface UserChargeItem {
|
||||
chargeId: number;
|
||||
stock: number;
|
||||
purchaseDate: Date;
|
||||
validDate: Date;
|
||||
param1: number;
|
||||
param2: number;
|
||||
paramDate: Date;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { Crush, readDate, writeObject } from "./base";
|
||||
import { UserChargeItem } from "../model/userCharge";
|
||||
|
||||
export type UserChargeJson = Crush<UserChargeItem>;
|
||||
|
||||
export function readUserCharge(json: UserChargeJson): UserChargeItem {
|
||||
return {
|
||||
chargeId: parseInt(json.chargeId),
|
||||
stock: parseInt(json.stock),
|
||||
purchaseDate: readDate(json.purchaseDate),
|
||||
validDate: readDate(json.validDate),
|
||||
param1: parseInt(json.param1),
|
||||
param2: parseInt(json.param2),
|
||||
paramDate: readDate(json.paramDate),
|
||||
};
|
||||
}
|
||||
|
||||
export function writeUserCharge(obj: UserChargeItem): UserChargeJson {
|
||||
return writeObject(obj);
|
||||
}
|
||||
@@ -20,15 +20,3 @@ export function writeUserRecentRating(
|
||||
): UserRecentRatingJson {
|
||||
return writeObject(obj);
|
||||
}
|
||||
|
||||
export function writeUserRecentRatingFromLog(
|
||||
obj: UserPlaylogItem
|
||||
): UserRecentRatingJson {
|
||||
return {
|
||||
musicId: obj.musicId.toString(),
|
||||
difficultId: obj.level.toString(),
|
||||
// game version not saved in play log, just return a fixed version now
|
||||
romVersionCode: "1030000",
|
||||
score: obj.score.toString(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { UserCourseRepository } from "./userCourse";
|
||||
|
||||
export { Page } from "./_defs";
|
||||
|
||||
import { UserActivityRepository } from "./userActivity";
|
||||
import { UserCharacterRepository } from "./userCharacter";
|
||||
import { UserCourseRepository } from "./userCourse";
|
||||
import { UserDataRepository } from "./userData";
|
||||
import { UserDataExRepository } from "./userDataEx";
|
||||
import { UserDuelListRepository } from "./userDuelList";
|
||||
@@ -13,12 +12,16 @@ import { UserItemRepository } from "./userItem";
|
||||
import { UserMapRepository } from "./userMap";
|
||||
import { UserMusicRepository } from "./userMusic";
|
||||
import { UserPlaylogRepository } from "./userPlaylog";
|
||||
import { UserChargeRepository } from "./userCharge";
|
||||
import { UserRecentRatingRepository } from "./userRecentRating";
|
||||
|
||||
export interface Repositories {
|
||||
userActivity(): UserActivityRepository;
|
||||
|
||||
userCharacter(): UserCharacterRepository;
|
||||
|
||||
userCourse(): UserCourseRepository;
|
||||
|
||||
userData(): UserDataRepository;
|
||||
|
||||
userDataEx(): UserDataExRepository;
|
||||
@@ -37,5 +40,9 @@ export interface Repositories {
|
||||
|
||||
userPlaylog(): UserPlaylogRepository;
|
||||
|
||||
userCharge(): UserChargeRepository;
|
||||
|
||||
userCourse(): UserCourseRepository;
|
||||
|
||||
userRecentRating(): UserRecentRatingRepository;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
import { UserChargeItem } from "../model/userCharge";
|
||||
import { RepositoryN } from "./_defs";
|
||||
|
||||
export type UserChargeRepository = RepositoryN<UserChargeItem>;
|
||||
@@ -0,0 +1,12 @@
|
||||
import { UserDataItem } from "../model/userData";
|
||||
import { UserRecentRatingItem } from "../model/userRecentRating";
|
||||
import { Id } from "../../model";
|
||||
|
||||
export interface UserRecentRatingRepository {
|
||||
load(profileId: Id<UserDataItem>): Promise<UserRecentRatingItem[]>;
|
||||
|
||||
save(
|
||||
profileId: Id<UserDataItem>,
|
||||
objs: UserRecentRatingItem[]
|
||||
): Promise<void>;
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import { UserMusicDetailJson } from "../proto/userMusic";
|
||||
import { UserActivityJson } from "../proto/userActivity";
|
||||
import { UserRecentRatingJson } from "../proto/userRecentRating";
|
||||
import { UserPlaylogJson } from "../proto/userPlaylog";
|
||||
import { UserChargeJson } from "../proto/userCharge";
|
||||
import { UserCourseJson } from "../proto/userCourse";
|
||||
import { UserDataExJson } from "../proto/userDataEx";
|
||||
import { UserDuelListJson } from "../proto/userDuelList";
|
||||
@@ -27,6 +28,7 @@ export interface UpsertUserAllRequest {
|
||||
userActivityList?: UserActivityJson[];
|
||||
userRecentRatingList?: UserRecentRatingJson[];
|
||||
userPlaylogList?: UserPlaylogJson[];
|
||||
userChargeList?: UserChargeJson[];
|
||||
userCourseList?: UserCourseJson[];
|
||||
userDataEx?: UserDataExJson[];
|
||||
userDuelList?: UserDuelListJson[];
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
export interface UpsertUserChargelogApiRequest {
|
||||
userId: string;
|
||||
|
||||
userChargelog: {
|
||||
chargeId: string,
|
||||
price: string,
|
||||
purchaseDate: Date,
|
||||
playCount: string,
|
||||
playerRating: string,
|
||||
placeId: string,
|
||||
regionId: string,
|
||||
clientId: string
|
||||
};
|
||||
|
||||
userCharge: {
|
||||
chargeId: string,
|
||||
stock: string,
|
||||
purchaseDate: Date,
|
||||
validDate: Date,
|
||||
param1: string,
|
||||
param2: string,
|
||||
paramDate: Date
|
||||
};
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import { UserChargeJson } from "../proto/userCharge";
|
||||
|
||||
export interface GetUserChargeResponse {
|
||||
/** Integer, AiMe ID */
|
||||
userId: string;
|
||||
@@ -5,6 +7,5 @@ export interface GetUserChargeResponse {
|
||||
/** Integer, number of results returned */
|
||||
length: string;
|
||||
|
||||
/** TBD */
|
||||
userChargeList: [];
|
||||
userChargeList: UserChargeJson[];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export interface UpsertUserChargelogApiResponse {
|
||||
returnCode: string;
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
import { SqlUserActivityRepository } from "./userActivity";
|
||||
import { SqlUserCharacterRepository } from "./userCharacter";
|
||||
import { SqlUserChargeRepository } from "./userCharge";
|
||||
import { SqlUserCourseRepository } from "./userCourse";
|
||||
import { SqlUserDataRepository } from "./userData";
|
||||
import { SqlUserDataExRepository } from "./userDataEx";
|
||||
import { SqlUserDuelListRepository } from "./userDuelList";
|
||||
@@ -9,9 +11,12 @@ import { SqlUserItemRepository } from "./userItem";
|
||||
import { SqlUserMapRepository } from "./userMap";
|
||||
import { SqlUserMusicRepository } from "./userMusic";
|
||||
import { SqlUserPlaylogRepository } from "./userPlaylog";
|
||||
import { SqlUserRecentRatingRepository } from "./userRecentRating";
|
||||
import { Repositories } from "../repo";
|
||||
import { UserActivityRepository } from "../repo/userActivity";
|
||||
import { UserCharacterRepository } from "../repo/userCharacter";
|
||||
import { UserChargeRepository } from "../repo/userCharge";
|
||||
import { UserCourseRepository } from "../repo/userCourse";
|
||||
import { UserDataRepository } from "../repo/userData";
|
||||
import { UserDataExRepository } from "../repo/userDataEx";
|
||||
import { UserDuelListRepository } from "../repo/userDuelList";
|
||||
@@ -21,9 +26,8 @@ import { UserItemRepository } from "../repo/userItem";
|
||||
import { UserMapRepository } from "../repo/userMap";
|
||||
import { UserMusicRepository } from "../repo/userMusic";
|
||||
import { UserPlaylogRepository } from "../repo/userPlaylog";
|
||||
import { UserRecentRatingRepository } from "../repo/userRecentRating";
|
||||
import { Transaction } from "../../sql";
|
||||
import { UserCourseRepository } from "../repo/userCourse";
|
||||
import { SqlUserCourseRepository } from "./userCourse";
|
||||
|
||||
export class SqlRepositories implements Repositories {
|
||||
constructor(private readonly _txn: Transaction) {}
|
||||
@@ -36,6 +40,14 @@ export class SqlRepositories implements Repositories {
|
||||
return new SqlUserCharacterRepository(this._txn);
|
||||
}
|
||||
|
||||
userCharge(): UserChargeRepository {
|
||||
return new SqlUserChargeRepository(this._txn);
|
||||
}
|
||||
|
||||
userCourse(): UserCourseRepository {
|
||||
return new SqlUserCourseRepository(this._txn);
|
||||
}
|
||||
|
||||
userData(): UserDataRepository {
|
||||
return new SqlUserDataRepository(this._txn);
|
||||
}
|
||||
@@ -72,7 +84,7 @@ export class SqlRepositories implements Repositories {
|
||||
return new SqlUserPlaylogRepository(this._txn);
|
||||
}
|
||||
|
||||
userCourse(): UserCourseRepository {
|
||||
return new SqlUserCourseRepository(this._txn);
|
||||
userRecentRating(): UserRecentRatingRepository {
|
||||
return new SqlUserRecentRatingRepository(this._txn);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,8 @@ export class SqlUserActivityRepository implements UserActivityRepository {
|
||||
.select("*")
|
||||
.from("cm_user_activity")
|
||||
.where("profile_id", profileId)
|
||||
.where("kind", kind);
|
||||
.where("kind", kind)
|
||||
.orderBy("sort_number DESC");
|
||||
|
||||
const rows = await this._txn.fetchRows(stmt);
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import sql from "sql-bricks-postgres";
|
||||
import { createSqlMapper, T } from "../../sql/util";
|
||||
import { UserChargeRepository } from "../repo/userCharge";
|
||||
import { Id } from "../../model";
|
||||
import { Page } from "../repo";
|
||||
import { UserDataItem } from "../model/userData";
|
||||
import { UserChargeItem } from "../model/userCharge";
|
||||
import { Transaction } from "../../sql";
|
||||
|
||||
const { readRow, writeRow, colNames } = createSqlMapper({
|
||||
chargeId: T.number,
|
||||
stock: T.number,
|
||||
purchaseDate: T.Date,
|
||||
validDate: T.Date,
|
||||
param1: T.number,
|
||||
param2: T.number,
|
||||
paramDate: T.Date,
|
||||
});
|
||||
|
||||
export class SqlUserChargeRepository implements UserChargeRepository {
|
||||
constructor(private readonly _txn: Transaction) {}
|
||||
|
||||
async load(profileId: Id<UserDataItem>): Promise<UserChargeItem[]> {
|
||||
const stmt = sql
|
||||
.select("*")
|
||||
.from("cm_user_charge")
|
||||
.where("profile_id", profileId);
|
||||
|
||||
const rows = await this._txn.fetchRows(stmt);
|
||||
|
||||
return rows.map(readRow);
|
||||
}
|
||||
|
||||
save(profileId: Id<UserDataItem>, obj: UserChargeItem): Promise<void> {
|
||||
const stmt = sql
|
||||
.insert("cm_user_charge", {
|
||||
id: this._txn.generateId(),
|
||||
profile_id: profileId,
|
||||
...writeRow(obj),
|
||||
})
|
||||
.onConflict("profile_id", "charge_id")
|
||||
.doUpdate(colNames);
|
||||
|
||||
return this._txn.modify(stmt);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -37,7 +37,10 @@ export class SqlUserCourseRepository implements UserCourseRepository {
|
||||
.from("cm_user_course")
|
||||
.where("profile_id", profileId);
|
||||
|
||||
if (page) {
|
||||
/**
|
||||
* UserCourse has no paging before CHUNITHM Amazon
|
||||
*/
|
||||
if (page && !isNaN(page.limit)) {
|
||||
stmt.limit(page.limit).offset(page.offset);
|
||||
}
|
||||
|
||||
|
||||
@@ -56,7 +56,47 @@ export class SqlUserDataExRepository implements UserDataExRepository {
|
||||
const row = await this._txn.fetchRow(stmt);
|
||||
|
||||
if (row === undefined) {
|
||||
throw new Error("UserDataEx record not found");
|
||||
/**
|
||||
* When upgrading from CHUNITHM Star Plus & earlier
|
||||
* to Amazon, there is no UserDataEx, so we return
|
||||
* a "default" row.
|
||||
*/
|
||||
return {
|
||||
compatibleCmVersion: "",
|
||||
medal: 0,
|
||||
mapIconId: 0,
|
||||
voiceId: 0,
|
||||
ext1: 0,
|
||||
ext2: 0,
|
||||
ext3: 0,
|
||||
ext4: 0,
|
||||
ext5: 0,
|
||||
ext6: 0,
|
||||
ext7: 0,
|
||||
ext8: 0,
|
||||
ext9: 0,
|
||||
ext10: 0,
|
||||
ext11: 0,
|
||||
ext12: 0,
|
||||
ext13: 0,
|
||||
ext14: 0,
|
||||
ext15: 0,
|
||||
ext16: 0,
|
||||
ext17: 0,
|
||||
ext18: 0,
|
||||
ext19: 0,
|
||||
ext20: 0,
|
||||
extStr1: "",
|
||||
extStr2: "",
|
||||
extStr3: "",
|
||||
extStr4: "",
|
||||
extStr5: "",
|
||||
extLong1: 0n,
|
||||
extLong2: 0n,
|
||||
extLong3: 0n,
|
||||
extLong4: 0n,
|
||||
extLong5: 0n,
|
||||
};
|
||||
}
|
||||
|
||||
return readRow(row);
|
||||
|
||||
@@ -34,14 +34,22 @@ export class SqlUserMusicRepository implements UserMusicRepository {
|
||||
profileId: Id<UserDataItem>,
|
||||
page?: Page
|
||||
): Promise<UserMusicDetailItem[]> {
|
||||
const preStmt = sql
|
||||
.select("DISTINCT(music_id)")
|
||||
.from("cm_user_music")
|
||||
.where("profile_id", profileId)
|
||||
.orderBy("music_id");
|
||||
if (page) {
|
||||
preStmt.limit(page.limit).offset(page.offset);
|
||||
}
|
||||
const preRows = await this._txn.fetchRows(preStmt);
|
||||
const musicIds = preRows.map(r => r.music_id);
|
||||
|
||||
const stmt = sql
|
||||
.select("*")
|
||||
.from("cm_user_music")
|
||||
.where("profile_id", profileId);
|
||||
|
||||
if (page) {
|
||||
stmt.limit(page.limit).offset(page.offset);
|
||||
}
|
||||
.where("profile_id", profileId)
|
||||
.and(sql.in('music_id', musicIds));
|
||||
|
||||
const rows = await this._txn.fetchRows(stmt);
|
||||
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import sql from "sql-bricks-postgres";
|
||||
|
||||
import { Id } from "../../model";
|
||||
import { UserDataItem } from "../model/userData";
|
||||
import { UserRecentRatingItem } from "../model/userRecentRating";
|
||||
import { UserRecentRatingRepository } from "../repo/userRecentRating";
|
||||
import { Transaction } from "../../sql";
|
||||
import { T, createSqlMapper } from "../../sql/util";
|
||||
|
||||
const { readRow, writeRow, colNames } = createSqlMapper({
|
||||
musicId: T.number,
|
||||
difficultId: T.number,
|
||||
romVersionCode: T.number,
|
||||
score: T.number,
|
||||
});
|
||||
|
||||
export class SqlUserRecentRatingRepository
|
||||
implements UserRecentRatingRepository {
|
||||
constructor(private readonly _txn: Transaction) {}
|
||||
|
||||
async load(profileId: Id<UserDataItem>): Promise<UserRecentRatingItem[]> {
|
||||
const stmt = sql
|
||||
.select("music_id", "difficult_id", "rom_version_code", "score")
|
||||
.from("cm_user_recent_rating")
|
||||
.where("profile_id", profileId)
|
||||
.order("sort_order ASC");
|
||||
|
||||
const rows = await this._txn.fetchRows(stmt);
|
||||
|
||||
return rows.map(readRow);
|
||||
}
|
||||
|
||||
save(
|
||||
profileId: Id<UserDataItem>,
|
||||
objs: UserRecentRatingItem[]
|
||||
): Promise<void> {
|
||||
// Don't do anything if there's nothing to save,
|
||||
// since trying to execute an empty insert statement fails.
|
||||
if (objs.length === 0) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const stmt = sql
|
||||
.insert(
|
||||
"cm_user_recent_rating",
|
||||
objs.map((obj, idx) => {
|
||||
return {
|
||||
id: this._txn.generateId(),
|
||||
profile_id: profileId,
|
||||
sort_order: idx + 1,
|
||||
...writeRow(obj),
|
||||
};
|
||||
})
|
||||
)
|
||||
.onConflict("profile_id", "sort_order")
|
||||
.doUpdate(colNames);
|
||||
|
||||
return this._txn.modify(stmt);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
export const CHARGE_IDS = Object.freeze([
|
||||
{
|
||||
id: 2310, //World's End ticket x5
|
||||
price: 1,
|
||||
salePrice: 1,
|
||||
},
|
||||
{
|
||||
id: 2060, //4x bonus ticket
|
||||
price: 1,
|
||||
salePrice: 1,
|
||||
},
|
||||
{
|
||||
id: 2230, //6 songs premium ticket
|
||||
price: 2,
|
||||
salePrice: 2,
|
||||
},
|
||||
]);
|
||||
@@ -0,0 +1,854 @@
|
||||
export const EVENT_IDS = Object.freeze([
|
||||
// A000
|
||||
0,
|
||||
1506,
|
||||
1604,
|
||||
1702,
|
||||
1953,
|
||||
2206,
|
||||
2353,
|
||||
2553,
|
||||
2804,
|
||||
2999,
|
||||
3000,
|
||||
3001,
|
||||
3002,
|
||||
3003,
|
||||
3006,
|
||||
3007,
|
||||
3008,
|
||||
3009,
|
||||
3010,
|
||||
3011,
|
||||
3012,
|
||||
3013,
|
||||
3014,
|
||||
3015,
|
||||
3016,
|
||||
3017,
|
||||
3018,
|
||||
3019,
|
||||
3020,
|
||||
3021,
|
||||
3026,
|
||||
3027,
|
||||
20003,
|
||||
|
||||
// A014
|
||||
3004,
|
||||
3005,
|
||||
3022,
|
||||
3023,
|
||||
3024,
|
||||
3025,
|
||||
3028,
|
||||
3029,
|
||||
3030,
|
||||
3031,
|
||||
3032,
|
||||
3100,
|
||||
3101,
|
||||
3102,
|
||||
3103,
|
||||
3104,
|
||||
3150,
|
||||
3151,
|
||||
3152,
|
||||
3153,
|
||||
3154,
|
||||
3155,
|
||||
3156,
|
||||
3157,
|
||||
3158,
|
||||
3159,
|
||||
3160,
|
||||
3161,
|
||||
3162,
|
||||
3163,
|
||||
3164,
|
||||
3165,
|
||||
3166,
|
||||
3167,
|
||||
3168,
|
||||
3169,
|
||||
3200,
|
||||
3201,
|
||||
3202,
|
||||
3203,
|
||||
3204,
|
||||
3205,
|
||||
3206,
|
||||
3207,
|
||||
3208,
|
||||
3209,
|
||||
3210,
|
||||
3211,
|
||||
3212,
|
||||
3213,
|
||||
3214,
|
||||
3215,
|
||||
3216,
|
||||
3217,
|
||||
3218,
|
||||
3219,
|
||||
3250,
|
||||
3251,
|
||||
3252,
|
||||
3253,
|
||||
3254,
|
||||
3255,
|
||||
3256,
|
||||
3257,
|
||||
3258,
|
||||
3259,
|
||||
3260,
|
||||
3261,
|
||||
3262,
|
||||
3263,
|
||||
3300,
|
||||
3301,
|
||||
3302,
|
||||
3303,
|
||||
3304,
|
||||
3305,
|
||||
3306,
|
||||
3307,
|
||||
3308,
|
||||
3309,
|
||||
3310,
|
||||
3311,
|
||||
3350,
|
||||
3351,
|
||||
3352,
|
||||
3353,
|
||||
3400,
|
||||
3401,
|
||||
3402,
|
||||
3403,
|
||||
3404,
|
||||
3405,
|
||||
3406,
|
||||
3407,
|
||||
3408,
|
||||
3409,
|
||||
3410,
|
||||
3411,
|
||||
3412,
|
||||
3413,
|
||||
3414,
|
||||
3415,
|
||||
3416,
|
||||
3417,
|
||||
3450,
|
||||
3451,
|
||||
3452,
|
||||
3453,
|
||||
3454,
|
||||
3455,
|
||||
|
||||
// A017
|
||||
3500,
|
||||
3501,
|
||||
3502,
|
||||
3503,
|
||||
3504,
|
||||
3505,
|
||||
3506,
|
||||
3507,
|
||||
3508,
|
||||
3509,
|
||||
3510,
|
||||
3511,
|
||||
3512,
|
||||
3513,
|
||||
3514,
|
||||
3515,
|
||||
3550,
|
||||
3551,
|
||||
3552,
|
||||
3553,
|
||||
3554,
|
||||
3555,
|
||||
3556,
|
||||
3557,
|
||||
3580,
|
||||
3581,
|
||||
|
||||
// Other event IDs for Chunithm Amazon
|
||||
1,
|
||||
10,
|
||||
11,
|
||||
100,
|
||||
101,
|
||||
102,
|
||||
103,
|
||||
200,
|
||||
201,
|
||||
202,
|
||||
203,
|
||||
300,
|
||||
301,
|
||||
400,
|
||||
401,
|
||||
500,
|
||||
501,
|
||||
700,
|
||||
701,
|
||||
810,
|
||||
820,
|
||||
821,
|
||||
830,
|
||||
831,
|
||||
832,
|
||||
834,
|
||||
835,
|
||||
840,
|
||||
841,
|
||||
842,
|
||||
843,
|
||||
850,
|
||||
851,
|
||||
852,
|
||||
853,
|
||||
860,
|
||||
861,
|
||||
862,
|
||||
863,
|
||||
864,
|
||||
865,
|
||||
870,
|
||||
871,
|
||||
872,
|
||||
873,
|
||||
880,
|
||||
890,
|
||||
891,
|
||||
900,
|
||||
901,
|
||||
902,
|
||||
903,
|
||||
904,
|
||||
905,
|
||||
906,
|
||||
907,
|
||||
908,
|
||||
909,
|
||||
910,
|
||||
911,
|
||||
912,
|
||||
913,
|
||||
914,
|
||||
915,
|
||||
916,
|
||||
917,
|
||||
918,
|
||||
930,
|
||||
931,
|
||||
932,
|
||||
933,
|
||||
934,
|
||||
935,
|
||||
936,
|
||||
937,
|
||||
938,
|
||||
939,
|
||||
940,
|
||||
941,
|
||||
942,
|
||||
943,
|
||||
944,
|
||||
945,
|
||||
946,
|
||||
947,
|
||||
960,
|
||||
961,
|
||||
962,
|
||||
963,
|
||||
964,
|
||||
965,
|
||||
966,
|
||||
967,
|
||||
968,
|
||||
969,
|
||||
990,
|
||||
991,
|
||||
992,
|
||||
993,
|
||||
994,
|
||||
995,
|
||||
996,
|
||||
997,
|
||||
998,
|
||||
999,
|
||||
1000,
|
||||
1001,
|
||||
1002,
|
||||
1003,
|
||||
1004,
|
||||
1005,
|
||||
1006,
|
||||
1020,
|
||||
1021,
|
||||
1022,
|
||||
1023,
|
||||
1024,
|
||||
1025,
|
||||
1026,
|
||||
1027,
|
||||
1028,
|
||||
1029,
|
||||
1030,
|
||||
1031,
|
||||
1050,
|
||||
1051,
|
||||
1052,
|
||||
1053,
|
||||
1054,
|
||||
1055,
|
||||
1056,
|
||||
1057,
|
||||
1058,
|
||||
1059,
|
||||
1060,
|
||||
1061,
|
||||
1070,
|
||||
1098,
|
||||
1099,
|
||||
1100,
|
||||
1101,
|
||||
1102,
|
||||
1103,
|
||||
1104,
|
||||
1105,
|
||||
1106,
|
||||
1107,
|
||||
1108,
|
||||
1109,
|
||||
1110,
|
||||
1111,
|
||||
1112,
|
||||
1113,
|
||||
1114,
|
||||
1130,
|
||||
1131,
|
||||
1132,
|
||||
1133,
|
||||
1134,
|
||||
1135,
|
||||
1136,
|
||||
1137,
|
||||
1138,
|
||||
1159,
|
||||
1160,
|
||||
1161,
|
||||
1162,
|
||||
1163,
|
||||
1164,
|
||||
1165,
|
||||
1166,
|
||||
1167,
|
||||
1168,
|
||||
1169,
|
||||
1170,
|
||||
1171,
|
||||
1172,
|
||||
1173,
|
||||
1174,
|
||||
1175,
|
||||
1190,
|
||||
1191,
|
||||
1192,
|
||||
1193,
|
||||
1194,
|
||||
1195,
|
||||
1196,
|
||||
1197,
|
||||
1198,
|
||||
1199,
|
||||
1200,
|
||||
1201,
|
||||
1220,
|
||||
1221,
|
||||
1222,
|
||||
1223,
|
||||
1224,
|
||||
1225,
|
||||
1226,
|
||||
1227,
|
||||
1228,
|
||||
1229,
|
||||
1230,
|
||||
1231,
|
||||
1232,
|
||||
1233,
|
||||
1250,
|
||||
1251,
|
||||
1252,
|
||||
1253,
|
||||
1254,
|
||||
1255,
|
||||
1256,
|
||||
1257,
|
||||
1258,
|
||||
1259,
|
||||
1260,
|
||||
1261,
|
||||
1279,
|
||||
1280,
|
||||
1281,
|
||||
1282,
|
||||
1283,
|
||||
1284,
|
||||
1285,
|
||||
1286,
|
||||
1287,
|
||||
1288,
|
||||
1289,
|
||||
1290,
|
||||
1291,
|
||||
1292,
|
||||
1293,
|
||||
1294,
|
||||
1295,
|
||||
1296,
|
||||
1297,
|
||||
1298,
|
||||
1299,
|
||||
1310,
|
||||
1311,
|
||||
1312,
|
||||
1313,
|
||||
1314,
|
||||
1315,
|
||||
1316,
|
||||
1317,
|
||||
1318,
|
||||
1319,
|
||||
1320,
|
||||
1321,
|
||||
1322,
|
||||
1323,
|
||||
1324,
|
||||
1325,
|
||||
1326,
|
||||
1327,
|
||||
1328,
|
||||
1329,
|
||||
1340,
|
||||
1341,
|
||||
1342,
|
||||
1343,
|
||||
1344,
|
||||
1345,
|
||||
1346,
|
||||
1347,
|
||||
1348,
|
||||
1349,
|
||||
1350,
|
||||
1351,
|
||||
1352,
|
||||
1353,
|
||||
1354,
|
||||
1355,
|
||||
1356,
|
||||
1357,
|
||||
1370,
|
||||
1371,
|
||||
1372,
|
||||
1373,
|
||||
1374,
|
||||
1375,
|
||||
1376,
|
||||
1377,
|
||||
1378,
|
||||
1379,
|
||||
1380,
|
||||
1381,
|
||||
1382,
|
||||
1383,
|
||||
1384,
|
||||
1385,
|
||||
1386,
|
||||
1387,
|
||||
1388,
|
||||
1400,
|
||||
1401,
|
||||
1402,
|
||||
1403,
|
||||
1404,
|
||||
1405,
|
||||
1406,
|
||||
1407,
|
||||
1408,
|
||||
1409,
|
||||
1410,
|
||||
1411,
|
||||
1412,
|
||||
1413,
|
||||
1414,
|
||||
1415,
|
||||
1416,
|
||||
1430,
|
||||
1431,
|
||||
1432,
|
||||
1433,
|
||||
1434,
|
||||
1435,
|
||||
1436,
|
||||
1437,
|
||||
1438,
|
||||
1439,
|
||||
1440,
|
||||
1441,
|
||||
1442,
|
||||
1443,
|
||||
1444,
|
||||
1445,
|
||||
1446,
|
||||
1447,
|
||||
1460,
|
||||
1461,
|
||||
1462,
|
||||
1463,
|
||||
1464,
|
||||
1465,
|
||||
1500,
|
||||
1501,
|
||||
1502,
|
||||
1504,
|
||||
1505,
|
||||
1507,
|
||||
1508,
|
||||
1509,
|
||||
1510,
|
||||
1511,
|
||||
1512,
|
||||
1513,
|
||||
1514,
|
||||
1515,
|
||||
1516,
|
||||
1517,
|
||||
1518,
|
||||
1519,
|
||||
1520,
|
||||
1521,
|
||||
1522,
|
||||
1523,
|
||||
1524,
|
||||
1600,
|
||||
1601,
|
||||
1603,
|
||||
1605,
|
||||
1606,
|
||||
1650,
|
||||
1651,
|
||||
1652,
|
||||
1653,
|
||||
1654,
|
||||
1655,
|
||||
1656,
|
||||
1657,
|
||||
1658,
|
||||
1659,
|
||||
1660,
|
||||
1661,
|
||||
1700,
|
||||
1701,
|
||||
1703,
|
||||
1750,
|
||||
1751,
|
||||
1752,
|
||||
1753,
|
||||
1754,
|
||||
1755,
|
||||
1756,
|
||||
1757,
|
||||
1758,
|
||||
1759,
|
||||
1760,
|
||||
1761,
|
||||
1762,
|
||||
1763,
|
||||
1764,
|
||||
1765,
|
||||
1766,
|
||||
1767,
|
||||
1768,
|
||||
1769,
|
||||
1770,
|
||||
1800,
|
||||
1801,
|
||||
1802,
|
||||
1803,
|
||||
1804,
|
||||
1805,
|
||||
1850,
|
||||
1851,
|
||||
1852,
|
||||
1853,
|
||||
1854,
|
||||
1855,
|
||||
1856,
|
||||
1857,
|
||||
1880,
|
||||
1881,
|
||||
1882,
|
||||
1883,
|
||||
1884,
|
||||
1885,
|
||||
1886,
|
||||
1887,
|
||||
1888,
|
||||
1889,
|
||||
1890,
|
||||
1900,
|
||||
1901,
|
||||
1903,
|
||||
1904,
|
||||
1905,
|
||||
1906,
|
||||
1907,
|
||||
1908,
|
||||
1909,
|
||||
1910,
|
||||
1911,
|
||||
1912,
|
||||
1913,
|
||||
1950,
|
||||
1951,
|
||||
1952,
|
||||
1954,
|
||||
1955,
|
||||
1956,
|
||||
2000,
|
||||
2001,
|
||||
2003,
|
||||
2004,
|
||||
2005,
|
||||
2006,
|
||||
2007,
|
||||
2008,
|
||||
2009,
|
||||
2010,
|
||||
2011,
|
||||
2012,
|
||||
2013,
|
||||
2050,
|
||||
2051,
|
||||
2052,
|
||||
2053,
|
||||
2054,
|
||||
2055,
|
||||
2056,
|
||||
2100,
|
||||
2101,
|
||||
2103,
|
||||
2104,
|
||||
2105,
|
||||
2106,
|
||||
2107,
|
||||
2108,
|
||||
2109,
|
||||
2110,
|
||||
2111,
|
||||
2150,
|
||||
2151,
|
||||
2152,
|
||||
2153,
|
||||
2154,
|
||||
2155,
|
||||
2156,
|
||||
2200,
|
||||
2201,
|
||||
2202,
|
||||
2203,
|
||||
2204,
|
||||
2205,
|
||||
2207,
|
||||
2208,
|
||||
2209,
|
||||
2210,
|
||||
2211,
|
||||
2212,
|
||||
2213,
|
||||
2214,
|
||||
2215,
|
||||
2216,
|
||||
2217,
|
||||
2218,
|
||||
2219,
|
||||
2220,
|
||||
2221,
|
||||
2222,
|
||||
2223,
|
||||
2224,
|
||||
2225,
|
||||
2226,
|
||||
2250,
|
||||
2251,
|
||||
2252,
|
||||
2253,
|
||||
2280,
|
||||
2281,
|
||||
2282,
|
||||
2283,
|
||||
2300,
|
||||
2301,
|
||||
2302,
|
||||
2303,
|
||||
2304,
|
||||
2305,
|
||||
2306,
|
||||
2307,
|
||||
2308,
|
||||
2350,
|
||||
2351,
|
||||
2352,
|
||||
2354,
|
||||
2355,
|
||||
2356,
|
||||
2380,
|
||||
2381,
|
||||
2382,
|
||||
2383,
|
||||
2400,
|
||||
2401,
|
||||
2402,
|
||||
2403,
|
||||
2404,
|
||||
2405,
|
||||
2406,
|
||||
2407,
|
||||
2408,
|
||||
2409,
|
||||
2410,
|
||||
2411,
|
||||
2412,
|
||||
2413,
|
||||
2414,
|
||||
2415,
|
||||
2416,
|
||||
2417,
|
||||
2418,
|
||||
2419,
|
||||
2420,
|
||||
2421,
|
||||
2450,
|
||||
2451,
|
||||
2452,
|
||||
2453,
|
||||
2454,
|
||||
2455,
|
||||
2456,
|
||||
2457,
|
||||
2458,
|
||||
2459,
|
||||
2500,
|
||||
2501,
|
||||
2502,
|
||||
2503,
|
||||
2504,
|
||||
2505,
|
||||
2506,
|
||||
2507,
|
||||
2508,
|
||||
2509,
|
||||
2510,
|
||||
2511,
|
||||
2550,
|
||||
2551,
|
||||
2552,
|
||||
2554,
|
||||
2555,
|
||||
2556,
|
||||
2557,
|
||||
2558,
|
||||
2559,
|
||||
2560,
|
||||
2561,
|
||||
2562,
|
||||
2600,
|
||||
2601,
|
||||
2602,
|
||||
2603,
|
||||
2604,
|
||||
2605,
|
||||
2606,
|
||||
2607,
|
||||
2608,
|
||||
2609,
|
||||
2610,
|
||||
2611,
|
||||
2612,
|
||||
2613,
|
||||
2614,
|
||||
2615,
|
||||
2650,
|
||||
2651,
|
||||
2652,
|
||||
2653,
|
||||
2654,
|
||||
2655,
|
||||
2656,
|
||||
2657,
|
||||
2658,
|
||||
2659,
|
||||
2660,
|
||||
2700,
|
||||
2701,
|
||||
2702,
|
||||
2703,
|
||||
2704,
|
||||
2705,
|
||||
2706,
|
||||
2707,
|
||||
2708,
|
||||
2709,
|
||||
2710,
|
||||
2711,
|
||||
2750,
|
||||
2751,
|
||||
2752,
|
||||
2753,
|
||||
2754,
|
||||
2755,
|
||||
2800,
|
||||
2801,
|
||||
2802,
|
||||
2803,
|
||||
2805,
|
||||
2806,
|
||||
2807,
|
||||
2808,
|
||||
2809,
|
||||
2810,
|
||||
2811,
|
||||
2812,
|
||||
2813,
|
||||
2814,
|
||||
2815,
|
||||
2816,
|
||||
2817,
|
||||
2818,
|
||||
2850,
|
||||
2851,
|
||||
2852,
|
||||
2853,
|
||||
2854,
|
||||
2855,
|
||||
2856,
|
||||
2857,
|
||||
2900,
|
||||
2901,
|
||||
2902,
|
||||
2903,
|
||||
2950,
|
||||
2951,
|
||||
2952,
|
||||
2953,
|
||||
8001,
|
||||
20000,
|
||||
20001,
|
||||
20002,
|
||||
20004,
|
||||
]);
|
||||
@@ -13,10 +13,11 @@ export function _team(res: CreateAutoTeamResponse | LoadTeamResponse) {
|
||||
buf.writeInt16LE(0x0078, 0x0000);
|
||||
}
|
||||
|
||||
const leader = res.members.find(item => item.leader);
|
||||
|
||||
const leader = res.members.find(item => item.leader === true);
|
||||
buf.writeUInt32LE(res.team.extId, 0x000c);
|
||||
iconv.encode(leader ? leader.profile.name : "Error\0", "shift_jis").copy(buf, 0x0010);
|
||||
iconv.encode(res.team.name, "shift_jis").copy(buf, 0x0024);
|
||||
iconv.encode(process.env.SHOP_NAME ? process.env.SHOP_NAME : "\0", "shift_jis").copy(buf, 0x0044);
|
||||
buf.writeUInt32LE(res.team.nameBg, 0x00d8);
|
||||
buf.writeUInt32LE(res.team.nameFx, 0x00dc);
|
||||
buf.fill(0xff, 0x00e0, 0x00f9); // Bitset: Unlocked BGs probably
|
||||
|
||||
@@ -10,6 +10,6 @@ export async function loadGarage(
|
||||
|
||||
return {
|
||||
type: "load_garage_res",
|
||||
cars: await w.car().loadAllCars(profileId),
|
||||
cars: await w.car().loadCars(profileId, 10, req.fetchOffset),
|
||||
};
|
||||
}
|
||||
|
||||
+5
-1
@@ -11,7 +11,11 @@ export type TeamSpec = Subtract<
|
||||
export interface CarRepository {
|
||||
countCars(profileId: Id<Model.Profile>): Promise<number>;
|
||||
|
||||
loadAllCars(profileId: Id<Model.Profile>): Promise<Model.Car[]>;
|
||||
loadCars(
|
||||
profileId: Id<Model.Profile>,
|
||||
limit: number,
|
||||
offset: number
|
||||
): Promise<Model.Car[]>;
|
||||
|
||||
loadSelectedCar(profileId: Id<Model.Profile>): Promise<Model.Car>;
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ export class SqlBackgroundsRepository
|
||||
const result = new Set<BackgroundCode>();
|
||||
|
||||
for (const row of rows) {
|
||||
result.add(parseInt(row.background_no) as BackgroundCode);
|
||||
result.add(parseInt(row.background_no!) as BackgroundCode);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
+25
-18
@@ -8,21 +8,21 @@ import { Row, Transaction } from "../../sql";
|
||||
|
||||
function _extractRow(row: Row): Car {
|
||||
return {
|
||||
selector: parseInt(row.selector) as CarSelector,
|
||||
field_00: parseInt(row.field_00),
|
||||
field_02: parseInt(row.field_02),
|
||||
field_04: row.field_04.split(",").map((x: string) => parseInt(x)),
|
||||
field_46: parseInt(row.field_46),
|
||||
field_48: parseInt(row.field_48),
|
||||
field_4a: parseInt(row.field_4a),
|
||||
field_4c: parseInt(row.field_4c),
|
||||
field_50_lo: parseInt(row.field_50_lo),
|
||||
field_50_hi: parseInt(row.field_50_hi),
|
||||
field_58: parseInt(row.field_58),
|
||||
field_5a: parseInt(row.field_5a),
|
||||
field_5b: parseInt(row.field_5b),
|
||||
field_5c: parseInt(row.field_5c),
|
||||
field_5e: parseInt(row.field_5e),
|
||||
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!),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -37,14 +37,21 @@ export class SqlCarRepository implements CarRepository {
|
||||
|
||||
const row = await this._txn.fetchRow(countSql);
|
||||
|
||||
return parseInt(row!.result);
|
||||
return parseInt(row!.result!);
|
||||
}
|
||||
|
||||
async loadAllCars(profileId: Id<Profile>): Promise<Car[]> {
|
||||
async loadCars(
|
||||
profileId: Id<Profile>,
|
||||
limit: number,
|
||||
offset: number
|
||||
): Promise<Car[]> {
|
||||
const loadSql = sql
|
||||
.select("c.*")
|
||||
.from("idz_car c")
|
||||
.where("c.profile_id", profileId);
|
||||
.where("c.profile_id", profileId)
|
||||
.orderBy("selector asc")
|
||||
.limit(limit)
|
||||
.offset(offset);
|
||||
|
||||
const rows = await this._txn.fetchRows(loadSql);
|
||||
|
||||
|
||||
+10
-10
@@ -9,16 +9,16 @@ import { Row, Transaction } from "../../sql";
|
||||
|
||||
export function _extractChara(row: Row): Chara {
|
||||
return {
|
||||
gender: row.gender as Gender,
|
||||
field_02: parseInt(row.field_02),
|
||||
field_04: parseInt(row.field_04),
|
||||
field_06: parseInt(row.field_06),
|
||||
field_08: parseInt(row.field_08),
|
||||
field_0a: parseInt(row.field_0a),
|
||||
field_0c: parseInt(row.field_0c),
|
||||
field_0e: parseInt(row.field_0e),
|
||||
title: parseInt(row.title) as TitleCode,
|
||||
background: parseInt(row.background) as BackgroundCode,
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -19,8 +19,8 @@ export class SqlCoursePlaysRepository implements CoursePlaysRepository {
|
||||
const result = new Map<CourseNo, number>();
|
||||
|
||||
for (const row of rows) {
|
||||
const courseNo = parseInt(row.course_no) as CourseNo;
|
||||
const count = parseInt(row.count);
|
||||
const courseNo = parseInt(row.course_no!) as CourseNo;
|
||||
const count = parseInt(row.count!);
|
||||
|
||||
result.set(courseNo, count);
|
||||
}
|
||||
|
||||
@@ -36,9 +36,9 @@ export class SqlMissionsRepository implements FacetRepository<MissionState> {
|
||||
const rows = await this._txn.fetchRows(loadSoloSql);
|
||||
|
||||
for (const row of rows) {
|
||||
const gridNo = parseInt(row.grid_no);
|
||||
const cellNo = parseInt(row.cell_no);
|
||||
const value = parseInt(row.value);
|
||||
const gridNo = parseInt(row.grid_no!);
|
||||
const cellNo = parseInt(row.cell_no!);
|
||||
const value = parseInt(row.value!);
|
||||
|
||||
result.solo[gridNo].cells[cellNo] = value;
|
||||
}
|
||||
|
||||
@@ -7,15 +7,15 @@ import { Row, Transaction } from "../../sql";
|
||||
|
||||
export function _extractProfile(row: Row): Profile {
|
||||
return {
|
||||
aimeId: parseInt(row.aime_id) as AimeId,
|
||||
name: row.name,
|
||||
lv: parseInt(row.lv),
|
||||
exp: parseInt(row.exp),
|
||||
fame: parseInt(row.fame),
|
||||
dpoint: parseInt(row.dpoint),
|
||||
mileage: parseInt(row.mileage),
|
||||
accessTime: new Date(row.access_time),
|
||||
registerTime: new Date(row.register_time),
|
||||
aimeId: parseInt(row.aime_id!) as AimeId,
|
||||
name: row.name!,
|
||||
lv: parseInt(row.lv!),
|
||||
exp: parseInt(row.exp!),
|
||||
fame: parseInt(row.fame!),
|
||||
dpoint: parseInt(row.dpoint!),
|
||||
mileage: parseInt(row.mileage!),
|
||||
accessTime: new Date(row.access_time!),
|
||||
registerTime: new Date(row.register_time!),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -22,11 +22,11 @@ export class SqlSettingsRepository implements FacetRepository<Settings> {
|
||||
}
|
||||
|
||||
return {
|
||||
music: parseInt(row.music),
|
||||
pack: parseInt(row.pack),
|
||||
aura: parseInt(row.aura),
|
||||
paperCup: parseInt(row.paper_cup),
|
||||
gauges: parseInt(row.gauges),
|
||||
music: parseInt(row.music!),
|
||||
pack: parseInt(row.pack!),
|
||||
aura: parseInt(row.aura!),
|
||||
paperCup: parseInt(row.paper_cup!),
|
||||
gauges: parseInt(row.gauges!),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -20,8 +20,8 @@ export class SqlStoryRepository implements FacetRepository<Story> {
|
||||
// Must succeed even if nonexistent (required by save method below)
|
||||
|
||||
const result = {
|
||||
x: header !== undefined ? parseInt(header.x) : 0,
|
||||
y: header !== undefined ? parseInt(header.y) : 0,
|
||||
x: header !== undefined ? parseInt(header.x!) : 0,
|
||||
y: header !== undefined ? parseInt(header.y!) : 0,
|
||||
rows: new Array<StoryRow>(),
|
||||
};
|
||||
|
||||
@@ -43,12 +43,12 @@ export class SqlStoryRepository implements FacetRepository<Story> {
|
||||
const rows = await this._txn.fetchRows(loadCellSql);
|
||||
|
||||
for (const row of rows) {
|
||||
const rowNo = parseInt(row.row_no);
|
||||
const colNo = parseInt(row.col_no);
|
||||
const rowNo = parseInt(row.row_no!);
|
||||
const colNo = parseInt(row.col_no!);
|
||||
const cell = result.rows[rowNo].cells[colNo];
|
||||
|
||||
cell.a = parseInt(row.a);
|
||||
cell.b = parseInt(row.b);
|
||||
cell.a = parseInt(row.a!);
|
||||
cell.b = parseInt(row.b!);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
+5
-5
@@ -37,11 +37,11 @@ export class SqlTeamRepository implements TeamRepository {
|
||||
}
|
||||
|
||||
return {
|
||||
extId: parseInt(row.ext_id) as ExtId<Team>,
|
||||
name: row.name,
|
||||
nameBg: parseInt(row.name_bg),
|
||||
nameFx: parseInt(row.name_fx),
|
||||
registerTime: new Date(row.register_time),
|
||||
extId: parseInt(row.ext_id!) as ExtId<Team>,
|
||||
name: row.name!,
|
||||
nameBg: parseInt(row.name_bg!),
|
||||
nameFx: parseInt(row.name_fx!),
|
||||
registerTime: new Date(row.register_time!),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -20,8 +20,8 @@ export class SqlTeamAutoRepository implements TeamAutoRepository {
|
||||
return (
|
||||
row && [
|
||||
{
|
||||
serialNo: parseInt(row.serial_no),
|
||||
nameIdx: parseInt(row.name_idx),
|
||||
serialNo: parseInt(row.serial_no!),
|
||||
nameIdx: parseInt(row.name_idx!),
|
||||
},
|
||||
row.id as Id<Team>,
|
||||
]
|
||||
|
||||
@@ -56,8 +56,8 @@ export class SqlTeamMemberRepository implements TeamMemberRepository {
|
||||
return rows.map(row => ({
|
||||
profile: _extractProfile(row),
|
||||
chara: _extractChara(row),
|
||||
leader: !!row.leader,
|
||||
joinTime: new Date(row.join_time),
|
||||
leader: row.leader === "true",
|
||||
joinTime: new Date(row.join_time!),
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ export class SqlTeamMemberRepository implements TeamMemberRepository {
|
||||
|
||||
const row = await this._txn.fetchRow(countSql);
|
||||
|
||||
if (parseInt(row!.count) >= 6) {
|
||||
if (parseInt(row!.count!) >= 6) {
|
||||
throw new Error(`Team ${teamId} is full`);
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ export class SqlTeamReservationRepository
|
||||
.where("tm.team_id", teamId);
|
||||
|
||||
const memberRes = await this._txn.fetchRow(memberSql);
|
||||
const memberCount = parseInt(memberRes!.count);
|
||||
const memberCount = parseInt(memberRes!.count!);
|
||||
|
||||
const reservSql = sql
|
||||
.select("count(*) as count")
|
||||
@@ -36,7 +36,7 @@ export class SqlTeamReservationRepository
|
||||
.where("tr.team_id", teamId);
|
||||
|
||||
const reservRes = await this._txn.fetchRow(reservSql);
|
||||
const reservCount = parseInt(reservRes!.count);
|
||||
const reservCount = parseInt(reservRes!.count!);
|
||||
|
||||
return memberCount + reservCount;
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ export class SqlTicketsRepository implements FacetRepository<Tickets> {
|
||||
|
||||
return {
|
||||
freeCar: row && {
|
||||
validFrom: new Date(row.valid_from),
|
||||
validFrom: new Date(row.valid_from!),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
+14
-14
@@ -11,13 +11,13 @@ import { Row, Transaction } from "../../sql";
|
||||
|
||||
function _extractRow(row: Row): TimeAttackScore {
|
||||
return {
|
||||
routeNo: parseInt(row.route_no) as RouteNo,
|
||||
timestamp: new Date(row.timestamp),
|
||||
flags: parseInt(row.flags),
|
||||
totalTime: parseFloat(row.total_time),
|
||||
sectionTimes: row.section_times.split(",").map(parseFloat),
|
||||
grade: parseInt(row.grade),
|
||||
carSelector: parseInt(row.car_selector) as CarSelector,
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -61,13 +61,13 @@ export class SqlTimeAttackRepository implements TimeAttackRepository {
|
||||
const rows = await this._txn.fetchRows(loadSql);
|
||||
|
||||
return rows.map(row => ({
|
||||
driverName: row.profile_name,
|
||||
driverName: row.profile_name!,
|
||||
team: {
|
||||
extId: parseInt(row.team_ext_id) as ExtId<Team>,
|
||||
name: row.team_name,
|
||||
nameBg: parseInt(row.team_name_bg),
|
||||
nameFx: parseInt(row.team_name_fx),
|
||||
registerTime: new Date(row.team_register_time),
|
||||
extId: parseInt(row.team_ext_id!) as ExtId<Team>,
|
||||
name: row.team_name!,
|
||||
nameBg: parseInt(row.team_name_bg!),
|
||||
nameFx: parseInt(row.team_name_fx!),
|
||||
registerTime: new Date(row.team_register_time!),
|
||||
},
|
||||
ta: _extractRow(row),
|
||||
}));
|
||||
@@ -122,7 +122,7 @@ export class SqlTimeAttackRepository implements TimeAttackRepository {
|
||||
|
||||
await this._txn.modify(insertSql);
|
||||
} else {
|
||||
if (score.totalTime < parseFloat(row.total_time)) {
|
||||
if (score.totalTime < parseFloat(row.total_time!)) {
|
||||
const updateSql = sql
|
||||
.update("idz_ta_best", {
|
||||
total_time: score.totalTime,
|
||||
|
||||
@@ -19,7 +19,7 @@ export class SqlTitlesRepository implements FlagRepository<TitleCode> {
|
||||
const result = new Set<TitleCode>();
|
||||
|
||||
for (const row of rows) {
|
||||
result.add(parseInt(row.title_no) as TitleCode);
|
||||
result.add(parseInt(row.title_no!) as TitleCode);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
@@ -22,11 +22,11 @@ export class SqlUnlocksRepository implements FacetRepository<Unlocks> {
|
||||
}
|
||||
|
||||
return {
|
||||
auras: parseInt(row.auras),
|
||||
cup: parseInt(row.cup),
|
||||
gauges: parseInt(row.gauges),
|
||||
music: parseInt(row.music),
|
||||
lastMileageReward: parseInt(row.last_mileage_reward),
|
||||
auras: parseInt(row.auras!),
|
||||
cup: parseInt(row.cup!),
|
||||
gauges: parseInt(row.gauges!),
|
||||
music: parseInt(row.music!),
|
||||
lastMileageReward: parseInt(row.last_mileage_reward!),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ import * as sql from "sql-bricks-postgres";
|
||||
import { Id } from "../model";
|
||||
|
||||
export interface Row {
|
||||
[key: string]: string;
|
||||
[key: string]: string | null;
|
||||
}
|
||||
|
||||
export interface Transaction {
|
||||
|
||||
+21
-7
@@ -3,15 +3,23 @@ import snakeCase from "snake-case";
|
||||
import { Row } from "./api";
|
||||
|
||||
interface ColMapper<F> {
|
||||
_read(str: string): F;
|
||||
_read(str: string | null): F;
|
||||
|
||||
_write(val: F): string;
|
||||
_write(val: F): string | null;
|
||||
}
|
||||
|
||||
type Spec<R> = {
|
||||
[K in keyof R]: ColMapper<R[K]>;
|
||||
};
|
||||
|
||||
function _nn(str: string | null): string {
|
||||
if (str === null) {
|
||||
throw new Error("Unexpected NULL returned from database");
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function objects describing the precise way in which our SQL driver
|
||||
* transmits and receives values to the database as strings. Note that we could
|
||||
@@ -20,25 +28,31 @@ type Spec<R> = {
|
||||
*/
|
||||
export const T = {
|
||||
bigint: {
|
||||
_read: (str: string) => BigInt(str),
|
||||
_read: (str: string) => BigInt(_nn(str)),
|
||||
_write: (val: bigint) => val.toString(),
|
||||
},
|
||||
boolean: {
|
||||
_read: (str: string) => str === "true",
|
||||
_read: (str: string) => _nn(str) === "true",
|
||||
_write: (val: boolean) => val.toString(),
|
||||
},
|
||||
number: {
|
||||
_read: (str: string) => parseInt(str),
|
||||
_read: (str: string) => parseInt(_nn(str)),
|
||||
_write: (val: number) => val.toString(),
|
||||
},
|
||||
string: {
|
||||
_read: (str: string) => str,
|
||||
_read: (str: string) => _nn(str),
|
||||
_write: (val: string) => val,
|
||||
},
|
||||
Date: {
|
||||
_read: (str: string) => new Date(str),
|
||||
_read: (str: string) => new Date(_nn(str)),
|
||||
_write: (val: Date) => val.toISOString(),
|
||||
},
|
||||
nullable: <F>(inner: ColMapper<F>) => ({
|
||||
_read: (str: string | null) =>
|
||||
str !== null ? inner._read(str) : undefined,
|
||||
_write: (val: F | undefined) =>
|
||||
val !== undefined ? inner._write(val) : null,
|
||||
}),
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
Vendored
+1
-1
@@ -7,7 +7,7 @@ declare module "sql-bricks-postgres" {
|
||||
}
|
||||
|
||||
interface LimitClause extends sql.SelectStatement {
|
||||
offset(value: number): sql.Statement;
|
||||
offset(value: number): sql.SelectStatement;
|
||||
}
|
||||
|
||||
export interface PgInsertStatement extends sql.InsertStatement {
|
||||
|
||||
Reference in New Issue
Block a user