From 9ecd8be582e021d3c761765cc4d725bfc35f35a7 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Wed, 8 Dec 2021 12:53:38 +0000 Subject: [PATCH 01/32] Bump tachi-common to use the latest chunithm folders. --- server/package.json | 2 +- server/pnpm-lock.yaml | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/server/package.json b/server/package.json index ebb991d54..d9a17d71b 100644 --- a/server/package.json +++ b/server/package.json @@ -83,7 +83,7 @@ "rimraf": "3.0.2", "safe-json-stringify": "1.2.0", "seq-logging": "^1.1.1", - "tachi-common": "0.3.13", + "tachi-common": "0.3.14", "ts-node": "10.0.0", "typescript": "4.3.4", "winston": "3.3.3", diff --git a/server/pnpm-lock.yaml b/server/pnpm-lock.yaml index ad124496d..e85fa1a93 100644 --- a/server/pnpm-lock.yaml +++ b/server/pnpm-lock.yaml @@ -63,7 +63,7 @@ specifiers: safe-json-stringify: 1.2.0 seq-logging: ^1.1.1 supertest: 6.1.3 - tachi-common: 0.3.13 + tachi-common: 0.3.14 tap: 15.0.9 ts-node: 10.0.0 tsconfig-paths: 3.10.1 @@ -107,7 +107,7 @@ dependencies: rimraf: 3.0.2 safe-json-stringify: 1.2.0 seq-logging: 1.1.1 - tachi-common: 0.3.13 + tachi-common: 0.3.14 ts-node: 10.0.0_83f53b0a0c5616d3fa00ed4e30b9ce1b typescript: 4.3.4 winston: 3.3.3 @@ -5387,8 +5387,8 @@ packages: strip-ansi: 6.0.0 dev: true - /tachi-common/0.3.13: - resolution: {integrity: sha512-nelJPWufn30Q2Mn+6mD+aTlIsWXxdSug1+wkWcT6TCynVEG+peY2WWGkpNUfMenRM9OBIImY6VoC4AvHZ4qI6Q==} + /tachi-common/0.3.14: + resolution: {integrity: sha512-LjUItf/Lp4xHdqhH1ZBE/4Czvco2WwUs2OgHIDkYSot+Wy1F/K2xycgJp6jLkXdJeeJWVO4hSIUQakzeqdhS1A==} dependencies: monk: 7.3.4 transitivePeerDependencies: From a9a62d20a54c0f0c422150fb7d3f7b3bd42a8fc7 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Thu, 9 Dec 2021 00:40:27 +0000 Subject: [PATCH 02/32] Bump versions to v2.0.4. --- server/package.json | 2 +- server/src/lib/constants/version.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/server/package.json b/server/package.json index d9a17d71b..e9ab70e28 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "tachi-server", - "version": "2.0.3", + "version": "2.0.4", "description": "A score tracking server.", "main": "js/index.js", "private": true, diff --git a/server/src/lib/constants/version.ts b/server/src/lib/constants/version.ts index 6e1a0e2f6..87b5cb835 100644 --- a/server/src/lib/constants/version.ts +++ b/server/src/lib/constants/version.ts @@ -4,7 +4,7 @@ const MAJOR = 2; const MINOR = 0; -const PATCH = 3; +const PATCH = 4; // As is with all front-facing zkldi projects, the version names for tachi-server // are from an album I like. In this case, the album is Portishead - Dummy. From 8f1f4e3b10c109dd1a12850f72f0fc8b876657d2 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Thu, 9 Dec 2021 01:40:01 +0000 Subject: [PATCH 03/32] Make critical optimisation to update-score.ts --- server/src/lib/score-mutation/update-score.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/server/src/lib/score-mutation/update-score.ts b/server/src/lib/score-mutation/update-score.ts index e1aa8f9e4..0e9b27458 100644 --- a/server/src/lib/score-mutation/update-score.ts +++ b/server/src/lib/score-mutation/update-score.ts @@ -2,6 +2,7 @@ import db from "external/mongo/db"; import { KtLogger, rootLogger } from "lib/logger/logger"; import { CreateCalculatedData } from "lib/score-import/framework/calculated-data/calculated-data"; +import { UpdateChartRanking } from "lib/score-import/framework/pb/create-pb-doc"; import { CreateScoreID } from "lib/score-import/framework/score-importing/score-id"; import { ScoreDocument } from "tachi-common"; import { UpdateAllPBs } from "utils/calculations/recalc-scores"; @@ -123,13 +124,16 @@ export default async function UpdateScore(oldScore: ScoreDocument, newScore: Sco // We run updateAllPbs on just the modified chart -- the reason // for this is to update ranking info incase that might fall out of // sync as a result. - await UpdateAllPBs(undefined, { + await UpdateAllPBs([userID], { chartID: newScore.chartID, }); - await UpdateAllPBs(undefined, { + await UpdateAllPBs([userID], { chartID: oldScore.chartID, }); + await UpdateChartRanking(newScore.chartID); + await UpdateChartRanking(oldScore.chartID); + const imports = await db.imports.find({ scoreIDs: oldScoreID, }); From 17bdfc219ad625ee6934adf67db4872d7b2af225 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Thu, 9 Dec 2021 01:41:57 +0000 Subject: [PATCH 04/32] downgrade log level to reduce spam# Please enter the commit message for your changes. Lines starting --- server/src/lib/score-mutation/update-score.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/server/src/lib/score-mutation/update-score.ts b/server/src/lib/score-mutation/update-score.ts index 0e9b27458..3f23762e8 100644 --- a/server/src/lib/score-mutation/update-score.ts +++ b/server/src/lib/score-mutation/update-score.ts @@ -57,7 +57,7 @@ export default async function UpdateScore(oldScore: ScoreDocument, newScore: Sco context: ["Update Score", oldScore.scoreID, newScore.scoreID, FormatUserDoc(user)], }) as KtLogger; - logger.info("Received Update Score request."); + logger.verbose("Received Update Score request."); newScore.calculatedData = await CreateCalculatedData( newScore, @@ -83,7 +83,7 @@ export default async function UpdateScore(oldScore: ScoreDocument, newScore: Sco "scoreInfo.scoreID": oldScoreID, }); - logger.info(`Updating ${sessions.length} sessions.`); + logger.verbose(`Updating ${sessions.length} sessions.`); // For every session that interacts with this score ID (there should only ever be one) for (const session of sessions) { @@ -118,7 +118,7 @@ export default async function UpdateScore(oldScore: ScoreDocument, newScore: Sco ); } - logger.info(`Updating PBs.`); + logger.verbose(`Updating PBs.`); // Update the PBs to reference properly. // We run updateAllPbs on just the modified chart -- the reason @@ -138,7 +138,7 @@ export default async function UpdateScore(oldScore: ScoreDocument, newScore: Sco scoreIDs: oldScoreID, }); - logger.info(`Updating ${imports.length} imports.`); + logger.verbose(`Updating ${imports.length} imports.`); for (const importDoc of imports) { await db.imports.update( @@ -153,5 +153,5 @@ export default async function UpdateScore(oldScore: ScoreDocument, newScore: Sco ); } - logger.info(`Done updating score.`); + logger.verbose(`Done updating score.`); } From e884de6077ad5513b8677ff32e73d67cf503f7bb Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Thu, 9 Dec 2021 12:57:41 +0000 Subject: [PATCH 05/32] Make sure the recalc scripts exit. --- server/scripts/single-use/adjust-faulty-iidx-deltas.ts | 2 ++ server/scripts/single-use/recalc-iidx-percents.ts | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/server/scripts/single-use/adjust-faulty-iidx-deltas.ts b/server/scripts/single-use/adjust-faulty-iidx-deltas.ts index 1bef11f91..33b463070 100644 --- a/server/scripts/single-use/adjust-faulty-iidx-deltas.ts +++ b/server/scripts/single-use/adjust-faulty-iidx-deltas.ts @@ -44,5 +44,7 @@ if (require.main === module) { await UpdateScore(badScore, newScore); } } + + process.exit(0); })(); } diff --git a/server/scripts/single-use/recalc-iidx-percents.ts b/server/scripts/single-use/recalc-iidx-percents.ts index c31a6ac8b..6f815f26b 100644 --- a/server/scripts/single-use/recalc-iidx-percents.ts +++ b/server/scripts/single-use/recalc-iidx-percents.ts @@ -46,6 +46,9 @@ if (require.main === module) { resume(); }) - .then(() => logger.info(`Fixed ${i} bad scores.`)); + .then(() => { + logger.info(`Fixed ${i} bad scores.`); + process.exit(0); + }); })(); } From a364f23cd348bfc8ab665e87ee6388b352c46a90 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Thu, 9 Dec 2021 13:13:02 +0000 Subject: [PATCH 06/32] Make sure update-score removes a score if all references to it are popped --- server/src/lib/score-mutation/update-score.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/server/src/lib/score-mutation/update-score.ts b/server/src/lib/score-mutation/update-score.ts index 3f23762e8..f2f0f4f0c 100644 --- a/server/src/lib/score-mutation/update-score.ts +++ b/server/src/lib/score-mutation/update-score.ts @@ -75,8 +75,11 @@ export default async function UpdateScore(oldScore: ScoreDocument, newScore: Sco ); } catch (err) { logger.warn( - `Score ID ${newScoreID} already existed -- this update caused a collision. Updating old references anyway.` + `Score ID ${newScoreID} already existed -- this update caused a collision. Removing old score and updating old references anyway.` ); + await db.scores.remove({ + scoreID: oldScoreID, + }); } const sessions = await db.sessions.find({ From e08fe3f6d174f225f37f19b966881323b2ce9d4a Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Thu, 9 Dec 2021 20:55:13 +0000 Subject: [PATCH 07/32] Update BMS sync scripts to have scr and ln tables --- server/src/lib/constants/bms-tables.ts | 2 ++ server/src/lib/jobs/bms-table-sync.ts | 16 ++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/server/src/lib/constants/bms-tables.ts b/server/src/lib/constants/bms-tables.ts index 06e1be4a7..7f46dcc6d 100644 --- a/server/src/lib/constants/bms-tables.ts +++ b/server/src/lib/constants/bms-tables.ts @@ -8,4 +8,6 @@ export const BMS_TABLES = { satellite: "sl", dpNormal: "δ", dpInsane: "★", + scratch: "h◎", + ln: "◆", }; diff --git a/server/src/lib/jobs/bms-table-sync.ts b/server/src/lib/jobs/bms-table-sync.ts index 51abcd0f4..54c19377f 100644 --- a/server/src/lib/jobs/bms-table-sync.ts +++ b/server/src/lib/jobs/bms-table-sync.ts @@ -84,6 +84,22 @@ const registeredTables: BMSTablesDataset[] = [ playtype: "14K", url: "https://stellabms.xyz/dp/score.json", }, + { + name: "Scratch 3rd", + description: "The 7K Sara 3 table.", + humanisedPrefix: "scr", + prefix: BMS_TABLES.scratch, + playtype: "7K", + url: "http://minddnim.web.fc2.com/sara/3rd_hard/json/data.json", + }, + { + name: "LN", + description: "The 7K LN table.", + humanisedPrefix: "ln", + prefix: BMS_TABLES.ln, + playtype: "7K", + url: "http://flowermaster.web.fc2.com/lrnanido/gla/score.json", + }, ]; /* eslint-disable no-await-in-loop */ From 17cd6a0c4b6271855a70c48dd7b5b7926e4d6334 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Fri, 10 Dec 2021 00:45:58 +0000 Subject: [PATCH 08/32] Add rekidai.info forks to the list of supported beatoraja clients --- .../src/lib/score-import/import-types/ir/beatoraja/parser.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/server/src/lib/score-import/import-types/ir/beatoraja/parser.ts b/server/src/lib/score-import/import-types/ir/beatoraja/parser.ts index de32ce9e8..7ca9671c5 100644 --- a/server/src/lib/score-import/import-types/ir/beatoraja/parser.ts +++ b/server/src/lib/score-import/import-types/ir/beatoraja/parser.ts @@ -68,6 +68,10 @@ const SUPPORTED_BEATORAJA_CLIENTS = [ "LR2oraja 0.8.2", "LR2oraja 0.8.1", "LR2oraja 0.8.0", + "LR2oraja(rekidai.info) 0.8.3", + "LR2oraja(rekidai.info) 0.8.2", + "LR2oraja(rekidai.info) 0.8.1", + "LR2oraja(rekidai.info) 0.8.0", ]; export function ParseBeatorajaSingle( From cebd6eb26ab38c99cf12996f4ebd37281ecd8806 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Fri, 10 Dec 2021 13:31:25 +0000 Subject: [PATCH 09/32] Bump tachi-common to use the latest grades --- server/package.json | 2 +- server/pnpm-lock.yaml | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/server/package.json b/server/package.json index e9ab70e28..35217ff98 100644 --- a/server/package.json +++ b/server/package.json @@ -83,7 +83,7 @@ "rimraf": "3.0.2", "safe-json-stringify": "1.2.0", "seq-logging": "^1.1.1", - "tachi-common": "0.3.14", + "tachi-common": "0.3.15", "ts-node": "10.0.0", "typescript": "4.3.4", "winston": "3.3.3", diff --git a/server/pnpm-lock.yaml b/server/pnpm-lock.yaml index e85fa1a93..388a72a50 100644 --- a/server/pnpm-lock.yaml +++ b/server/pnpm-lock.yaml @@ -63,7 +63,7 @@ specifiers: safe-json-stringify: 1.2.0 seq-logging: ^1.1.1 supertest: 6.1.3 - tachi-common: 0.3.14 + tachi-common: 0.3.15 tap: 15.0.9 ts-node: 10.0.0 tsconfig-paths: 3.10.1 @@ -107,7 +107,7 @@ dependencies: rimraf: 3.0.2 safe-json-stringify: 1.2.0 seq-logging: 1.1.1 - tachi-common: 0.3.14 + tachi-common: 0.3.15 ts-node: 10.0.0_83f53b0a0c5616d3fa00ed4e30b9ce1b typescript: 4.3.4 winston: 3.3.3 @@ -5387,8 +5387,8 @@ packages: strip-ansi: 6.0.0 dev: true - /tachi-common/0.3.14: - resolution: {integrity: sha512-LjUItf/Lp4xHdqhH1ZBE/4Czvco2WwUs2OgHIDkYSot+Wy1F/K2xycgJp6jLkXdJeeJWVO4hSIUQakzeqdhS1A==} + /tachi-common/0.3.15: + resolution: {integrity: sha512-ZxdS+vIq9sFgpHxIn0VkaMUFcWXWsr+eHkn2HISq10Cudjv8uGzREGFSFN64e8YOKyPNYMMVi6mSoVyvY08ang==} dependencies: monk: 7.3.4 transitivePeerDependencies: From 3897c7087d7cb600791a47dedfc1e789972f5414 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Fri, 10 Dec 2021 13:31:46 +0000 Subject: [PATCH 10/32] move state-sync scripts out of __ktdb migration, since they're still useful. --- server/scripts/bpi-poyashi-data/update-bpi-poyashi.ts | 2 +- .../state-sync/create-game-profiles.ts | 0 .../state-sync/dedupe-score-ids.ts | 0 .../state-sync/recalc-all-scores.ts | 0 .../{__KT_DATABASE_MIGRATION => }/state-sync/recalc-sessions.ts | 0 .../{__KT_DATABASE_MIGRATION => }/state-sync/sync-state.ts | 0 .../{__KT_DATABASE_MIGRATION => }/state-sync/update-all-pbs.ts | 0 7 files changed, 1 insertion(+), 1 deletion(-) rename server/scripts/{__KT_DATABASE_MIGRATION => }/state-sync/create-game-profiles.ts (100%) rename server/scripts/{__KT_DATABASE_MIGRATION => }/state-sync/dedupe-score-ids.ts (100%) rename server/scripts/{__KT_DATABASE_MIGRATION => }/state-sync/recalc-all-scores.ts (100%) rename server/scripts/{__KT_DATABASE_MIGRATION => }/state-sync/recalc-sessions.ts (100%) rename server/scripts/{__KT_DATABASE_MIGRATION => }/state-sync/sync-state.ts (100%) rename server/scripts/{__KT_DATABASE_MIGRATION => }/state-sync/update-all-pbs.ts (100%) diff --git a/server/scripts/bpi-poyashi-data/update-bpi-poyashi.ts b/server/scripts/bpi-poyashi-data/update-bpi-poyashi.ts index e1e6dfb84..a380dcc42 100644 --- a/server/scripts/bpi-poyashi-data/update-bpi-poyashi.ts +++ b/server/scripts/bpi-poyashi-data/update-bpi-poyashi.ts @@ -8,7 +8,7 @@ import path from "path"; import { ChartDocument, Difficulties, IIDXBPIData } from "tachi-common"; import { FindChartWithPTDFVersion } from "utils/queries/charts"; import { FindSongOnTitle } from "utils/queries/songs"; -import { RecalcAllScores } from "../__KT_DATABASE_MIGRATION/state-sync/recalc-all-scores"; +import { RecalcAllScores } from "../state-sync/recalc-all-scores"; const program = new Command(); diff --git a/server/scripts/__KT_DATABASE_MIGRATION/state-sync/create-game-profiles.ts b/server/scripts/state-sync/create-game-profiles.ts similarity index 100% rename from server/scripts/__KT_DATABASE_MIGRATION/state-sync/create-game-profiles.ts rename to server/scripts/state-sync/create-game-profiles.ts diff --git a/server/scripts/__KT_DATABASE_MIGRATION/state-sync/dedupe-score-ids.ts b/server/scripts/state-sync/dedupe-score-ids.ts similarity index 100% rename from server/scripts/__KT_DATABASE_MIGRATION/state-sync/dedupe-score-ids.ts rename to server/scripts/state-sync/dedupe-score-ids.ts diff --git a/server/scripts/__KT_DATABASE_MIGRATION/state-sync/recalc-all-scores.ts b/server/scripts/state-sync/recalc-all-scores.ts similarity index 100% rename from server/scripts/__KT_DATABASE_MIGRATION/state-sync/recalc-all-scores.ts rename to server/scripts/state-sync/recalc-all-scores.ts diff --git a/server/scripts/__KT_DATABASE_MIGRATION/state-sync/recalc-sessions.ts b/server/scripts/state-sync/recalc-sessions.ts similarity index 100% rename from server/scripts/__KT_DATABASE_MIGRATION/state-sync/recalc-sessions.ts rename to server/scripts/state-sync/recalc-sessions.ts diff --git a/server/scripts/__KT_DATABASE_MIGRATION/state-sync/sync-state.ts b/server/scripts/state-sync/sync-state.ts similarity index 100% rename from server/scripts/__KT_DATABASE_MIGRATION/state-sync/sync-state.ts rename to server/scripts/state-sync/sync-state.ts diff --git a/server/scripts/__KT_DATABASE_MIGRATION/state-sync/update-all-pbs.ts b/server/scripts/state-sync/update-all-pbs.ts similarity index 100% rename from server/scripts/__KT_DATABASE_MIGRATION/state-sync/update-all-pbs.ts rename to server/scripts/state-sync/update-all-pbs.ts From c53d2a82df984f091138e394995d63f4b63afcbc Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Fri, 10 Dec 2021 13:33:31 +0000 Subject: [PATCH 11/32] Add a chunithm recalc script. --- .../single-use/recalc-chunithm-scores.ts | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 server/scripts/single-use/recalc-chunithm-scores.ts diff --git a/server/scripts/single-use/recalc-chunithm-scores.ts b/server/scripts/single-use/recalc-chunithm-scores.ts new file mode 100644 index 000000000..84592f80d --- /dev/null +++ b/server/scripts/single-use/recalc-chunithm-scores.ts @@ -0,0 +1,49 @@ +import db from "external/mongo/db"; +import CreateLogCtx from "lib/logger/logger"; +import { GetGradeFromPercent } from "lib/score-import/framework/common/score-utils"; +import UpdateScore from "lib/score-mutation/update-score"; +import { ChartDocument, GetGamePTConfig } from "tachi-common"; + +const logger = CreateLogCtx(__filename); + +if (require.main === module) { + let i = 0; + db.scores + .find({ game: "chunithm", "scoreData.grade": "SS" }) + // @ts-expect-error dumb + .each(async (oldScore, { pause, resume }) => { + pause(); + + const chart = (await db.charts.chunithm.findOne({ + chartID: oldScore.chartID, + })) as ChartDocument<"chunithm:Single">; + + if (!chart) { + logger.severe(`Chart ${oldScore.chartID} has no chart?`); + return resume(); + } + + const newScore = JSON.parse(JSON.stringify(oldScore)); + + newScore.scoreData.grade = GetGradeFromPercent( + "chunithm", + oldScore.playtype, + oldScore.scoreData.percent + ); + + newScore.scoreData.gradeIndex = GetGamePTConfig( + "chunithm", + oldScore.playtype + ).grades.indexOf(newScore.scoreData.grade); + + await UpdateScore(oldScore, newScore); + + i++; + + resume(); + }) + .then(() => { + logger.info(`Fixed ${i} bad scores.`); + process.exit(0); + }); +} From af02ca1f5948b7dc750bf54779a3a9cf8b56e273 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Fri, 10 Dec 2021 13:43:32 +0000 Subject: [PATCH 12/32] Add a recalc endpoint. --- .../src/server/router/api/v1/admin/router.ts | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/server/src/server/router/api/v1/admin/router.ts b/server/src/server/router/api/v1/admin/router.ts index 5d1bd4783..a3b677235 100644 --- a/server/src/server/router/api/v1/admin/router.ts +++ b/server/src/server/router/api/v1/admin/router.ts @@ -8,11 +8,11 @@ import { GetUserWithID } from "utils/user"; import { ONE_MINUTE } from "lib/constants/time"; import { ServerConfig, TachiConfig } from "lib/setup/config"; import { Game, UserAuthLevels } from "tachi-common"; - import db from "external/mongo/db"; import { DeleteScore } from "lib/score-mutation/delete-scores"; -import { UpdateAllPBs } from "utils/calculations/recalc-scores"; +import { RecalcAllScores, UpdateAllPBs } from "utils/calculations/recalc-scores"; import DestroyUserGamePlaytypeData from "utils/reset-state/destroy-ugpt"; +import { RecalcSessions } from "utils/calculations/recalc-sessions"; const logger = CreateLogCtx(__filename); @@ -264,7 +264,7 @@ router.post("/delete-score", prValidate({ scoreID: "string" }), async (req, res) /** * Destroys a users UGPT profile and forces a site recalc. * - * @POST /api/v1/admin/destroy-ugpt + * @name POST /api/v1/admin/destroy-ugpt */ router.post( "/destroy-ugpt", @@ -302,7 +302,7 @@ router.post( /** * Destroy a chart and all of its scores (and sessions). * - * name @POST /api/v1/admin/destroy-chart + * @name POST /api/v1/admin/destroy-chart */ router.post( "/destroy-chart", @@ -370,4 +370,21 @@ router.post( } ); +/** + * Perform a site recalc on this set of scores. + * + * @name POST /api/v1/admin/recalc + */ +router.post("/recalc", async (req, res) => { + const filter = req.body ?? {}; + await RecalcAllScores(filter); + await RecalcSessions(filter); + + return res.status(200).json({ + success: true, + description: `Recalced scores.`, + body: {}, + }); +}); + export default router; From 4bf39da0beea8455ebe7e1a81a897efadf62f561 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Fri, 10 Dec 2021 15:07:07 +0000 Subject: [PATCH 13/32] bump tachi-common --- server/package.json | 2 +- server/pnpm-lock.yaml | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/server/package.json b/server/package.json index 35217ff98..83d3f9cd9 100644 --- a/server/package.json +++ b/server/package.json @@ -83,7 +83,7 @@ "rimraf": "3.0.2", "safe-json-stringify": "1.2.0", "seq-logging": "^1.1.1", - "tachi-common": "0.3.15", + "tachi-common": "0.3.16", "ts-node": "10.0.0", "typescript": "4.3.4", "winston": "3.3.3", diff --git a/server/pnpm-lock.yaml b/server/pnpm-lock.yaml index 388a72a50..fd4000f6a 100644 --- a/server/pnpm-lock.yaml +++ b/server/pnpm-lock.yaml @@ -63,7 +63,7 @@ specifiers: safe-json-stringify: 1.2.0 seq-logging: ^1.1.1 supertest: 6.1.3 - tachi-common: 0.3.15 + tachi-common: 0.3.16 tap: 15.0.9 ts-node: 10.0.0 tsconfig-paths: 3.10.1 @@ -107,7 +107,7 @@ dependencies: rimraf: 3.0.2 safe-json-stringify: 1.2.0 seq-logging: 1.1.1 - tachi-common: 0.3.15 + tachi-common: 0.3.16 ts-node: 10.0.0_83f53b0a0c5616d3fa00ed4e30b9ce1b typescript: 4.3.4 winston: 3.3.3 @@ -5387,8 +5387,8 @@ packages: strip-ansi: 6.0.0 dev: true - /tachi-common/0.3.15: - resolution: {integrity: sha512-ZxdS+vIq9sFgpHxIn0VkaMUFcWXWsr+eHkn2HISq10Cudjv8uGzREGFSFN64e8YOKyPNYMMVi6mSoVyvY08ang==} + /tachi-common/0.3.16: + resolution: {integrity: sha512-cCTD+gKWRvOuQAUEf9MNkIAt961JKu/pHOF/Sw+OoMNxAQfiJ9qwjsIF9YXBX16r0q+Olh1Ps+ImaP3j5/PLRQ==} dependencies: monk: 7.3.4 transitivePeerDependencies: From c30c5e2dbce2e045caceaa6355977dff2340812e Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Fri, 10 Dec 2021 18:24:28 +0000 Subject: [PATCH 14/32] LR2Hook Converter and Parser definitions --- server/package.json | 2 +- server/pnpm-lock.yaml | 8 +- .../score-import/import-types/common/types.ts | 3 + .../score-import/import-types/converters.ts | 2 + .../import-types/ir/lr2hook/converter.test.ts | 61 ++++++++++++ .../import-types/ir/lr2hook/converter.ts | 86 +++++++++++++++++ .../import-types/ir/lr2hook/parser.test.ts | 92 +++++++++++++++++++ .../import-types/ir/lr2hook/parser.ts | 59 ++++++++++++ .../import-types/ir/lr2hook/types.ts | 23 +++++ .../lib/score-import/import-types/parsers.ts | 2 + server/src/test-utils/test-data.ts | 20 ++++ server/src/utils/misc.ts | 11 ++- 12 files changed, 363 insertions(+), 6 deletions(-) create mode 100644 server/src/lib/score-import/import-types/ir/lr2hook/converter.test.ts create mode 100644 server/src/lib/score-import/import-types/ir/lr2hook/converter.ts create mode 100644 server/src/lib/score-import/import-types/ir/lr2hook/parser.test.ts create mode 100644 server/src/lib/score-import/import-types/ir/lr2hook/parser.ts create mode 100644 server/src/lib/score-import/import-types/ir/lr2hook/types.ts diff --git a/server/package.json b/server/package.json index 83d3f9cd9..dd2e47a3f 100644 --- a/server/package.json +++ b/server/package.json @@ -83,7 +83,7 @@ "rimraf": "3.0.2", "safe-json-stringify": "1.2.0", "seq-logging": "^1.1.1", - "tachi-common": "0.3.16", + "tachi-common": "0.3.17", "ts-node": "10.0.0", "typescript": "4.3.4", "winston": "3.3.3", diff --git a/server/pnpm-lock.yaml b/server/pnpm-lock.yaml index fd4000f6a..399ae5cdf 100644 --- a/server/pnpm-lock.yaml +++ b/server/pnpm-lock.yaml @@ -63,7 +63,7 @@ specifiers: safe-json-stringify: 1.2.0 seq-logging: ^1.1.1 supertest: 6.1.3 - tachi-common: 0.3.16 + tachi-common: 0.3.17 tap: 15.0.9 ts-node: 10.0.0 tsconfig-paths: 3.10.1 @@ -107,7 +107,7 @@ dependencies: rimraf: 3.0.2 safe-json-stringify: 1.2.0 seq-logging: 1.1.1 - tachi-common: 0.3.16 + tachi-common: 0.3.17 ts-node: 10.0.0_83f53b0a0c5616d3fa00ed4e30b9ce1b typescript: 4.3.4 winston: 3.3.3 @@ -5387,8 +5387,8 @@ packages: strip-ansi: 6.0.0 dev: true - /tachi-common/0.3.16: - resolution: {integrity: sha512-cCTD+gKWRvOuQAUEf9MNkIAt961JKu/pHOF/Sw+OoMNxAQfiJ9qwjsIF9YXBX16r0q+Olh1Ps+ImaP3j5/PLRQ==} + /tachi-common/0.3.17: + resolution: {integrity: sha512-YL5UlTT5qHSaAzIbSgz2UzagTE9HV++C0XVAopFg8wYwUei2b1R5shwXgzyO0RtZgqVT0ivz2ug+jx+RN37pCw==} dependencies: monk: 7.3.4 transitivePeerDependencies: diff --git a/server/src/lib/score-import/import-types/common/types.ts b/server/src/lib/score-import/import-types/common/types.ts index 05a085e5b..42caab878 100644 --- a/server/src/lib/score-import/import-types/common/types.ts +++ b/server/src/lib/score-import/import-types/common/types.ts @@ -21,6 +21,7 @@ import { BeatorajaContext, BeatorajaScore } from "../ir/beatoraja/types"; import { FervidexStaticContext, FervidexStaticScore } from "../ir/fervidex-static/types"; import { FervidexContext, FervidexScore } from "../ir/fervidex/types"; import { KsHookSV3CContext, KsHookSV3CScore } from "../ir/kshook-sv3c/types"; +import { LR2HookContext, LR2HookScore } from "../ir/lr2hook/types"; import { IRUSCContext } from "../ir/usc/types"; import { KaiContext } from "./api-kai/types"; import { BatchManualContext } from "./batch-manual/types"; @@ -41,6 +42,7 @@ export interface ImportTypeDataMap { "ir/beatoraja": BeatorajaScore; "ir/usc": USCClientScore; "ir/kshook-sv3c": KsHookSV3CScore; + "ir/lr2hook": LR2HookScore; // These aren't placeholder values - the data is yielded in a way that // the value of these is legitimately unknown at convert time. @@ -68,6 +70,7 @@ export interface ImportTypeContextMap { "ir/beatoraja": BeatorajaContext; "ir/usc": IRUSCContext; "ir/kshook-sv3c": KsHookSV3CContext; + "ir/lr2hook": LR2HookContext; "api/arc-iidx": EmptyObject; "api/arc-sdvx": EmptyObject; diff --git a/server/src/lib/score-import/import-types/converters.ts b/server/src/lib/score-import/import-types/converters.ts index acf58c102..15b56ddde 100644 --- a/server/src/lib/score-import/import-types/converters.ts +++ b/server/src/lib/score-import/import-types/converters.ts @@ -14,6 +14,7 @@ import { ConverterIRBeatoraja } from "./ir/beatoraja/converter"; import { ConverterIRFervidexStatic } from "./ir/fervidex-static/converter"; import { ConverterIRFervidex } from "./ir/fervidex/converter"; import { ConverterIRKsHookSV3C } from "./ir/kshook-sv3c/converter"; +import { ConverterLR2Hook } from "./ir/lr2hook/converter"; import { ConverterIRUSC } from "./ir/usc/converter"; export type ConverterMap = { @@ -43,4 +44,5 @@ export const Converters: ConverterMap = { "ir/direct-manual": ConverterBatchManual, "ir/usc": ConverterIRUSC, "ir/kshook-sv3c": ConverterIRKsHookSV3C, + "ir/lr2hook": ConverterLR2Hook, }; diff --git a/server/src/lib/score-import/import-types/ir/lr2hook/converter.test.ts b/server/src/lib/score-import/import-types/ir/lr2hook/converter.test.ts new file mode 100644 index 000000000..ec70ad485 --- /dev/null +++ b/server/src/lib/score-import/import-types/ir/lr2hook/converter.test.ts @@ -0,0 +1,61 @@ +import CreateLogCtx from "lib/logger/logger"; +import t from "tap"; +import ResetDBState from "test-utils/resets"; +import { TestingLR2HookScore } from "test-utils/test-data"; +import { ConverterLR2Hook } from "./converter"; + +const logger = CreateLogCtx(__filename); + +t.test("#ConverterLR2Hook", (t) => { + t.beforeEach(ResetDBState); + + t.test("Should match a score with its song and chart.", async (t) => { + const res = await ConverterLR2Hook( + TestingLR2HookScore, + { timeReceived: 10 }, + "ir/lr2hook", + logger + ); + + t.hasStrict(res, { + song: { + id: 27339, + }, + chart: { + chartID: "88eb6cc5683e2740cbd07f588a5f3db1db8d467b", + data: { + hashMD5: TestingLR2HookScore.md5, + }, + }, + dryScore: { + scoreData: { + score: TestingLR2HookScore.scoreData.exScore, + }, + game: "bms", + importType: "ir/lr2hook", + scoreMeta: { + client: "LR2", + }, + }, + }); + + t.end(); + }); + + t.test("Should throw an error if song or chart can't be found.", (t) => { + t.rejects( + () => + ConverterLR2Hook( + Object.assign({}, TestingLR2HookScore, { md5: "nonsense_md5" }), + { timeReceived: 10 }, + "ir/lr2hook", + logger + ), + "Should throw a KTDataNotFoundError if chart can't be found." + ); + + t.end(); + }); + + t.end(); +}); diff --git a/server/src/lib/score-import/import-types/ir/lr2hook/converter.ts b/server/src/lib/score-import/import-types/ir/lr2hook/converter.ts new file mode 100644 index 000000000..036bc8e6e --- /dev/null +++ b/server/src/lib/score-import/import-types/ir/lr2hook/converter.ts @@ -0,0 +1,86 @@ +import { Lamps } from ".pnpm/tachi-common@0.3.16/node_modules/tachi-common"; +import { + InternalFailure, + KTDataNotFoundFailure, +} from "lib/score-import/framework/common/converter-failures"; +import { GenericGetGradeAndPercent } from "lib/score-import/framework/common/score-utils"; +import { DryScore } from "lib/score-import/framework/common/types"; +import { FindBMSChartOnHash } from "utils/queries/charts"; +import { FindSongOnID } from "utils/queries/songs"; +import { ConverterFunction } from "../../common/types"; +import { LR2HookContext, LR2HookScore } from "./types"; + +export const ConverterLR2Hook: ConverterFunction = async ( + data, + context, + importType, + logger +) => { + const chart = await FindBMSChartOnHash(data.md5); + + if (!chart) { + throw new KTDataNotFoundFailure( + `Could not find chart with md5 ${data.md5}.`, + importType, + data, + context + ); + } + + const song = await FindSongOnID("bms", chart.songID); + + if (!song) { + logger.severe(`Song ${chart.songID} (bms) has no parent song?`); + throw new InternalFailure(`Song ${chart.songID} (bms) has no parent song?`); + } + + const { percent, grade } = GenericGetGradeAndPercent("bms", data.scoreData.exScore, chart); + + const dryScore: DryScore<"bms:7K" | "bms:14K"> = { + game: "bms", + service: "LR2Hook", + comment: null, + importType: "ir/lr2hook", + timeAchieved: context.timeReceived, + scoreData: { + score: data.scoreData.exScore, + percent, + grade, + lamp: ConvertLamp(data.scoreData.lamp), + judgements: { + pgreat: data.scoreData.pgreat, + great: data.scoreData.great, + good: data.scoreData.good, + bad: data.scoreData.bad, + poor: data.scoreData.poor, + }, + hitMeta: { + bp: data.scoreData.bad + data.scoreData.poor, + maxCombo: data.scoreData.maxCombo, + gauge: data.scoreData.hpGraph[999] ?? 0, + }, + }, + scoreMeta: { + client: "LR2", + }, + }; + + return { song, chart, dryScore }; +}; + +function ConvertLamp(lamp: LR2HookScore["scoreData"]["lamp"]): Lamps["bms:7K" | "bms:14K"] { + switch (lamp) { + case "EASY": + return "EASY CLEAR"; + case "FAIL": + return "FAILED"; + case "FULL COMBO": + return "FULL COMBO"; + case "HARD": + return "HARD CLEAR"; + case "NO PLAY": + return "NO PLAY"; + case "NORMAL": + return "CLEAR"; + } +} diff --git a/server/src/lib/score-import/import-types/ir/lr2hook/parser.test.ts b/server/src/lib/score-import/import-types/ir/lr2hook/parser.test.ts new file mode 100644 index 000000000..24b3d4b3b --- /dev/null +++ b/server/src/lib/score-import/import-types/ir/lr2hook/parser.test.ts @@ -0,0 +1,92 @@ +import CreateLogCtx from "lib/logger/logger"; +import t from "tap"; +import { ParseLR2Hook } from "./parser"; +import deepmerge from "deepmerge"; +import { TestingLR2HookScore } from "test-utils/test-data"; +import { ApplyNTimes } from "utils/misc"; + +const logger = CreateLogCtx(__filename); + +t.test("#ParseLR2Hook", (t) => { + const assertFail = (data: any, message: string) => { + t.throws(() => ParseLR2Hook(data, logger), message); + }; + + const assertSuccess = (data: any, message: string) => { + try { + t.doesNotThrow(() => ParseLR2Hook(data, logger), message); + + const res = ParseLR2Hook(data, logger); + + t.equal(res.game, "bms"); + t.type(res.context.timeReceived, "number"); + t.ok(Array.isArray(res.iterable)); + t.equal(res.classHandler, null); + } catch (err) { + t.fail(`[${message}] ${err.message}`); + } + }; + + const dm = (data: any) => deepmerge(TestingLR2HookScore, data); + const dms = (data: any) => + deepmerge( + TestingLR2HookScore, + { scoreData: data }, + { + arrayMerge: (a, b) => b, + } + ); + + assertSuccess(TestingLR2HookScore, "Should parse a valid score."); + assertSuccess( + dm({ unexpectedField: "foo" }), + "Should allow excess keys that we do not recognise." + ); + assertSuccess( + dm({ scoreData: { unexpectedField: "foo" } }), + "Should allow excess keys inside scoreData that we do not recognise." + ); + + assertFail({}, "Should reject an empty object"); + + for (const key of [ + "pgreat", + "good", + "bad", + "poor", + "great", + "maxCombo", + "exScore", + "notesTotal", + "notesPlayed", + ]) { + assertFail(dms({ [key]: -1 }), `Should reject negative ${key}`); + assertFail(dms({ [key]: 0.5 }), `Should reject decimal ${key}`); + assertFail(dms({ [key]: "0" }), `Should reject string ${key}`); + assertFail(dms({ [key]: null }), `Should reject nonsense ${key}`); + } + + assertFail(dms({ lamp: "UNKNOWN_LAMP" }), "Should reject unknown lamp."); + assertFail(dms({ lamp: null }), "Should reject null lamp."); + assertFail(dms({ lamp: undefined }), "Should reject no lamp."); + + assertFail( + dms({ hpGraph: ApplyNTimes(999, () => 50) }), + "Should disallow hp graph with <1000 elements." + ); + assertFail(dms({ hpGraph: [] }), "Should disallow hp graph with 0 elements."); + assertFail( + dms({ hpGraph: ApplyNTimes(1001, () => 50) }), + "Should disallow hp graph with >1000 elements." + ); + assertFail( + dms({ hpGraph: ApplyNTimes(1000, () => 101) }), + "Should disallow hp graph with elements larger than 100." + ); + assertFail( + dms({ hpGraph: ApplyNTimes(1000, () => -1) }), + "Should disallow hp graph with negative element values." + ); + + t.end(); +}); diff --git a/server/src/lib/score-import/import-types/ir/lr2hook/parser.ts b/server/src/lib/score-import/import-types/ir/lr2hook/parser.ts new file mode 100644 index 000000000..5676497ca --- /dev/null +++ b/server/src/lib/score-import/import-types/ir/lr2hook/parser.ts @@ -0,0 +1,59 @@ +import { KtLogger } from "lib/logger/logger"; +import { ParserFunctionReturns } from "../../common/types"; +import { LR2HookContext, LR2HookScore } from "./types"; +import p, { PrudenceSchema } from "prudence"; +import ScoreImportFatalError from "lib/score-import/framework/score-importing/score-import-error"; +import { FormatPrError } from "utils/prudence"; + +const PR_LR2Hook: PrudenceSchema = { + md5: "string", + scoreData: { + pgreat: p.isPositiveInteger, + great: p.isPositiveInteger, + good: p.isPositiveInteger, + bad: p.isPositiveInteger, + poor: p.isPositiveInteger, + maxCombo: p.isPositiveInteger, + exScore: p.isPositiveInteger, + moneyScore: p.isPositiveInteger, + notesTotal: p.isPositiveInteger, + notesPlayed: p.isPositiveInteger, + lamp: p.isIn("NO PLAY", "FAIL", "EASY", "NORMAL", "HARD", "FULL COMBO"), + hpGraph: (self) => { + if (!Array.isArray(self)) { + return "Expected an array"; + } + if (self.length !== 1000) { + return "Expected an array with length 1000."; + } + if (self.some((x) => x < 0 || x > 100 || !Number.isInteger(x))) { + return "Expected an array of 1000 integers between 0 and 100."; + } + + return true; + }, + }, +}; + +export function ParseLR2Hook( + body: Record, + logger: KtLogger +): ParserFunctionReturns { + // Ignore excess keys, as lr2hook is likely to add more features in the future. + const err = p(body, PR_LR2Hook, undefined, { allowExcessKeys: true }); + + if (err) { + throw new ScoreImportFatalError(400, FormatPrError(err)); + } + + const score = body as unknown as LR2HookScore; + + return { + game: "bms", + iterable: [score], + context: { + timeReceived: Date.now(), + }, + classHandler: null, + }; +} diff --git a/server/src/lib/score-import/import-types/ir/lr2hook/types.ts b/server/src/lib/score-import/import-types/ir/lr2hook/types.ts new file mode 100644 index 000000000..59ff88010 --- /dev/null +++ b/server/src/lib/score-import/import-types/ir/lr2hook/types.ts @@ -0,0 +1,23 @@ +import { integer } from "tachi-common"; + +export interface LR2HookScore { + md5: string; + scoreData: { + pgreat: integer; + great: integer; + good: integer; + bad: integer; + poor: integer; + maxCombo: integer; + exScore: integer; + moneyScore: integer; + notesTotal: integer; + notesPlayed: integer; + lamp: "NO PLAY" | "FAIL" | "EASY" | "NORMAL" | "HARD" | "FULL COMBO"; + hpGraph: integer[]; + }; +} + +export interface LR2HookContext { + timeReceived: number; +} diff --git a/server/src/lib/score-import/import-types/parsers.ts b/server/src/lib/score-import/import-types/parsers.ts index 842781a87..b0d554b3a 100644 --- a/server/src/lib/score-import/import-types/parsers.ts +++ b/server/src/lib/score-import/import-types/parsers.ts @@ -17,6 +17,7 @@ import ParseDirectManual from "./ir/direct-manual/parser"; import { ParseFervidexStatic } from "./ir/fervidex-static/parser"; import { ParseFervidexSingle } from "./ir/fervidex/parser"; import { ParseKsHookSV3C } from "./ir/kshook-sv3c/parser"; +import { ParseLR2Hook } from "./ir/lr2hook/parser"; import { ParseIRUSC } from "./ir/usc/parser"; export const Parsers = { @@ -42,4 +43,5 @@ export const Parsers = { "ir/direct-manual": ParseDirectManual, "ir/usc": ParseIRUSC, "ir/kshook-sv3c": ParseKsHookSV3C, + "ir/lr2hook": ParseLR2Hook, }; diff --git a/server/src/test-utils/test-data.ts b/server/src/test-utils/test-data.ts index abeb8b1b9..23d924437 100644 --- a/server/src/test-utils/test-data.ts +++ b/server/src/test-utils/test-data.ts @@ -10,6 +10,7 @@ import { import { DryScore } from "lib/score-import/framework/common/types"; import { BarbatosScore } from "lib/score-import/import-types/ir/barbatos/types"; import { KsHookSV3CScore } from "lib/score-import/import-types/ir/kshook-sv3c/types"; +import { LR2HookScore } from "lib/score-import/import-types/ir/lr2hook/types"; import path from "path"; import { USCClientScore } from "server/router/ir/usc/_playtype/types"; import { @@ -22,6 +23,7 @@ import { SongDocument, UserGoalDocument, } from "tachi-common"; +import { ApplyNTimes, RFA } from "utils/misc"; const file = (name: string) => path.join(__dirname, "/test-data", name); @@ -41,6 +43,24 @@ export const TestingIIDXSPScore = GetKTDataJSON( export const TestingKsHookSV3CScore = GetKTDataJSON("./kshook-sv3c/base.json") as KsHookSV3CScore; +export const TestingLR2HookScore: LR2HookScore = { + md5: "38616b85332037cc12924f2ae2840262", + scoreData: { + pgreat: 1000, + great: 500, + good: 100, + bad: 50, + poor: 25, + exScore: 2500, + maxCombo: 50, + moneyScore: 150_000, + notesTotal: 2256, + notesPlayed: 2256, + lamp: "HARD", + hpGraph: ApplyNTimes(1000, () => RFA([100, 50, 80, 0])), + }, +}; + export const Testing511SPA = GetKTDataJSON("./tachi/iidx-511spa.json") as ChartDocument<"iidx:SP">; export const Testing511Song = GetKTDataJSON("./tachi/iidx-511-song.json") as SongDocument<"iidx">; diff --git a/server/src/utils/misc.ts b/server/src/utils/misc.ts index 67f00cfbb..2d6563d10 100644 --- a/server/src/utils/misc.ts +++ b/server/src/utils/misc.ts @@ -37,7 +37,7 @@ export function MStoS(ms: number) { /** * Random From Array - Selects a random value from an array. */ -export function RFA(arr: unknown[]) { +export function RFA(arr: T[]): T { return arr[Math.floor(Math.random() * arr.length)]; } @@ -133,3 +133,12 @@ export function Sleep(ms: number) { export function GetTimeXHoursAgo(hours: integer) { return Date.now() - ONE_HOUR * hours; } + +export function ApplyNTimes(n: integer, fn: (i: integer) => T): T[] { + const arr = []; + for (let i = 0; i < n; i++) { + arr.push(fn(i)); + } + + return arr; +} From 9a4605972edaabeda0fcee71a0966b6bf6030e14 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Fri, 10 Dec 2021 18:25:11 +0000 Subject: [PATCH 15/32] KsHook is a kt specific IR. --- server/src/server/router/ir/router.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/server/src/server/router/ir/router.ts b/server/src/server/router/ir/router.ts index 67c864d13..f2392368d 100644 --- a/server/src/server/router/ir/router.ts +++ b/server/src/server/router/ir/router.ts @@ -18,7 +18,6 @@ const router: Router = Router({ mergeParams: true }); // Common IRs router.use("/direct-manual", SetRequestPermissions, RequireNotGuest, directManualIR); -router.use("/kshook", SetFervidexStyleRequestPermissions, FervidexStyleRequireNotGuest, ksHookIR); // Bokutachi IRs @@ -30,6 +29,13 @@ router.use("/beatoraja", SetRequestPermissions, RequireBokutachi, beatorajaIR); // Kamaitachi IRs +router.use( + "/kshook", + RequireKamaitachi, + SetFervidexStyleRequestPermissions, + FervidexStyleRequireNotGuest, + ksHookIR +); router.use("/barbatos", SetRequestPermissions, RequireNotGuest, RequireKamaitachi, barbatosIR); router.use( "/fervidex", From 6f3864793ccf6bc3e41995b62e964548559dea9e Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Fri, 10 Dec 2021 18:50:20 +0000 Subject: [PATCH 16/32] LR2Hook mounting and basic tests --- .../server/router/ir/lr2hook/router.test.ts | 61 +++++++++++++++++++ server/src/server/router/ir/lr2hook/router.ts | 26 ++++++++ server/src/server/router/ir/router.ts | 6 +- 3 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 server/src/server/router/ir/lr2hook/router.test.ts create mode 100644 server/src/server/router/ir/lr2hook/router.ts diff --git a/server/src/server/router/ir/lr2hook/router.test.ts b/server/src/server/router/ir/lr2hook/router.test.ts new file mode 100644 index 000000000..1f39fcb5d --- /dev/null +++ b/server/src/server/router/ir/lr2hook/router.test.ts @@ -0,0 +1,61 @@ +import db from "external/mongo/db"; +import t from "tap"; +import mockApi from "test-utils/mock-api"; +import ResetDBState from "test-utils/resets"; +import { TestingLR2HookScore } from "test-utils/test-data"; + +t.test("POST /ir/lr2hook/import", (t) => { + t.beforeEach(ResetDBState); + t.beforeEach(async () => { + await db["api-tokens"].insert({ + token: "foo", + permissions: { submit_score: true }, + fromAPIClient: null, + identifier: "foo", + userID: 1, + }); + }); + + t.test("Should import a score if the API token has the right permissions.", async (t) => { + const res = await mockApi + .post("/ir/lr2hook/import") + .set("Authorization", "Bearer foo") + .send(TestingLR2HookScore); + + t.equal(res.statusCode, 200); + + t.end(); + }); + + t.test("Should import a score if the API token has the right permissions.", async (t) => { + await db["api-tokens"].insert({ + token: "bar", + permissions: { submit_score: false }, + fromAPIClient: null, + identifier: "foo", + userID: 1, + }); + + const res = await mockApi + .post("/ir/lr2hook/import") + .set("Authorization", "Bearer bar") + .send(TestingLR2HookScore); + + t.equal(res.statusCode, 403); + + t.end(); + }); + + t.test("Should import a score if the API token has the right permissions.", async (t) => { + const res = await mockApi + .post("/ir/lr2hook/import") + .set("Authorization", "Bearer unknown token") + .send(TestingLR2HookScore); + + t.equal(res.statusCode, 401); + + t.end(); + }); + + t.end(); +}); diff --git a/server/src/server/router/ir/lr2hook/router.ts b/server/src/server/router/ir/lr2hook/router.ts new file mode 100644 index 000000000..1ff2458ae --- /dev/null +++ b/server/src/server/router/ir/lr2hook/router.ts @@ -0,0 +1,26 @@ +import { Router } from "express"; +import { SYMBOL_TachiAPIAuth } from "lib/constants/tachi"; +import { ExpressWrappedScoreImportMain } from "lib/score-import/framework/express-wrapper"; +import { RequirePermissions } from "server/middleware/auth"; + +const router: Router = Router({ mergeParams: true }); + +router.use(RequirePermissions("submit_score")); + +/** + * Import a score with the LR2Hook Format. + * + * @name POST /ir/lr2hook/import + */ +router.post("/import", async (req, res) => { + const importRes = await ExpressWrappedScoreImportMain( + req[SYMBOL_TachiAPIAuth].userID!, + false, + "ir/lr2hook", + [req.body] + ); + + return res.status(importRes.statusCode).json(importRes.body); +}); + +export default router; diff --git a/server/src/server/router/ir/router.ts b/server/src/server/router/ir/router.ts index f2392368d..c6b76b6c8 100644 --- a/server/src/server/router/ir/router.ts +++ b/server/src/server/router/ir/router.ts @@ -12,6 +12,7 @@ import directManualIR from "./direct-manual/router"; import fervidexIR from "./fervidex/router"; import ksHookIR from "./kshook/router"; import uscIR from "./usc/router"; +import lr2hookIR from "./lr2hook/router"; const router: Router = Router({ mergeParams: true }); @@ -24,8 +25,9 @@ router.use("/direct-manual", SetRequestPermissions, RequireNotGuest, directManua // note: this is the only IR that cannot use SetRequestPermissions for its // auth, because the USCIR spec requires a different set of response // codes for auth. -router.use("/usc", RequireBokutachi, uscIR); -router.use("/beatoraja", SetRequestPermissions, RequireBokutachi, beatorajaIR); +router.use("/usc", RequireNotGuest, RequireBokutachi, uscIR); +router.use("/beatoraja", SetRequestPermissions, RequireNotGuest, RequireBokutachi, beatorajaIR); +router.use("/lr2hook", SetRequestPermissions, RequireNotGuest, RequireBokutachi, lr2hookIR); // Kamaitachi IRs From 362c72be0de78bcf5e21c239b5d4bd9ddf15c126 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Fri, 10 Dec 2021 19:18:01 +0000 Subject: [PATCH 17/32] thanks vscode --- .../src/lib/score-import/import-types/ir/lr2hook/converter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/lib/score-import/import-types/ir/lr2hook/converter.ts b/server/src/lib/score-import/import-types/ir/lr2hook/converter.ts index 036bc8e6e..1addeac09 100644 --- a/server/src/lib/score-import/import-types/ir/lr2hook/converter.ts +++ b/server/src/lib/score-import/import-types/ir/lr2hook/converter.ts @@ -1,4 +1,4 @@ -import { Lamps } from ".pnpm/tachi-common@0.3.16/node_modules/tachi-common"; +import { Lamps } from "tachi-common"; import { InternalFailure, KTDataNotFoundFailure, From a5d1d2739505484aa153644fb82c521c76407482 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Fri, 10 Dec 2021 19:27:43 +0000 Subject: [PATCH 18/32] Remove RequireGuest from the USC IR mountpoint --- server/src/server/router/ir/router.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/server/router/ir/router.ts b/server/src/server/router/ir/router.ts index c6b76b6c8..5dd7ce82c 100644 --- a/server/src/server/router/ir/router.ts +++ b/server/src/server/router/ir/router.ts @@ -25,7 +25,7 @@ router.use("/direct-manual", SetRequestPermissions, RequireNotGuest, directManua // note: this is the only IR that cannot use SetRequestPermissions for its // auth, because the USCIR spec requires a different set of response // codes for auth. -router.use("/usc", RequireNotGuest, RequireBokutachi, uscIR); +router.use("/usc", RequireBokutachi, uscIR); router.use("/beatoraja", SetRequestPermissions, RequireNotGuest, RequireBokutachi, beatorajaIR); router.use("/lr2hook", SetRequestPermissions, RequireNotGuest, RequireBokutachi, lr2hookIR); From 7a555fc8dcd50f39dc644836fcf1c0034d669887 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Fri, 10 Dec 2021 19:28:47 +0000 Subject: [PATCH 19/32] Fix potential test collision in lr2hook api key insertion --- server/src/server/router/ir/lr2hook/router.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/server/router/ir/lr2hook/router.test.ts b/server/src/server/router/ir/lr2hook/router.test.ts index 1f39fcb5d..b97c1b146 100644 --- a/server/src/server/router/ir/lr2hook/router.test.ts +++ b/server/src/server/router/ir/lr2hook/router.test.ts @@ -32,7 +32,7 @@ t.test("POST /ir/lr2hook/import", (t) => { token: "bar", permissions: { submit_score: false }, fromAPIClient: null, - identifier: "foo", + identifier: "bar", userID: 1, }); From 93e2af013819b5760ec7a218de2222b24df39887 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Fri, 10 Dec 2021 19:42:25 +0000 Subject: [PATCH 20/32] Fix two more test failures --- .../router/ir/beatoraja/charts/router.test.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/server/src/server/router/ir/beatoraja/charts/router.test.ts b/server/src/server/router/ir/beatoraja/charts/router.test.ts index 38c3086ca..facf69ca8 100644 --- a/server/src/server/router/ir/beatoraja/charts/router.test.ts +++ b/server/src/server/router/ir/beatoraja/charts/router.test.ts @@ -6,6 +6,17 @@ import ResetDBState from "test-utils/resets"; t.test("GET /ir/beatoraja/charts/:chartSHA256/scores", (t) => { t.beforeEach(ResetDBState); + t.beforeEach(() => + db["api-tokens"].insert({ + userID: 1, + identifier: "Mock API Beatoraja Token", + permissions: { + submit_score: true, + }, + token: "mock_token", + fromAPIClient: null, + }) + ); const GAZER_SHA256 = "195fe1be5c3e74fccd04dc426e05f8a9cfa8a1059c339d0a23e99f63661f0b7d"; const GAZER_CHARTID = "88eb6cc5683e2740cbd07f588a5f3db1db8d467b"; @@ -35,6 +46,7 @@ t.test("GET /ir/beatoraja/charts/:chartSHA256/scores", (t) => { const res = await mockApi .get(`/ir/beatoraja/charts/${GAZER_SHA256}/scores`) + .set("Authorization", "Bearer mock_token") .set("X-TachiIR-Version", "v2.0.0"); t.equal(res.status, 200); @@ -45,6 +57,7 @@ t.test("GET /ir/beatoraja/charts/:chartSHA256/scores", (t) => { t.test("Should return 404 if chart doesnt exist", async (t) => { const res = await mockApi .get(`/ir/beatoraja/charts/INVALID/scores`) + .set("Authorization", "Bearer mock_token") .set("X-TachiIR-Version", "v2.0.0"); t.equal(res.status, 404); From f646c12a826653ff95fe4c6f1bf3aea31ff65913 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Fri, 10 Dec 2021 19:45:36 +0000 Subject: [PATCH 21/32] Fix api token collision --- .../router/ir/usc/_playtype/router.test.ts | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/server/src/server/router/ir/usc/_playtype/router.test.ts b/server/src/server/router/ir/usc/_playtype/router.test.ts index 2b279d756..8fd934596 100644 --- a/server/src/server/router/ir/usc/_playtype/router.test.ts +++ b/server/src/server/router/ir/usc/_playtype/router.test.ts @@ -71,17 +71,18 @@ t.test("GET /ir/usc/Keyboard", async (t) => { }); t.test("GET /ir/usc/Controller", async (t) => { - await db["api-tokens"].insert({ - userID: 1, - identifier: "USC Token", - permissions: { - submit_score: true, - }, - token: "foo", - fromAPIClient: null, - }); - t.beforeEach(ResetDBState); + t.beforeEach(async () => { + await db["api-tokens"].insert({ + userID: 1, + identifier: "USC Token", + permissions: { + submit_score: true, + }, + token: "foo", + fromAPIClient: null, + }); + }); TestAuth("/ir/usc/Controller"); From 612a3de389c8f8143d63a61daddcb5142c8a480f Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Sat, 11 Dec 2021 23:49:17 +0000 Subject: [PATCH 22/32] Add the new fields for the lr2hook. --- .../import-types/ir/lr2hook/converter.ts | 19 +++++++++++++- .../import-types/ir/lr2hook/parser.ts | 25 ++++++++++++++++++- .../import-types/ir/lr2hook/types.ts | 5 ++++ 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/server/src/lib/score-import/import-types/ir/lr2hook/converter.ts b/server/src/lib/score-import/import-types/ir/lr2hook/converter.ts index 1addeac09..dde272d4a 100644 --- a/server/src/lib/score-import/import-types/ir/lr2hook/converter.ts +++ b/server/src/lib/score-import/import-types/ir/lr2hook/converter.ts @@ -1,6 +1,7 @@ -import { Lamps } from "tachi-common"; +import { Lamps, ScoreDocument } from "tachi-common"; import { InternalFailure, + InvalidScoreFailure, KTDataNotFoundFailure, } from "lib/score-import/framework/common/converter-failures"; import { GenericGetGradeAndPercent } from "lib/score-import/framework/common/score-utils"; @@ -61,6 +62,7 @@ export const ConverterLR2Hook: ConverterFunction = }, }, scoreMeta: { + random: chart.playtype === "7K" ? ConvertRandom(data.playerData.random) : null, client: "LR2", }, }; @@ -68,6 +70,21 @@ export const ConverterLR2Hook: ConverterFunction = return { song, chart, dryScore }; }; +function ConvertRandom( + random: LR2HookScore["playerData"]["random"] +): ScoreDocument<"bms:7K">["scoreMeta"]["random"] { + switch (random) { + case "NONRAN": + return "NONRAN"; + case "MIRROR": + return "MIRROR"; + case "RAN": + return "RANDOM"; + case "S-RAN": + return "S-RANDOM"; + } +} + function ConvertLamp(lamp: LR2HookScore["scoreData"]["lamp"]): Lamps["bms:7K" | "bms:14K"] { switch (lamp) { case "EASY": diff --git a/server/src/lib/score-import/import-types/ir/lr2hook/parser.ts b/server/src/lib/score-import/import-types/ir/lr2hook/parser.ts index 5676497ca..7747a3071 100644 --- a/server/src/lib/score-import/import-types/ir/lr2hook/parser.ts +++ b/server/src/lib/score-import/import-types/ir/lr2hook/parser.ts @@ -5,8 +5,18 @@ import p, { PrudenceSchema } from "prudence"; import ScoreImportFatalError from "lib/score-import/framework/score-importing/score-import-error"; import { FormatPrError } from "utils/prudence"; +const SUPPORTED_RANDOMS = ["NONRAN", "MIRROR", "RAN", "S-RAN"]; + const PR_LR2Hook: PrudenceSchema = { md5: "string", + playerData: { + autoScr: p.is(false), + // don't really need this, but it's + // { "ALL", "SINGLE", "7K", "5K", "DOUBLE", "14K", "10K", "9K" }; + gameMode: p.any, + // ALLSCR and H-RAN may also be sent, but we don't support them. + random: p.isIn(SUPPORTED_RANDOMS), + }, scoreData: { pgreat: p.isPositiveInteger, great: p.isPositiveInteger, @@ -40,7 +50,20 @@ export function ParseLR2Hook( logger: KtLogger ): ParserFunctionReturns { // Ignore excess keys, as lr2hook is likely to add more features in the future. - const err = p(body, PR_LR2Hook, undefined, { allowExcessKeys: true }); + const err = p( + body, + PR_LR2Hook, + { + playerData: { + autoScr: + "Auto Scratch cannot be turned on, as it is treated as an EASY CLEAR by LR2.", + random: `Expected any of ${SUPPORTED_RANDOMS.join( + ", " + )}. Note that ALLSCR and H-RAN are not supported!`, + }, + }, + { allowExcessKeys: true } + ); if (err) { throw new ScoreImportFatalError(400, FormatPrError(err)); diff --git a/server/src/lib/score-import/import-types/ir/lr2hook/types.ts b/server/src/lib/score-import/import-types/ir/lr2hook/types.ts index 59ff88010..cf66aac8c 100644 --- a/server/src/lib/score-import/import-types/ir/lr2hook/types.ts +++ b/server/src/lib/score-import/import-types/ir/lr2hook/types.ts @@ -2,6 +2,11 @@ import { integer } from "tachi-common"; export interface LR2HookScore { md5: string; + playerData: { + autoScr: boolean; + gameMode: unknown; + random: "NONRAN" | "MIRROR" | "RAN" | "S-RAN"; + }; scoreData: { pgreat: integer; great: integer; From e79220b038362e61fdf761658f01e366de422e50 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Sat, 11 Dec 2021 23:49:44 +0000 Subject: [PATCH 23/32] Update test data --- server/src/test-utils/test-data.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/server/src/test-utils/test-data.ts b/server/src/test-utils/test-data.ts index 23d924437..981b57735 100644 --- a/server/src/test-utils/test-data.ts +++ b/server/src/test-utils/test-data.ts @@ -45,6 +45,11 @@ export const TestingKsHookSV3CScore = GetKTDataJSON("./kshook-sv3c/base.json") a export const TestingLR2HookScore: LR2HookScore = { md5: "38616b85332037cc12924f2ae2840262", + playerData: { + autoScr: false, + gameMode: "7K", + random: "RAN", + }, scoreData: { pgreat: 1000, great: 500, From cc107df08159a21f29ece29cd162b86d386a276e Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Sat, 11 Dec 2021 23:51:29 +0000 Subject: [PATCH 24/32] Add some more tests to ensure rejection of nonsense --- .../import-types/ir/lr2hook/parser.test.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/server/src/lib/score-import/import-types/ir/lr2hook/parser.test.ts b/server/src/lib/score-import/import-types/ir/lr2hook/parser.test.ts index 24b3d4b3b..d84b0cc88 100644 --- a/server/src/lib/score-import/import-types/ir/lr2hook/parser.test.ts +++ b/server/src/lib/score-import/import-types/ir/lr2hook/parser.test.ts @@ -46,6 +46,23 @@ t.test("#ParseLR2Hook", (t) => { dm({ scoreData: { unexpectedField: "foo" } }), "Should allow excess keys inside scoreData that we do not recognise." ); + assertSuccess( + dm({ playerData: { unexpectedField: "foo" } }), + "Should allow excess keys inside playerData that we do not recognise." + ); + + assertFail( + dm({ playerData: { autoScr: true } }), + "Should reject scores where autoScr is set to true." + ); + assertFail( + dm({ playerData: { random: "H-RAN" } }), + "Should reject scores where random is set to H-RAN." + ); + assertFail( + dm({ playerData: { random: "ALLSCR" } }), + "Should reject scores where random is set to ALLSCR" + ); assertFail({}, "Should reject an empty object"); From b3b20c8e145d60634a638e669d73a36d4ce07cc1 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Sun, 12 Dec 2021 01:23:09 +0000 Subject: [PATCH 25/32] Add deletableScores as a new setting. --- server/package.json | 2 +- server/pnpm-lock.yaml | 8 ++++---- server/src/external/mongo/schemas.ts | 1 + server/src/server/router/api/v1/auth/auth.ts | 1 + .../server/router/api/v1/users/_userID/settings/router.ts | 2 ++ server/src/test-utils/mock-db/user-settings.json | 3 ++- 6 files changed, 11 insertions(+), 6 deletions(-) diff --git a/server/package.json b/server/package.json index dd2e47a3f..48eb8cc25 100644 --- a/server/package.json +++ b/server/package.json @@ -83,7 +83,7 @@ "rimraf": "3.0.2", "safe-json-stringify": "1.2.0", "seq-logging": "^1.1.1", - "tachi-common": "0.3.17", + "tachi-common": "0.3.18", "ts-node": "10.0.0", "typescript": "4.3.4", "winston": "3.3.3", diff --git a/server/pnpm-lock.yaml b/server/pnpm-lock.yaml index 399ae5cdf..b6e09de91 100644 --- a/server/pnpm-lock.yaml +++ b/server/pnpm-lock.yaml @@ -63,7 +63,7 @@ specifiers: safe-json-stringify: 1.2.0 seq-logging: ^1.1.1 supertest: 6.1.3 - tachi-common: 0.3.17 + tachi-common: 0.3.18 tap: 15.0.9 ts-node: 10.0.0 tsconfig-paths: 3.10.1 @@ -107,7 +107,7 @@ dependencies: rimraf: 3.0.2 safe-json-stringify: 1.2.0 seq-logging: 1.1.1 - tachi-common: 0.3.17 + tachi-common: 0.3.18 ts-node: 10.0.0_83f53b0a0c5616d3fa00ed4e30b9ce1b typescript: 4.3.4 winston: 3.3.3 @@ -5387,8 +5387,8 @@ packages: strip-ansi: 6.0.0 dev: true - /tachi-common/0.3.17: - resolution: {integrity: sha512-YL5UlTT5qHSaAzIbSgz2UzagTE9HV++C0XVAopFg8wYwUei2b1R5shwXgzyO0RtZgqVT0ivz2ug+jx+RN37pCw==} + /tachi-common/0.3.18: + resolution: {integrity: sha512-Qom4IbPtLv83+6mW2OU3+thdqZIGrcUJaH9vhTWN6FoFTw60l0FjtBGsB7343qwsuK7l9JqcEaEJ/psKTDZb1w==} dependencies: monk: 7.3.4 transitivePeerDependencies: diff --git a/server/src/external/mongo/schemas.ts b/server/src/external/mongo/schemas.ts index d1b7fbb40..af2cb39dd 100644 --- a/server/src/external/mongo/schemas.ts +++ b/server/src/external/mongo/schemas.ts @@ -528,6 +528,7 @@ export const DatabaseSchemas: Record = { developerMode: "boolean", advancedMode: "boolean", contentiousContent: "boolean", + deletableScores: "boolean", }, }), sessions: (self) => { diff --git a/server/src/server/router/api/v1/auth/auth.ts b/server/src/server/router/api/v1/auth/auth.ts index 0271925b5..9b89fff0e 100644 --- a/server/src/server/router/api/v1/auth/auth.ts +++ b/server/src/server/router/api/v1/auth/auth.ts @@ -81,6 +81,7 @@ export const DEFAULT_USER_SETTINGS: UserSettings["preferences"] = { advancedMode: false, invisible: false, contentiousContent: false, + deletableScores: false, }; export function HashPassword(plaintext: string) { diff --git a/server/src/server/router/api/v1/users/_userID/settings/router.ts b/server/src/server/router/api/v1/users/_userID/settings/router.ts index f33393b6c..dd60422bc 100644 --- a/server/src/server/router/api/v1/users/_userID/settings/router.ts +++ b/server/src/server/router/api/v1/users/_userID/settings/router.ts @@ -55,6 +55,7 @@ router.patch( developerMode: "*boolean", contentiousContent: "*boolean", advancedMode: "*boolean", + deletableScores: "*boolean", }), async (req, res) => { const user = req[SYMBOL_TachiData]!.requestedUser!; @@ -64,6 +65,7 @@ router.patch( developerMode: req.body.developerMode, contentiousContent: req.body.contentiousContent, advancedMode: req.body.advancedMode, + deletableScores: req.body.deletableScores, }; DeleteUndefinedProps(preferences); diff --git a/server/src/test-utils/mock-db/user-settings.json b/server/src/test-utils/mock-db/user-settings.json index 374504e92..ba919f0eb 100644 --- a/server/src/test-utils/mock-db/user-settings.json +++ b/server/src/test-utils/mock-db/user-settings.json @@ -5,7 +5,8 @@ "developerMode": true, "invisible": false, "advancedMode": false, - "contentiousContent": false + "contentiousContent": false, + "deletableScores": false } } ] \ No newline at end of file From 3af6d7890a6a9a1238fde2138b03cd7deda3647f Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Sun, 12 Dec 2021 01:43:07 +0000 Subject: [PATCH 26/32] Fix broken tests --- .../router/api/v1/users/_userID/settings/router.test.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/server/src/server/router/api/v1/users/_userID/settings/router.test.ts b/server/src/server/router/api/v1/users/_userID/settings/router.test.ts index 1d1a0ccae..f68fa4f9b 100644 --- a/server/src/server/router/api/v1/users/_userID/settings/router.test.ts +++ b/server/src/server/router/api/v1/users/_userID/settings/router.test.ts @@ -19,6 +19,7 @@ t.test("GET /api/v1/users/:userID/settings", (t) => { developerMode: true, advancedMode: false, contentiousContent: false, + deletableScores: false, }, }); @@ -46,6 +47,7 @@ t.test("PATCH /api/v1/users/:userID/settings", async (t) => { developerMode: false, advancedMode: false, contentiousContent: false, + deletableScores: false, }, }); @@ -58,6 +60,7 @@ t.test("PATCH /api/v1/users/:userID/settings", async (t) => { developerMode: false, advancedMode: false, contentiousContent: false, + deletableScores: false, }, }); @@ -80,6 +83,7 @@ t.test("PATCH /api/v1/users/:userID/settings", async (t) => { developerMode: true, advancedMode: false, contentiousContent: false, + deletableScores: false, }, }, "User Settings should be unmodified." From aee67c5673e472b54b13dfe1231a34b18e7f882e Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Sun, 12 Dec 2021 02:17:24 +0000 Subject: [PATCH 27/32] Set leaderboard cap to 500. --- .../router/api/v1/games/_game/_playtype/router.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/server/src/server/router/api/v1/games/_game/_playtype/router.ts b/server/src/server/router/api/v1/games/_game/_playtype/router.ts index 0307539cd..b9afc9c92 100644 --- a/server/src/server/router/api/v1/games/_game/_playtype/router.ts +++ b/server/src/server/router/api/v1/games/_game/_playtype/router.ts @@ -76,11 +76,11 @@ router.get("/", async (req, res) => { }); /** - * Returns user-game-stats for this game in batches of 100. + * Returns user-game-stats for this game in batches of 500. * This is sorted by the games default-sorting-statistic. * * @param alg - An alternative algorithm to use instead of the gpts default. - * @param limit - How many users to return at most. Defaults (and is limited to) 50. + * @param limit - How many users to return at most. Defaults (and is limited to) 500. * * @name GET /api/v1/games/:game/:playtype/leaderboard */ @@ -89,12 +89,12 @@ router.get("/leaderboard", async (req, res) => { const playtype = req[SYMBOL_TachiData]!.playtype!; const gptConfig = GetGamePTConfig(game, playtype); - const limit = ParseStrPositiveNonZeroInt(req.query.limit) ?? 50; + const limit = ParseStrPositiveNonZeroInt(req.query.limit) ?? 100; - if (limit > 50) { + if (limit > 500) { return res.status(400).json({ success: false, - description: `Invalid limit. Limit is capped at 50.`, + description: `Invalid limit. Limit is capped at 500.`, }); } From 15a982752cd12d841d6b5cc15fdfc385f4467f05 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Mon, 13 Dec 2021 01:05:52 +0000 Subject: [PATCH 28/32] Add new settings. --- server/package.json | 2 +- server/pnpm-lock.yaml | 8 ++++---- server/src/external/mongo/schemas.ts | 1 + server/src/lib/game-settings/create-game-settings.ts | 1 + server/src/lib/showcase/get-stats.test.ts | 1 + .../_userID/games/_game/_playtype/settings/router.test.ts | 3 +++ .../_userID/games/_game/_playtype/settings/router.ts | 7 ++++++- .../_userID/games/_game/_playtype/showcase/router.test.ts | 2 ++ server/src/test-utils/mock-db/game-settings.json | 1 + 9 files changed, 20 insertions(+), 6 deletions(-) diff --git a/server/package.json b/server/package.json index 48eb8cc25..e4f7492eb 100644 --- a/server/package.json +++ b/server/package.json @@ -83,7 +83,7 @@ "rimraf": "3.0.2", "safe-json-stringify": "1.2.0", "seq-logging": "^1.1.1", - "tachi-common": "0.3.18", + "tachi-common": "0.3.19", "ts-node": "10.0.0", "typescript": "4.3.4", "winston": "3.3.3", diff --git a/server/pnpm-lock.yaml b/server/pnpm-lock.yaml index b6e09de91..63b897a34 100644 --- a/server/pnpm-lock.yaml +++ b/server/pnpm-lock.yaml @@ -63,7 +63,7 @@ specifiers: safe-json-stringify: 1.2.0 seq-logging: ^1.1.1 supertest: 6.1.3 - tachi-common: 0.3.18 + tachi-common: 0.3.19 tap: 15.0.9 ts-node: 10.0.0 tsconfig-paths: 3.10.1 @@ -107,7 +107,7 @@ dependencies: rimraf: 3.0.2 safe-json-stringify: 1.2.0 seq-logging: 1.1.1 - tachi-common: 0.3.18 + tachi-common: 0.3.19 ts-node: 10.0.0_83f53b0a0c5616d3fa00ed4e30b9ce1b typescript: 4.3.4 winston: 3.3.3 @@ -5387,8 +5387,8 @@ packages: strip-ansi: 6.0.0 dev: true - /tachi-common/0.3.18: - resolution: {integrity: sha512-Qom4IbPtLv83+6mW2OU3+thdqZIGrcUJaH9vhTWN6FoFTw60l0FjtBGsB7343qwsuK7l9JqcEaEJ/psKTDZb1w==} + /tachi-common/0.3.19: + resolution: {integrity: sha512-N3/n9emh2X9pA+3jQ1UfkLX9V8hk/oHGE1GPlkReRmyIR9gRAxhf3uZCiorPqibSkPzx9LrGLJcO52FR5NQ8oA==} dependencies: monk: 7.3.4 transitivePeerDependencies: diff --git a/server/src/external/mongo/schemas.ts b/server/src/external/mongo/schemas.ts index af2cb39dd..c663cf169 100644 --- a/server/src/external/mongo/schemas.ts +++ b/server/src/external/mongo/schemas.ts @@ -373,6 +373,7 @@ export const DatabaseSchemas: Record = { ], (self) => Array.isArray(self) && self.length <= 6 ), + scoreBucket: p.isIn(null, "grade", "lamp"), gameSpecific: game === "iidx" ? { diff --git a/server/src/lib/game-settings/create-game-settings.ts b/server/src/lib/game-settings/create-game-settings.ts index 19c893a9d..1d9d61e66 100644 --- a/server/src/lib/game-settings/create-game-settings.ts +++ b/server/src/lib/game-settings/create-game-settings.ts @@ -41,6 +41,7 @@ export async function CreateGameSettings(userID: integer, game: Game, playtype: preferredProfileAlg: null, preferredSessionAlg: null, preferredScoreAlg: null, + scoreBucket: null, stats: [], gameSpecific, }, diff --git a/server/src/lib/showcase/get-stats.test.ts b/server/src/lib/showcase/get-stats.test.ts index 5ec2497d6..8d8004696 100644 --- a/server/src/lib/showcase/get-stats.test.ts +++ b/server/src/lib/showcase/get-stats.test.ts @@ -26,6 +26,7 @@ t.test("#EvalulateUsersStatsShowcase", (t) => { preferredProfileAlg: null, preferredScoreAlg: null, preferredSessionAlg: null, + scoreBucket: null, stats: [ { folderID: TestingIIDXFolderSP10.folderID, diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/settings/router.test.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/settings/router.test.ts index a18a340e2..ab14daedd 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/settings/router.test.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/settings/router.test.ts @@ -18,6 +18,7 @@ t.test("GET /api/v1/users/:userID/games/:game/:playtype/settings", (t) => { preferredScoreAlg: null, preferredSessionAlg: null, preferredProfileAlg: null, + scoreBucket: null, stats: [], gameSpecific: { display2DXTra: false, @@ -62,6 +63,7 @@ t.test("PATCH /api/v1/users/:userID/games/:game/:playtype/settings", (t) => { preferredScoreAlg: "ktRating", preferredSessionAlg: null, preferredProfileAlg: null, + scoreBucket: null, stats: [], gameSpecific: { display2DXTra: false, @@ -113,6 +115,7 @@ t.test("PATCH /api/v1/users/:userID/games/:game/:playtype/settings", (t) => { preferredScoreAlg: "ktRating", preferredSessionAlg: null, preferredProfileAlg: null, + scoreBucket: null, stats: [], gameSpecific: { display2DXTra: true, diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/settings/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/settings/router.ts index 07184799e..ed865c379 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/settings/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/settings/router.ts @@ -5,7 +5,7 @@ import CreateLogCtx from "lib/logger/logger"; import p from "prudence"; import { RequirePermissions } from "server/middleware/auth"; import { GetGamePTConfig } from "tachi-common"; -import { FormatPrError } from "utils/prudence"; +import { FormatPrError, optNull } from "utils/prudence"; import { FormatUserDoc } from "utils/user"; import { RequireAuthedAsUser } from "../../../../middleware"; @@ -37,6 +37,7 @@ router.patch( preferredProfileAlg: p.optional(p.nullable(p.isIn(gptConfig.profileRatingAlgs))), // This is a pretty stupid IIFE level hack, ah well. gameSpecific: p.any, + scoreBucket: optNull(p.isIn("grade", "lamp")), }); if (err) { @@ -80,6 +81,10 @@ router.patch( } } + if (req.body.scoreBucket !== undefined) { + updateQuery[`preferences.scoreBucket`] = req.body.scoreBucket; + } + if (req.body.gameSpecific) { for (const key in req.body.gameSpecific) { updateQuery[`preferences.gameSpecific.${key}`] = req.body.gameSpecific[key]; diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/showcase/router.test.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/showcase/router.test.ts index 426010bb6..2f4de811c 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/showcase/router.test.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/showcase/router.test.ts @@ -21,6 +21,7 @@ const SetFolders = async () => { preferredProfileAlg: null, preferredScoreAlg: null, preferredSessionAlg: null, + scoreBucket: null, stats: [ { folderID: TestingIIDXFolderSP10.folderID, @@ -82,6 +83,7 @@ t.test("GET /api/v1/users/:userID/games/:game/:playtype/showcase", (t) => { preferredProfileAlg: null, preferredScoreAlg: null, preferredSessionAlg: null, + scoreBucket: null, stats: [ { mode: "folder", diff --git a/server/src/test-utils/mock-db/game-settings.json b/server/src/test-utils/mock-db/game-settings.json index 8ffa629fd..3a34d14dc 100644 --- a/server/src/test-utils/mock-db/game-settings.json +++ b/server/src/test-utils/mock-db/game-settings.json @@ -7,6 +7,7 @@ "preferredScoreAlg": null, "preferredSessionAlg": null, "preferredProfileAlg": null, + "scoreBucket": null, "stats": [], "gameSpecific": { "display2DXTra": false From fdf7e9d2cab4f0dff21f42efb18821297e545f1b Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Mon, 13 Dec 2021 01:11:03 +0000 Subject: [PATCH 29/32] Fix unrelated typo in sessions/last --- .../v1/users/_userID/games/_game/_playtype/sessions/router.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/sessions/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/sessions/router.ts index 34b5dd87d..e1bc8b446 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/sessions/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/sessions/router.ts @@ -114,7 +114,7 @@ router.get("/recent", async (req, res) => { /** * Returns a user's most recent session. * - * @name GET /api/v1/users/:userID/games/:game/:playtype/sessions/recent + * @name GET /api/v1/users/:userID/games/:game/:playtype/sessions/last */ router.get("/last", async (req, res) => { const user = req[SYMBOL_TachiData]!.requestedUser!; From 78710a07bb57f7fd9683dfb086fa0d593a31e2a9 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Mon, 13 Dec 2021 18:45:51 +0000 Subject: [PATCH 30/32] Add tests for TransposeFolderData and make it so it replaces multiple items in onekey --- server/src/utils/folder.test.ts | 46 ++++++++++++++++++++++++++++++++- server/src/utils/folder.ts | 2 +- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/server/src/utils/folder.test.ts b/server/src/utils/folder.test.ts index afb5cdd1b..91358f846 100644 --- a/server/src/utils/folder.test.ts +++ b/server/src/utils/folder.test.ts @@ -4,7 +4,7 @@ import { ChartDocument, FolderDocument } from "tachi-common"; import t from "tap"; import ResetDBState from "test-utils/resets"; import { Testing511SPA } from "test-utils/test-data"; -import { GetFolderChartIDs, ResolveFolderToCharts } from "./folder"; +import { GetFolderChartIDs, ResolveFolderToCharts, TransposeFolderData } from "./folder"; t.todo("#CreateFolderChartLookup"); t.todo("#GetFolderCharts"); @@ -198,3 +198,47 @@ t.test("#GetFolderChartIDs", (t) => { t.end(); }); + +t.test("#TransposeFolderData", (t) => { + t.strictSame( + TransposeFolderData({ + "foo¬bar": 1, + }), + { + "foo.bar": 1, + }, + "Should transpose single keys." + ); + + t.strictSame( + TransposeFolderData({ + "foo¬bar": 1, + "foo¬baz": 2, + "foo~bar": 2, + "foo~baz": 2, + }), + { + "foo.bar": 1, + "foo.baz": 2, + foo$baz: 2, + foo$baz: 2, + }, + "Should transpose multiple keys." + ); + + t.strictSame( + TransposeFolderData({ + "foo¬bar¬baz": 1, + "foo¬bar~baz": 1, + "foo~bar~baz": 1, + }), + { + "foo.bar$baz": 1, + "foo.bar.baz": 1, + foo$bar$baz: 1, + }, + "Should transpose multiple items in one key." + ); + + t.end(); +}); diff --git a/server/src/utils/folder.ts b/server/src/utils/folder.ts index 3f2fc40f3..59e31c8af 100644 --- a/server/src/utils/folder.ts +++ b/server/src/utils/folder.ts @@ -93,7 +93,7 @@ export function TransposeFolderData(obj: Record) { const transposedObj: Record = {}; for (const key in obj) { - const transposedKey = key.replace(/~/u, "$").replace(/¬/u, "."); + const transposedKey = key.replace(/~/gu, "$").replace(/¬/gu, "."); if (typeof obj[key] === "object" && obj[key]) { transposedObj[transposedKey] = TransposeFolderData(obj[key] as Record); From d4f78b4347926dd059bb83052dd5d05f94cc04b9 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Mon, 13 Dec 2021 18:46:13 +0000 Subject: [PATCH 31/32] Stop pushing code that doesnt compile. --- server/src/utils/folder.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/utils/folder.test.ts b/server/src/utils/folder.test.ts index 91358f846..6bdd06a21 100644 --- a/server/src/utils/folder.test.ts +++ b/server/src/utils/folder.test.ts @@ -220,7 +220,7 @@ t.test("#TransposeFolderData", (t) => { { "foo.bar": 1, "foo.baz": 2, - foo$baz: 2, + foo$bar: 2, foo$baz: 2, }, "Should transpose multiple keys." From 50b86410c44297acf49747f52f9fdf1b71dc25ce Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Mon, 13 Dec 2021 18:47:12 +0000 Subject: [PATCH 32/32] Bump Versions --- server/package.json | 2 +- server/src/lib/constants/version.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/server/package.json b/server/package.json index e4f7492eb..e21e81165 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "tachi-server", - "version": "2.0.4", + "version": "2.0.5", "description": "A score tracking server.", "main": "js/index.js", "private": true, diff --git a/server/src/lib/constants/version.ts b/server/src/lib/constants/version.ts index 87b5cb835..28d7b5d9f 100644 --- a/server/src/lib/constants/version.ts +++ b/server/src/lib/constants/version.ts @@ -4,7 +4,7 @@ const MAJOR = 2; const MINOR = 0; -const PATCH = 4; +const PATCH = 5; // As is with all front-facing zkldi projects, the version names for tachi-server // are from an album I like. In this case, the album is Portishead - Dummy.