From ee06f77cfef1f95fed46bd6ac679d2424d0e5091 Mon Sep 17 00:00:00 2001 From: zkldi Date: Sat, 24 Jul 2021 02:21:03 +0100 Subject: [PATCH 01/15] Bump TachiCommon --- server/pnpm-lock.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/server/pnpm-lock.yaml b/server/pnpm-lock.yaml index 2a55a78f5..ddb1fe743 100644 --- a/server/pnpm-lock.yaml +++ b/server/pnpm-lock.yaml @@ -81,7 +81,7 @@ dependencies: rate-limit-redis: 2.1.0 redis: 3.1.2 rimraf: 3.0.2 - tachi-common: github.com/zkldi/tachi-common/62ea387173a0ee7019adf3371b90f6328081ae92_ts-node@10.0.0+typescript@4.3.4 + tachi-common: github.com/zkldi/tachi-common/7a9f468590d81ea6309c34dc15a7619f9924ed9b_ts-node@10.0.0+typescript@4.3.4 typescript: 4.3.4 winston: 3.3.3 @@ -4214,9 +4214,9 @@ packages: engines: {node: '>=6'} dev: true - github.com/zkldi/tachi-common/62ea387173a0ee7019adf3371b90f6328081ae92_ts-node@10.0.0+typescript@4.3.4: - resolution: {tarball: https://codeload.github.com/zkldi/tachi-common/tar.gz/62ea387173a0ee7019adf3371b90f6328081ae92} - id: github.com/zkldi/tachi-common/62ea387173a0ee7019adf3371b90f6328081ae92 + github.com/zkldi/tachi-common/7a9f468590d81ea6309c34dc15a7619f9924ed9b_ts-node@10.0.0+typescript@4.3.4: + resolution: {tarball: https://codeload.github.com/zkldi/tachi-common/tar.gz/7a9f468590d81ea6309c34dc15a7619f9924ed9b} + id: github.com/zkldi/tachi-common/7a9f468590d81ea6309c34dc15a7619f9924ed9b name: tachi-common version: 0.1.0 dependencies: From 51c68dabc199cec272857331182f6b4bd26ca603 Mon Sep 17 00:00:00 2001 From: zkldi Date: Sat, 24 Jul 2021 02:44:55 +0100 Subject: [PATCH 02/15] change json.stringify to safejson stringify --- server/src/lib/logger/logger.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/server/src/lib/logger/logger.ts b/server/src/lib/logger/logger.ts index 89b69ae78..f52b274d3 100644 --- a/server/src/lib/logger/logger.ts +++ b/server/src/lib/logger/logger.ts @@ -1,6 +1,7 @@ import winston, { format, transports, Logger, LeveledLogMethod } from "winston"; import { EscapeStringRegexp } from "../../utils/misc"; import { LOG_LEVEL } from "../setup/config"; +import SafeJSONStringify from "safe-json-stringify"; export type KtLogger = Logger & { severe: LeveledLogMethod }; @@ -23,7 +24,7 @@ const formatExcessProperties = (meta: Record) => { return ""; } - return ` ${JSON.stringify(meta)}`; + return ` ${SafeJSONStringify(meta)}`; }; const formatExcessPropertiesNoStack = (meta: Record, omitKeys: string[] = []) => { @@ -49,7 +50,7 @@ const formatExcessPropertiesNoStack = (meta: Record, omitKeys: return ""; } - return ` ${JSON.stringify(realMeta)}`; + return ` ${SafeJSONStringify(realMeta)}`; }; const tachiPrintf = format.printf( From dbb4570c8181396ae513dfc8d1fab2bd7ec434e2 Mon Sep 17 00:00:00 2001 From: zkldi Date: Sat, 24 Jul 2021 02:59:47 +0100 Subject: [PATCH 03/15] Job framework for ugs-snapshots --- server/package.json | 2 + server/pnpm-lock.yaml | 20 ++++- server/src/external/mongo/db.ts | 2 + server/src/external/mongo/indexes.ts | 1 + server/src/lib/jobs/jobconf.json5 | 11 +++ server/src/lib/jobs/ugs-snapshot.ts | 87 +++++++++++++++++++ .../_userID/games/_game/_playtype/router.ts | 6 +- server/src/utils/user.ts | 34 ++++++++ 8 files changed, 156 insertions(+), 7 deletions(-) create mode 100644 server/src/lib/jobs/jobconf.json5 create mode 100644 server/src/lib/jobs/ugs-snapshot.ts diff --git a/server/package.json b/server/package.json index 83cbd2f9f..934afb44b 100644 --- a/server/package.json +++ b/server/package.json @@ -25,6 +25,7 @@ "@types/range-parser": "1.2.3", "@types/rate-limit-redis": "1.7.1", "@types/rimraf": "3.0.0", + "@types/safe-json-stringify": "1.1.2", "@types/supertest": "2.0.11", "@types/tap": "15.0.4", "@types/uuid": "8.3.0", @@ -67,6 +68,7 @@ "rate-limit-redis": "2.1.0", "redis": "3.1.2", "rimraf": "3.0.2", + "safe-json-stringify": "1.2.0", "tachi-common": "github:zkldi/tachi-common", "typescript": "4.3.4", "winston": "3.3.3" diff --git a/server/pnpm-lock.yaml b/server/pnpm-lock.yaml index ddb1fe743..68dc58400 100644 --- a/server/pnpm-lock.yaml +++ b/server/pnpm-lock.yaml @@ -13,6 +13,7 @@ specifiers: '@types/range-parser': 1.2.3 '@types/rate-limit-redis': 1.7.1 '@types/rimraf': 3.0.0 + '@types/safe-json-stringify': 1.1.2 '@types/supertest': 2.0.11 '@types/tap': 15.0.4 '@types/uuid': 8.3.0 @@ -48,6 +49,7 @@ specifiers: rate-limit-redis: 2.1.0 redis: 3.1.2 rimraf: 3.0.2 + safe-json-stringify: 1.2.0 supertest: 6.1.3 tachi-common: github:zkldi/tachi-common tap: 15.0.9 @@ -81,7 +83,8 @@ dependencies: rate-limit-redis: 2.1.0 redis: 3.1.2 rimraf: 3.0.2 - tachi-common: github.com/zkldi/tachi-common/7a9f468590d81ea6309c34dc15a7619f9924ed9b_ts-node@10.0.0+typescript@4.3.4 + safe-json-stringify: 1.2.0 + tachi-common: github.com/zkldi/tachi-common/dbfc3f67f73fb16e00adfaf5ec713f4e43cec2f9_ts-node@10.0.0+typescript@4.3.4 typescript: 4.3.4 winston: 3.3.3 @@ -98,6 +101,7 @@ devDependencies: '@types/range-parser': 1.2.3 '@types/rate-limit-redis': 1.7.1 '@types/rimraf': 3.0.0 + '@types/safe-json-stringify': 1.1.2 '@types/supertest': 2.0.11 '@types/tap': 15.0.4 '@types/uuid': 8.3.0 @@ -632,6 +636,10 @@ packages: '@types/node': 15.12.4 dev: true + /@types/safe-json-stringify/1.1.2: + resolution: {integrity: sha512-Hj/LZMBXFH3Qj9sNmNu6syBpkZqBaM00HgP7naf9CnZhB3kdxmrenWUD2cY6vvTWjuBM3NPOLknPKDUOQuTWXA==} + dev: true + /@types/serve-static/1.13.9: resolution: {integrity: sha512-ZFqF6qa48XsPdjXV5Gsz0Zqmux2PerNd3a/ktL45mHpa19cuMi/cL8tcxdAx497yRh+QtYPuofjT9oWw9P7nkA==} dependencies: @@ -3422,6 +3430,10 @@ packages: /safe-buffer/5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + /safe-json-stringify/1.2.0: + resolution: {integrity: sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==} + dev: false + /safer-buffer/2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} @@ -4214,9 +4226,9 @@ packages: engines: {node: '>=6'} dev: true - github.com/zkldi/tachi-common/7a9f468590d81ea6309c34dc15a7619f9924ed9b_ts-node@10.0.0+typescript@4.3.4: - resolution: {tarball: https://codeload.github.com/zkldi/tachi-common/tar.gz/7a9f468590d81ea6309c34dc15a7619f9924ed9b} - id: github.com/zkldi/tachi-common/7a9f468590d81ea6309c34dc15a7619f9924ed9b + github.com/zkldi/tachi-common/dbfc3f67f73fb16e00adfaf5ec713f4e43cec2f9_ts-node@10.0.0+typescript@4.3.4: + resolution: {tarball: https://codeload.github.com/zkldi/tachi-common/tar.gz/dbfc3f67f73fb16e00adfaf5ec713f4e43cec2f9} + id: github.com/zkldi/tachi-common/dbfc3f67f73fb16e00adfaf5ec713f4e43cec2f9 name: tachi-common version: 0.1.0 dependencies: diff --git a/server/src/external/mongo/db.ts b/server/src/external/mongo/db.ts index 99e435bce..54fe69779 100644 --- a/server/src/external/mongo/db.ts +++ b/server/src/external/mongo/db.ts @@ -14,6 +14,7 @@ import { PrivateUserDocument, ScoreDocument, KaiAuthDocument, + UserGameStatsSnapshot, SessionDocument, AnySongDocument, AnyChartDocument, @@ -143,6 +144,7 @@ const db = { "import-locks": monkDB.get("import-locks"), tables: monkDB.get("tables"), "game-settings": monkDB.get("game-settings"), + "game-stats-snapshots": monkDB.get("game-stats-snapshots"), }; export default db; diff --git a/server/src/external/mongo/indexes.ts b/server/src/external/mongo/indexes.ts index f1baa9d15..9d5735437 100644 --- a/server/src/external/mongo/indexes.ts +++ b/server/src/external/mongo/indexes.ts @@ -79,6 +79,7 @@ const staticIndexes: Partial> = { "bms-course-lookup": [index({ md5sums: 1 }, UNIQUE)], "api-tokens": [index({ token: 1 }, UNIQUE), index({ userID: 1 })], tables: [index({ tableID: 1 }, UNIQUE), index({ game: 1, playtype: 1 })], + "game-stats-snapshots": [index({ timestamp: 1, userID: 1, game: 1, playtype: 1 }, UNIQUE)], }; const indexes: Partial> = staticIndexes; diff --git a/server/src/lib/jobs/jobconf.json5 b/server/src/lib/jobs/jobconf.json5 new file mode 100644 index 000000000..58d7374fb --- /dev/null +++ b/server/src/lib/jobs/jobconf.json5 @@ -0,0 +1,11 @@ +{ + // this format is just an example - @ereti will be designing the jobs system and will *definitely* want something + // better. This only exists as a rough example. + prefix: "tsnode -r tsconfig-paths/register", + jobs: [{ + path: "ugs-snapshot.ts", + name: "Snapshot User Game Stats", + // especially this part - i don't expect any machine to parse this text. + interval: "daily at midnight" + }] +} \ No newline at end of file diff --git a/server/src/lib/jobs/ugs-snapshot.ts b/server/src/lib/jobs/ugs-snapshot.ts new file mode 100644 index 000000000..6cdc74711 --- /dev/null +++ b/server/src/lib/jobs/ugs-snapshot.ts @@ -0,0 +1,87 @@ +import { UserGameStats, UserGameStatsSnapshot } from "tachi-common"; + +import db from "external/mongo/db"; +import CreateLogCtx from "lib/logger/logger"; +import { GetUsersRanking } from "utils/user"; +import { GetMilisecondsSince } from "utils/misc"; + +const logger = CreateLogCtx(__filename); + +// get the time of this midnight. it's possible this script eclipses itself when weird timezone +// nonsense happens. we'll have to see. +const currentTime = new Date().setUTCHours(0, 0, 0, 0); + +let batchWrite: UserGameStatsSnapshot[] = []; + +(async () => { + const timeStart = process.hrtime.bigint(); + + const alreadyExists = await db["game-stats-snapshots"].findOne({ timestamp: currentTime }); + + if (alreadyExists) { + logger.severe( + `FATAL IN UGS-SNAPSHOT - There already exists snapshots at this time. Has this script been ran twice?` + ); + process.exit(1); + } + + logger.info(`Snapshotting UserGameStats.`); + + try { + await db["game-stats"] + .find({}) + // @ts-expect-error faulty TS types + .each(async (ugs: UserGameStats, { pause, resume }) => { + pause(); + + logger.debug(`Snapshotting ${ugs.userID} ${ugs.playtype} ${ugs.game}.`); + + const [playcount, ranking] = await Promise.all([ + db.scores.count({ userID: ugs.userID, playtype: ugs.playtype, game: ugs.game }), + GetUsersRanking(ugs), + ]); + + const ugsSnapshot: UserGameStatsSnapshot = { + ...ugs, + playcount, + ranking, + timestamp: currentTime, + }; + + batchWrite.push(ugsSnapshot); + + if (batchWrite.length >= 500) { + logger.verbose(`Flushed batch.`); + await db["game-stats-snapshots"].insert(batchWrite); + batchWrite = []; + } + + resume(); + }); + + if (batchWrite.length) { + await db["game-stats-snapshots"].insert(batchWrite); + } + + logger.info( + `Successfully snapshotted all data as of ${new Date( + currentTime + ).toString()}. Took ${GetMilisecondsSince(timeStart)} ms.` + ); + + process.exit(0); + } catch (err) { + // if we panic, we need to revert whatever we did. + logger.severe(`FATAL IN UGS-SNAPSHOT - Possibly failed midway through snapshotting.`, { + err, + }); + + logger.info(`Removing all snapshots at this timestamp (${currentTime}).`); + + await db["game-stats-snapshots"].remove({ timestamp: currentTime }); + + logger.info(`Removed.`); + + process.exit(1); + } +})(); diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.ts index b43437a8b..61909049c 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.ts @@ -2,7 +2,7 @@ import { Router } from "express"; import db from "../../../../../../../../../external/mongo/db"; import { SYMBOL_TachiData } from "../../../../../../../../../lib/constants/tachi"; import { GetRelevantSongsAndCharts } from "../../../../../../../../../utils/db"; -import { GetUsersRanking, GetUsersWithIDs } from "../../../../../../../../../utils/user"; +import { GetUsersRankingAndOutOf, GetUsersWithIDs } from "../../../../../../../../../utils/user"; import { CheckUserPlayedGamePlaytype } from "./middleware"; import { FilterQuery } from "mongodb"; import { @@ -69,7 +69,7 @@ router.get("/", async (req, res) => { }, } ), - GetUsersRanking(stats), + GetUsersRankingAndOutOf(stats), ]); return res.status(200).json({ @@ -386,7 +386,7 @@ router.get("/leaderboard-adjacent", async (req, res) => { ...below.map((e) => e.userID), ]); - const thisUsersRanking = await GetUsersRanking(thisUsersStats); + const thisUsersRanking = await GetUsersRankingAndOutOf(thisUsersStats); return res.status(200).json({ success: true, diff --git a/server/src/utils/user.ts b/server/src/utils/user.ts index 9cec7d04b..136786cc5 100644 --- a/server/src/utils/user.ts +++ b/server/src/utils/user.ts @@ -156,6 +156,40 @@ export function FormatUserDoc(userdoc: PublicUserDocument) { export async function GetUsersRanking(stats: UserGameStats) { const gptConfig = GetGamePTConfig(stats.game, stats.playtype); + const aggRes = await db["game-stats"].aggregate([ + { + $match: { + game: stats.game, + playtype: stats.playtype, + }, + }, + { + $group: { + _id: null, + ranking: { + $sum: { + $cond: { + if: { + $gt: [ + `$ratings.${gptConfig.defaultProfileRatingAlg}`, + stats.ratings[gptConfig.defaultProfileRatingAlg], + ], + }, + then: 1, + else: 0, + }, + }, + }, + }, + }, + ]); + + return (aggRes[0].ranking + 1) as integer; +} + +export async function GetUsersRankingAndOutOf(stats: UserGameStats) { + const gptConfig = GetGamePTConfig(stats.game, stats.playtype); + const aggRes = await db["game-stats"].aggregate([ { $match: { From 78ca5d22c8670ade5eabfbada3b4d4c0db4b353b Mon Sep 17 00:00:00 2001 From: zkldi Date: Sat, 24 Jul 2021 03:00:36 +0100 Subject: [PATCH 04/15] add warning note about job robustness --- server/src/lib/jobs/ugs-snapshot.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/src/lib/jobs/ugs-snapshot.ts b/server/src/lib/jobs/ugs-snapshot.ts index 6cdc74711..7f5628c4b 100644 --- a/server/src/lib/jobs/ugs-snapshot.ts +++ b/server/src/lib/jobs/ugs-snapshot.ts @@ -1,5 +1,4 @@ import { UserGameStats, UserGameStatsSnapshot } from "tachi-common"; - import db from "external/mongo/db"; import CreateLogCtx from "lib/logger/logger"; import { GetUsersRanking } from "utils/user"; @@ -13,6 +12,8 @@ const currentTime = new Date().setUTCHours(0, 0, 0, 0); let batchWrite: UserGameStatsSnapshot[] = []; +// This code is intentionally *very* robust, and handles a lot of unanticipated failures +// because if it breaks, we brick the database. (async () => { const timeStart = process.hrtime.bigint(); From 75f16d84c61d9a8207e2d2ce07e61c9e635de3fc Mon Sep 17 00:00:00 2001 From: zkldi Date: Sat, 24 Jul 2021 03:11:00 +0100 Subject: [PATCH 05/15] Rename ugptstats to showcase stats --- server/pnpm-lock.yaml | 8 ++++---- .../evaluator.test.ts | 12 ++++++------ .../{ugpt-stat => showcase-stats}/evaluator.ts | 14 +++++++------- .../get-related.ts | 4 ++-- .../get-stats.test.ts | 10 +++++----- .../{ugpt-stat => showcase-stats}/get-stats.ts | 18 +++++++++--------- .../_userID/games/_game/_playtype/router.ts | 2 +- .../{stats => showcase}/router.test.ts | 0 .../_playtype/{stats => showcase}/router.ts | 14 +++++++------- 9 files changed, 41 insertions(+), 41 deletions(-) rename server/src/lib/{ugpt-stat => showcase-stats}/evaluator.test.ts (87%) rename server/src/lib/{ugpt-stat => showcase-stats}/evaluator.ts (81%) rename server/src/lib/{ugpt-stat => showcase-stats}/get-related.ts (87%) rename server/src/lib/{ugpt-stat => showcase-stats}/get-stats.test.ts (89%) rename server/src/lib/{ugpt-stat => showcase-stats}/get-stats.ts (68%) rename server/src/server/router/api/v1/users/_userID/games/_game/_playtype/{stats => showcase}/router.test.ts (100%) rename server/src/server/router/api/v1/users/_userID/games/_game/_playtype/{stats => showcase}/router.ts (92%) diff --git a/server/pnpm-lock.yaml b/server/pnpm-lock.yaml index 68dc58400..371edd46c 100644 --- a/server/pnpm-lock.yaml +++ b/server/pnpm-lock.yaml @@ -84,7 +84,7 @@ dependencies: redis: 3.1.2 rimraf: 3.0.2 safe-json-stringify: 1.2.0 - tachi-common: github.com/zkldi/tachi-common/dbfc3f67f73fb16e00adfaf5ec713f4e43cec2f9_ts-node@10.0.0+typescript@4.3.4 + tachi-common: github.com/zkldi/tachi-common/21c3bb4cc4407601a902e049fb1e1b774b793e40_ts-node@10.0.0+typescript@4.3.4 typescript: 4.3.4 winston: 3.3.3 @@ -4226,9 +4226,9 @@ packages: engines: {node: '>=6'} dev: true - github.com/zkldi/tachi-common/dbfc3f67f73fb16e00adfaf5ec713f4e43cec2f9_ts-node@10.0.0+typescript@4.3.4: - resolution: {tarball: https://codeload.github.com/zkldi/tachi-common/tar.gz/dbfc3f67f73fb16e00adfaf5ec713f4e43cec2f9} - id: github.com/zkldi/tachi-common/dbfc3f67f73fb16e00adfaf5ec713f4e43cec2f9 + github.com/zkldi/tachi-common/21c3bb4cc4407601a902e049fb1e1b774b793e40_ts-node@10.0.0+typescript@4.3.4: + resolution: {tarball: https://codeload.github.com/zkldi/tachi-common/tar.gz/21c3bb4cc4407601a902e049fb1e1b774b793e40} + id: github.com/zkldi/tachi-common/21c3bb4cc4407601a902e049fb1e1b774b793e40 name: tachi-common version: 0.1.0 dependencies: diff --git a/server/src/lib/ugpt-stat/evaluator.test.ts b/server/src/lib/showcase-stats/evaluator.test.ts similarity index 87% rename from server/src/lib/ugpt-stat/evaluator.test.ts rename to server/src/lib/showcase-stats/evaluator.test.ts index b1d81a466..ac6d1821b 100644 --- a/server/src/lib/ugpt-stat/evaluator.test.ts +++ b/server/src/lib/showcase-stats/evaluator.test.ts @@ -1,6 +1,6 @@ import deepmerge from "deepmerge"; import t from "tap"; -import { EvaluateUGPTStat } from "./evaluator"; +import { EvaluateShowcaseStat } from "./evaluator"; import { CloseAllConnections } from "../../test-utils/close-connections"; import ResetDBState from "../../test-utils/resets"; import { @@ -19,9 +19,9 @@ t.beforeEach(ResetDBState); t.beforeEach(async () => await CreateFolderChartLookup(TestingIIDXFolderSP10)); t.beforeEach(async () => await db["personal-bests"].insert(deepmerge(TestingIIDXSPScorePB, {}))); -t.test("#EvaluateUGPTStat", (t) => { +t.test("#EvaluateShowcaseStat", (t) => { t.test("Should evaluate a folder stat.", async (t) => { - const data = await EvaluateUGPTStat( + const data = await EvaluateShowcaseStat( { folderID: TestingIIDXFolderSP10.folderID, mode: "folder", @@ -40,7 +40,7 @@ t.test("#EvaluateUGPTStat", (t) => { }); t.test("Should evaluate a multi-folder stat.", async (t) => { - const data = await EvaluateUGPTStat( + const data = await EvaluateShowcaseStat( { folderID: [TestingIIDXFolderSP10.folderID], mode: "folder", @@ -59,7 +59,7 @@ t.test("#EvaluateUGPTStat", (t) => { }); t.test("Should evaluate a chart stat.", async (t) => { - const data = await EvaluateUGPTStat( + const data = await EvaluateShowcaseStat( { chartID: Testing511SPA.chartID, mode: "chart", @@ -76,7 +76,7 @@ t.test("#EvaluateUGPTStat", (t) => { }); t.test("Should return null if the user has no score on this chart.", async (t) => { - const data = await EvaluateUGPTStat( + const data = await EvaluateShowcaseStat( { chartID: "nonsense", mode: "chart", diff --git a/server/src/lib/ugpt-stat/evaluator.ts b/server/src/lib/showcase-stats/evaluator.ts similarity index 81% rename from server/src/lib/ugpt-stat/evaluator.ts rename to server/src/lib/showcase-stats/evaluator.ts index 8054858de..192e93272 100644 --- a/server/src/lib/ugpt-stat/evaluator.ts +++ b/server/src/lib/showcase-stats/evaluator.ts @@ -1,25 +1,25 @@ -import { integer, UGPTStatDetails, UGPTStatChart, UGPTStatFolder } from "tachi-common"; +import { integer, ShowcaseStatDetails, ShowcaseStatChart, ShowcaseStatFolder } from "tachi-common"; import db from "../../external/mongo/db"; import { GetFolderChartIDs } from "../../utils/folder"; -export function EvaluateUGPTStat( - details: UGPTStatDetails, +export function EvaluateShowcaseStat( + details: ShowcaseStatDetails, userID: integer ): Promise<{ value: number | null; outOf?: number; }> { if (details.mode === "chart") { - return EvaluateUGPTChartStat(details, userID); + return EvaluateShowcaseChartStat(details, userID); } else if (details.mode === "folder") { - return EvaluateUGPTFolderStat(details, userID); + return EvaluateShowcaseFolderStat(details, userID); } // @ts-expect-error This should never happen anyway. throw new Error(`Invalid mode of ${details.mode} as details mode?`); } -async function EvaluateUGPTChartStat(details: UGPTStatChart, userID: integer) { +async function EvaluateShowcaseChartStat(details: ShowcaseStatChart, userID: integer) { // requires special handling if (details.property === "playcount") { return { value: await db.scores.count({ chartID: details.chartID, userID }) }; @@ -41,7 +41,7 @@ async function EvaluateUGPTChartStat(details: UGPTStatChart, userID: integer) { return { value: pb.scoreData[scProp] }; } -async function EvaluateUGPTFolderStat(details: UGPTStatFolder, userID: integer) { +async function EvaluateShowcaseFolderStat(details: ShowcaseStatFolder, userID: integer) { let chartIDs; if (Array.isArray(details.folderID)) { chartIDs = (await Promise.all(details.folderID.map(GetFolderChartIDs))).flat(1); diff --git a/server/src/lib/ugpt-stat/get-related.ts b/server/src/lib/showcase-stats/get-related.ts similarity index 87% rename from server/src/lib/ugpt-stat/get-related.ts rename to server/src/lib/showcase-stats/get-related.ts index c27cae4b4..368f037b4 100644 --- a/server/src/lib/ugpt-stat/get-related.ts +++ b/server/src/lib/showcase-stats/get-related.ts @@ -1,10 +1,10 @@ import db from "external/mongo/db"; import CreateLogCtx from "lib/logger/logger"; -import { Game, UGPTStatDetails } from "tachi-common"; +import { Game, ShowcaseStatDetails } from "tachi-common"; const logger = CreateLogCtx(__filename); -export async function GetRelatedStatDocuments(stat: UGPTStatDetails, game: Game) { +export async function GetRelatedStatDocuments(stat: ShowcaseStatDetails, game: Game) { if (stat.mode === "chart") { const chart = await db.charts[game].findOne({ chartID: stat.chartID }); diff --git a/server/src/lib/ugpt-stat/get-stats.test.ts b/server/src/lib/showcase-stats/get-stats.test.ts similarity index 89% rename from server/src/lib/ugpt-stat/get-stats.test.ts rename to server/src/lib/showcase-stats/get-stats.test.ts index 605e08241..4dd1f9064 100644 --- a/server/src/lib/ugpt-stat/get-stats.test.ts +++ b/server/src/lib/showcase-stats/get-stats.test.ts @@ -8,7 +8,7 @@ import { TestingIIDXSPScorePB, } from "../../test-utils/test-data"; import { IIDX_LAMPS } from "../constants/game"; -import { EvaluateUsersGPTStats } from "./get-stats"; +import { EvaluateUsersStatsShowcase } from "./get-stats"; import deepmerge from "deepmerge"; import { CreateFolderChartLookup } from "../../utils/folder"; @@ -47,9 +47,9 @@ t.beforeEach(async () => { await db["personal-bests"].insert(deepmerge(TestingIIDXSPScorePB, {})); }); -t.test("#EvalulateUsersGPTStats", (t) => { +t.test("#EvalulateUsersStatsShowcase", (t) => { t.test("Should evaluate a user's preferred stats.", async (t) => { - const res = await EvaluateUsersGPTStats(1, "iidx", "SP"); + const res = await EvaluateUsersStatsShowcase(1, "iidx", "SP"); t.hasStrict(res, [ { @@ -89,7 +89,7 @@ t.test("#EvalulateUsersGPTStats", (t) => { t.test("Should throw an error if the user does not have game-settings.", async (t) => { await db["game-settings"].remove({}); - t.rejects(() => EvaluateUsersGPTStats(1, "iidx", "SP")); + t.rejects(() => EvaluateUsersStatsShowcase(1, "iidx", "SP")); t.end(); }); @@ -105,7 +105,7 @@ t.test("#EvalulateUsersGPTStats", (t) => { }) ); - const res = await EvaluateUsersGPTStats(2, "iidx", "SP", 1); + const res = await EvaluateUsersStatsShowcase(2, "iidx", "SP", 1); t.hasStrict(res, [ { diff --git a/server/src/lib/ugpt-stat/get-stats.ts b/server/src/lib/showcase-stats/get-stats.ts similarity index 68% rename from server/src/lib/ugpt-stat/get-stats.ts rename to server/src/lib/showcase-stats/get-stats.ts index c7598a6f1..65e7bf842 100644 --- a/server/src/lib/ugpt-stat/get-stats.ts +++ b/server/src/lib/showcase-stats/get-stats.ts @@ -1,17 +1,17 @@ import db from "../../external/mongo/db"; -import { integer, Game, Playtypes, UGPTStatDetails } from "tachi-common"; +import { integer, Game, Playtypes, ShowcaseStatDetails } from "tachi-common"; import CreateLogCtx from "../logger/logger"; -import { EvaluateUGPTStat } from "./evaluator"; +import { EvaluateShowcaseStat } from "./evaluator"; import { GetRelatedStatDocuments } from "./get-related"; const logger = CreateLogCtx(__filename); /** - * Evaluate a users set GPT Stats. - * @param projectUserStats - Optionally, provide another users ID here. Their set stats will be + * Evaluate a users set Stats Showcase. + * @param projectUserStats - Optionally, provide another users ID here. Their stats showcase will be * used instead. */ -export async function EvaluateUsersGPTStats( +export async function EvaluateUsersStatsShowcase( userID: integer, game: Game, playtype: Playtypes[Game], @@ -26,11 +26,11 @@ export async function EvaluateUsersGPTStats( if (!settings) { logger.error( - `User ${getSettingsID} has no game-settings, yet a call to EvalulateUsersGPTStats was made.` + `User ${getSettingsID} has no game-settings, yet a call to EvaluateUsersStatsShowcase was made.` ); throw new Error( - `User ${getSettingsID} has no game-settings, yet a call to EvalulateUsersGPTStats was made.` + `User ${getSettingsID} has no game-settings, yet a call to EvaluateUsersStatsShowcase was made.` ); } @@ -41,9 +41,9 @@ export async function EvaluateUsersGPTStats( return results; } -async function EvaluateStats(details: UGPTStatDetails, userID: integer, game: Game) { +async function EvaluateStats(details: ShowcaseStatDetails, userID: integer, game: Game) { const [result, related] = await Promise.all([ - EvaluateUGPTStat(details, userID), + EvaluateShowcaseStat(details, userID), GetRelatedStatDocuments(details, game), ]); diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.ts index 61909049c..bd1d07f8c 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.ts @@ -20,7 +20,7 @@ import pbsRouter from "./pbs/router"; import sessionsRouter from "./sessions/router"; import foldersFolderIDRouter from "./folders/_folderID/router"; import tablesRouter from "./tables/router"; -import statsRouter from "./stats/router"; +import statsRouter from "./showcase/router"; import settingsRouter from "./settings/router"; const router: Router = Router({ mergeParams: true }); diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/stats/router.test.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/showcase/router.test.ts similarity index 100% rename from server/src/server/router/api/v1/users/_userID/games/_game/_playtype/stats/router.test.ts rename to server/src/server/router/api/v1/users/_userID/games/_game/_playtype/showcase/router.test.ts diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/stats/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/showcase/router.ts similarity index 92% rename from server/src/server/router/api/v1/users/_userID/games/_game/_playtype/stats/router.ts rename to server/src/server/router/api/v1/users/_userID/games/_game/_playtype/showcase/router.ts index 68334ae2f..bad458c90 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/stats/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/showcase/router.ts @@ -1,15 +1,15 @@ import { Router } from "express"; import { SYMBOL_TachiData } from "../../../../../../../../../../lib/constants/tachi"; -import { EvaluateUsersGPTStats } from "../../../../../../../../../../lib/ugpt-stat/get-stats"; +import { EvaluateUsersStatsShowcase } from "../../../../../../../../../../lib/showcase-stats/get-stats"; import { ResolveUser } from "../../../../../../../../../../utils/user"; import p from "prudence"; import { FormatPrError } from "../../../../../../../../../../utils/prudence"; -import { UGPTStatDetails, GetGamePTConfig } from "tachi-common"; -import { EvaluateUGPTStat } from "../../../../../../../../../../lib/ugpt-stat/evaluator"; +import { ShowcaseStatDetails, GetGamePTConfig } from "tachi-common"; +import { EvaluateShowcaseStat } from "../../../../../../../../../../lib/showcase-stats/evaluator"; import db from "../../../../../../../../../../external/mongo/db"; import { RequirePermissions } from "../../../../../../../../../middleware/auth"; import { RequireAuthedAsUser } from "../../../../middleware"; -import { GetRelatedStatDocuments } from "lib/ugpt-stat/get-related"; +import { GetRelatedStatDocuments } from "lib/showcase-stats/get-related"; const router: Router = Router({ mergeParams: true }); /** @@ -39,7 +39,7 @@ router.get("/", async (req, res) => { projectUser = user.id; } - const results = await EvaluateUsersGPTStats(user.id, game, playtype, projectUser); + const results = await EvaluateUsersStatsShowcase(user.id, game, playtype, projectUser); return res.status(200).json({ success: true, @@ -65,7 +65,7 @@ router.get("/custom", async (req, res) => { const user = req[SYMBOL_TachiData]!.requestedUser!; const game = req[SYMBOL_TachiData]!.game!; - let stat: UGPTStatDetails; + let stat: ShowcaseStatDetails; if (req.query.mode === "folder") { const err = p( @@ -127,7 +127,7 @@ router.get("/custom", async (req, res) => { }); } - const result = await EvaluateUGPTStat(stat, user.id); + const result = await EvaluateShowcaseStat(stat, user.id); const related = await GetRelatedStatDocuments(stat, game); From a0c3f56c125eb2a668318884309e17c13af3174d Mon Sep 17 00:00:00 2001 From: zkldi Date: Sat, 24 Jul 2021 03:11:27 +0100 Subject: [PATCH 06/15] but dont rename the folder - whoops --- .../router/api/v1/users/_userID/games/_game/_playtype/router.ts | 2 +- .../games/_game/_playtype/{showcase => stats}/router.test.ts | 0 .../_userID/games/_game/_playtype/{showcase => stats}/router.ts | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename server/src/server/router/api/v1/users/_userID/games/_game/_playtype/{showcase => stats}/router.test.ts (100%) rename server/src/server/router/api/v1/users/_userID/games/_game/_playtype/{showcase => stats}/router.ts (100%) diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.ts index bd1d07f8c..61909049c 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.ts @@ -20,7 +20,7 @@ import pbsRouter from "./pbs/router"; import sessionsRouter from "./sessions/router"; import foldersFolderIDRouter from "./folders/_folderID/router"; import tablesRouter from "./tables/router"; -import statsRouter from "./showcase/router"; +import statsRouter from "./stats/router"; import settingsRouter from "./settings/router"; const router: Router = Router({ mergeParams: true }); 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/stats/router.test.ts similarity index 100% rename from server/src/server/router/api/v1/users/_userID/games/_game/_playtype/showcase/router.test.ts rename to server/src/server/router/api/v1/users/_userID/games/_game/_playtype/stats/router.test.ts diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/showcase/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/stats/router.ts similarity index 100% rename from server/src/server/router/api/v1/users/_userID/games/_game/_playtype/showcase/router.ts rename to server/src/server/router/api/v1/users/_userID/games/_game/_playtype/stats/router.ts From 05dae6d2aba4d2883cd585b1723e203de2bd6f88 Mon Sep 17 00:00:00 2001 From: zkldi Date: Sat, 24 Jul 2021 03:21:10 +0100 Subject: [PATCH 07/15] actually, rename it, but commit all the way. --- .../evaluator.test.ts | 0 .../{showcase-stats => showcase}/evaluator.ts | 0 .../get-related.ts | 0 .../get-stats.test.ts | 0 .../{showcase-stats => showcase}/get-stats.ts | 0 .../_userID/games/_game/_playtype/router.ts | 4 +-- .../{stats => showcase}/router.test.ts | 32 +++++++++---------- .../_playtype/{stats => showcase}/router.ts | 24 +++++++------- 8 files changed, 30 insertions(+), 30 deletions(-) rename server/src/lib/{showcase-stats => showcase}/evaluator.test.ts (100%) rename server/src/lib/{showcase-stats => showcase}/evaluator.ts (100%) rename server/src/lib/{showcase-stats => showcase}/get-related.ts (100%) rename server/src/lib/{showcase-stats => showcase}/get-stats.test.ts (100%) rename server/src/lib/{showcase-stats => showcase}/get-stats.ts (100%) rename server/src/server/router/api/v1/users/_userID/games/_game/_playtype/{stats => showcase}/router.test.ts (82%) rename server/src/server/router/api/v1/users/_userID/games/_game/_playtype/{stats => showcase}/router.ts (86%) diff --git a/server/src/lib/showcase-stats/evaluator.test.ts b/server/src/lib/showcase/evaluator.test.ts similarity index 100% rename from server/src/lib/showcase-stats/evaluator.test.ts rename to server/src/lib/showcase/evaluator.test.ts diff --git a/server/src/lib/showcase-stats/evaluator.ts b/server/src/lib/showcase/evaluator.ts similarity index 100% rename from server/src/lib/showcase-stats/evaluator.ts rename to server/src/lib/showcase/evaluator.ts diff --git a/server/src/lib/showcase-stats/get-related.ts b/server/src/lib/showcase/get-related.ts similarity index 100% rename from server/src/lib/showcase-stats/get-related.ts rename to server/src/lib/showcase/get-related.ts diff --git a/server/src/lib/showcase-stats/get-stats.test.ts b/server/src/lib/showcase/get-stats.test.ts similarity index 100% rename from server/src/lib/showcase-stats/get-stats.test.ts rename to server/src/lib/showcase/get-stats.test.ts diff --git a/server/src/lib/showcase-stats/get-stats.ts b/server/src/lib/showcase/get-stats.ts similarity index 100% rename from server/src/lib/showcase-stats/get-stats.ts rename to server/src/lib/showcase/get-stats.ts diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.ts index 61909049c..e59954f99 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.ts @@ -20,7 +20,7 @@ import pbsRouter from "./pbs/router"; import sessionsRouter from "./sessions/router"; import foldersFolderIDRouter from "./folders/_folderID/router"; import tablesRouter from "./tables/router"; -import statsRouter from "./stats/router"; +import showcaseRouter from "./showcase/router"; import settingsRouter from "./settings/router"; const router: Router = Router({ mergeParams: true }); @@ -404,7 +404,7 @@ router.get("/leaderboard-adjacent", async (req, res) => { router.use("/pbs", pbsRouter); router.use("/sessions", sessionsRouter); router.use("/tables", tablesRouter); -router.use("/stats", statsRouter); +router.use("/showcase", showcaseRouter); router.use("/settings", settingsRouter); router.use("/folders/:folderID", foldersFolderIDRouter); diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/stats/router.test.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/showcase/router.test.ts similarity index 82% rename from server/src/server/router/api/v1/users/_userID/games/_game/_playtype/stats/router.test.ts rename to server/src/server/router/api/v1/users/_userID/games/_game/_playtype/showcase/router.test.ts index 8b40e100f..a3fd770a3 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/stats/router.test.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/showcase/router.test.ts @@ -42,9 +42,9 @@ t.beforeEach(async () => { await db["personal-bests"].insert(deepmerge(TestingIIDXSPScorePB, {})); }); -t.test("GET /api/v1/users/:userID/games/:game/:playtype/stats", (t) => { +t.test("GET /api/v1/users/:userID/games/:game/:playtype/showcase", (t) => { t.test("Should return the evaluated stats for this user.", async (t) => { - const res = await mockApi.get("/api/v1/users/1/games/iidx/SP/stats"); + const res = await mockApi.get("/api/v1/users/1/games/iidx/SP/showcase"); t.hasStrict(res.body.body, [ { @@ -95,7 +95,7 @@ t.test("GET /api/v1/users/:userID/games/:game/:playtype/stats", (t) => { id: 2, } as PrivateUserDocument); - const res = await mockApi.get("/api/v1/users/1/games/iidx/SP/stats?projectUser=2"); + const res = await mockApi.get("/api/v1/users/1/games/iidx/SP/showcase?projectUser=2"); t.hasStrict(res.body.body, [ { @@ -113,7 +113,7 @@ t.test("GET /api/v1/users/:userID/games/:game/:playtype/stats", (t) => { }); t.test("Should return 404 if the user has not played this game.", async (t) => { - const res = await mockApi.get("/api/v1/users/1/games/bms/7K/stats"); + const res = await mockApi.get("/api/v1/users/1/games/bms/7K/showcase"); t.equal(res.statusCode, 404); @@ -125,10 +125,10 @@ t.test("GET /api/v1/users/:userID/games/:game/:playtype/stats", (t) => { t.end(); }); -t.test("GET /api/v1/users/:userID/games/:game/:playtype/stats/custom", (t) => { +t.test("GET /api/v1/users/:userID/games/:game/:playtype/showcase/custom", (t) => { t.test("Should return a custom folder evaluated stat on a user.", async (t) => { const res = await mockApi.get( - `/api/v1/users/1/games/iidx/SP/stats/custom?mode=folder&prop=grade>e=3&folderID=${TestingIIDXFolderSP10.folderID}` + `/api/v1/users/1/games/iidx/SP/showcase/custom?mode=folder&prop=grade>e=3&folderID=${TestingIIDXFolderSP10.folderID}` ); t.strictSame(res.body.body, { @@ -141,7 +141,7 @@ t.test("GET /api/v1/users/:userID/games/:game/:playtype/stats/custom", (t) => { t.test("Should return a custom chart evaluated stat on a user.", async (t) => { const res = await mockApi.get( - `/api/v1/users/1/games/iidx/SP/stats/custom?mode=chart&prop=grade&chartID=${Testing511SPA.chartID}` + `/api/v1/users/1/games/iidx/SP/showcase/custom?mode=chart&prop=grade&chartID=${Testing511SPA.chartID}` ); t.strictSame(res.body.body, { @@ -153,13 +153,13 @@ t.test("GET /api/v1/users/:userID/games/:game/:playtype/stats/custom", (t) => { t.test("Should reject for invalid folderID.", async (t) => { const res = await mockApi.get( - `/api/v1/users/1/games/iidx/SP/stats/custom?mode=folder&prop=grade>e=4` + `/api/v1/users/1/games/iidx/SP/showcase/custom?mode=folder&prop=grade>e=4` ); t.equal(res.statusCode, 400, "Should reject for no folderID"); const res2 = await mockApi.get( - `/api/v1/users/1/games/iidx/SP/stats/custom?mode=folder&prop=grade>e=4&folderID=foo&folderID=bar` + `/api/v1/users/1/games/iidx/SP/showcase/custom?mode=folder&prop=grade>e=4&folderID=foo&folderID=bar` ); t.equal(res2.statusCode, 400, "Should reject for non-string folderID"); @@ -169,13 +169,13 @@ t.test("GET /api/v1/users/:userID/games/:game/:playtype/stats/custom", (t) => { t.test("Should reject for invalid chartID.", async (t) => { const res = await mockApi.get( - `/api/v1/users/1/games/iidx/SP/stats/custom?mode=chart&prop=grade>e=4` + `/api/v1/users/1/games/iidx/SP/showcase/custom?mode=chart&prop=grade>e=4` ); t.equal(res.statusCode, 400, "Should reject for no chartID"); const res2 = await mockApi.get( - `/api/v1/users/1/games/iidx/SP/stats/custom?mode=chart&prop=grade&chartID=foo&chartID=bar` + `/api/v1/users/1/games/iidx/SP/showcase/custom?mode=chart&prop=grade&chartID=foo&chartID=bar` ); t.equal(res2.statusCode, 400, "Should reject for non-string chartID"); @@ -185,7 +185,7 @@ t.test("GET /api/v1/users/:userID/games/:game/:playtype/stats/custom", (t) => { t.test("Should reject for invalid mode", async (t) => { const res = await mockApi.get( - `/api/v1/users/1/games/iidx/SP/stats/custom?mode=nonsense&prop=grade>e=4&chartID=foo` + `/api/v1/users/1/games/iidx/SP/showcase/custom?mode=nonsense&prop=grade>e=4&chartID=foo` ); t.equal(res.statusCode, 400); @@ -197,7 +197,7 @@ t.test("GET /api/v1/users/:userID/games/:game/:playtype/stats/custom", (t) => { }); // @todo #239 PUT UGPT-Stats needs some tests for input validation. -t.test("PUT /api/v1/users/:userID/games/:game/:playtype/stats", (t) => { +t.test("PUT /api/v1/users/:userID/games/:game/:playtype/showcase", (t) => { t.test("Requires the user to be authed as the requested user.", async (t) => { await db["api-tokens"].insert({ userID: 2, @@ -209,7 +209,7 @@ t.test("PUT /api/v1/users/:userID/games/:game/:playtype/stats", (t) => { }); const res = await mockApi - .put("/api/v1/users/1/games/iidx/SP/stats") + .put("/api/v1/users/1/games/iidx/SP/showcase") .set("Authorization", `Bearer alt_token`); t.equal(res.statusCode, 403); @@ -228,7 +228,7 @@ t.test("PUT /api/v1/users/:userID/games/:game/:playtype/stats", (t) => { }); const res = await mockApi - .put("/api/v1/users/1/games/iidx/SP/stats") + .put("/api/v1/users/1/games/iidx/SP/showcase") .set("Authorization", `Bearer alt_token`); t.equal(res.statusCode, 403); @@ -247,7 +247,7 @@ t.test("PUT /api/v1/users/:userID/games/:game/:playtype/stats", (t) => { }); const res = await mockApi - .put("/api/v1/users/1/games/iidx/SP/stats") + .put("/api/v1/users/1/games/iidx/SP/showcase") .set("Authorization", `Bearer alt_token`) .send([ { diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/stats/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/showcase/router.ts similarity index 86% rename from server/src/server/router/api/v1/users/_userID/games/_game/_playtype/stats/router.ts rename to server/src/server/router/api/v1/users/_userID/games/_game/_playtype/showcase/router.ts index bad458c90..e630f96ec 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/stats/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/showcase/router.ts @@ -1,15 +1,15 @@ import { Router } from "express"; -import { SYMBOL_TachiData } from "../../../../../../../../../../lib/constants/tachi"; -import { EvaluateUsersStatsShowcase } from "../../../../../../../../../../lib/showcase-stats/get-stats"; -import { ResolveUser } from "../../../../../../../../../../utils/user"; +import { SYMBOL_TachiData } from "lib/constants/tachi"; +import { EvaluateUsersStatsShowcase } from "lib/showcase/get-stats"; +import { ResolveUser } from "utils/user"; import p from "prudence"; -import { FormatPrError } from "../../../../../../../../../../utils/prudence"; +import { FormatPrError } from "utils/prudence"; import { ShowcaseStatDetails, GetGamePTConfig } from "tachi-common"; -import { EvaluateShowcaseStat } from "../../../../../../../../../../lib/showcase-stats/evaluator"; -import db from "../../../../../../../../../../external/mongo/db"; -import { RequirePermissions } from "../../../../../../../../../middleware/auth"; -import { RequireAuthedAsUser } from "../../../../middleware"; -import { GetRelatedStatDocuments } from "lib/showcase-stats/get-related"; +import { EvaluateShowcaseStat } from "lib/showcase/evaluator"; +import db from "external/mongo/db"; +import { RequirePermissions } from "server/middleware/auth"; +import { RequireAuthedAsUser } from "server/router/api/v1/users/_userID/middleware"; +import { GetRelatedStatDocuments } from "lib/showcase/get-related"; const router: Router = Router({ mergeParams: true }); /** @@ -17,7 +17,7 @@ const router: Router = Router({ mergeParams: true }); * * @param projectUser - Project another user's stats instead of their set stats. * - * @name GET /api/v1/users/:userID/games/:game/:playtype/stats + * @name GET /api/v1/users/:userID/games/:game/:playtype/showcase */ router.get("/", async (req, res) => { const user = req[SYMBOL_TachiData]!.requestedUser!; @@ -59,7 +59,7 @@ router.get("/", async (req, res) => { * @TODO: #237 This custom stat code accepts charts and folders from any game and any playtype - technically, * this is breaking rest quite painfully! * - * @name GET /api/v1/users/:userID/games/:game/:playtype/stats/custom + * @name GET /api/v1/users/:userID/games/:game/:playtype/showcase/custom */ router.get("/custom", async (req, res) => { const user = req[SYMBOL_TachiData]!.requestedUser!; @@ -141,7 +141,7 @@ router.get("/custom", async (req, res) => { /** * Replaces a user's preferred stats. * - * @name PUT /api/v1/users/:userID/games/:game/:playtype/stats + * @name PUT /api/v1/users/:userID/games/:game/:playtype/showcase */ router.put("/", RequireAuthedAsUser, RequirePermissions("customise_profile"), async (req, res) => { From 381e914f567b9d4a0ea3dafdb8f480f3ed99874b Mon Sep 17 00:00:00 2001 From: zkldi Date: Sat, 24 Jul 2021 03:45:55 +0100 Subject: [PATCH 08/15] add history endpoint --- .../src/server/middleware/request-logger.ts | 2 + .../games/_game/_playtype/router.test.ts | 48 ++++++++++++++++- .../_userID/games/_game/_playtype/router.ts | 54 ++++++++++++++++++- .../mock-db/game-stats-snapshots.json | 1 + server/src/utils/user.ts | 14 ++++- 5 files changed, 115 insertions(+), 4 deletions(-) create mode 100644 server/src/test-utils/mock-db/game-stats-snapshots.json diff --git a/server/src/server/middleware/request-logger.ts b/server/src/server/middleware/request-logger.ts index 25419f53f..dabedb385 100644 --- a/server/src/server/middleware/request-logger.ts +++ b/server/src/server/middleware/request-logger.ts @@ -1,5 +1,6 @@ import CreateLogCtx from "lib/logger/logger"; import { RequestHandler, Response } from "express-serve-static-core"; +import { SYMBOL_TachiAPIAuth } from "lib/constants/tachi"; const logger = CreateLogCtx(__filename); @@ -41,6 +42,7 @@ export const RequestLoggerMiddleware: RequestHandler = (req, res, next) => { statusCode: res.statusCode, requestQuery: req.query, requestBody: safeBody, + from: req[SYMBOL_TachiAPIAuth].userID, }; if (res.statusCode < 400) { diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.test.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.test.ts index 395f06e37..50e132b60 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.test.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.test.ts @@ -20,7 +20,7 @@ import { } from "../../../../../../../../../test-utils/test-data"; import deepmerge from "deepmerge"; -t.test("GET /api/v1/users/:userID/games/:game/:playtype/", (t) => { +t.test("GET /api/v1/users/:userID/games/:game/:playtype", (t) => { t.beforeEach(ResetDBState); t.test("Should return a users statistics for that game.", async (t) => { @@ -110,6 +110,52 @@ t.test("GET /api/v1/users/:userID/games/:game/:playtype/", (t) => { t.end(); }); +t.test("GET /api/v1/users/:userID/games/:game/:playtype/history", (t) => { + t.beforeEach(ResetDBState); + + t.test("Should return a users history snapshots for that gpt.", async (t) => { + await db["game-stats-snapshots"].insert([ + { + userID: 1, + game: "iidx", + playtype: "SP", + ranking: 5, + playcount: 100, + classes: {}, + ratings: {}, + timestamp: 1234, + }, + ]); + + const res = await mockApi.get("/api/v1/users/test_zkldi/games/iidx/SP/history"); + + res.body.body[1].timestamp = Math.floor(res.body.body[1].timestamp / 100_000); // by default, it's set to the current time. we can't + // test that nicely, so lets round it to the nearest 100 seconds. + + t.strictSame(res.body.body, [ + { + ranking: 5, + playcount: 100, + classes: {}, + ratings: {}, + timestamp: 1234, + }, + { + ranking: 1, + playcount: 1, + classes: {}, + ratings: {}, + // close enough, right? + timestamp: Math.floor(Date.now() / 100_000), + }, + ]); + + t.end(); + }); + + t.end(); +}); + t.test("GET /api/v1/users/:userID/games/:game/:playtype/goals", (t) => { t.beforeEach(ResetDBState); diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.ts index e59954f99..42313dc3d 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.ts @@ -2,7 +2,12 @@ import { Router } from "express"; import db from "../../../../../../../../../external/mongo/db"; import { SYMBOL_TachiData } from "../../../../../../../../../lib/constants/tachi"; import { GetRelevantSongsAndCharts } from "../../../../../../../../../utils/db"; -import { GetUsersRankingAndOutOf, GetUsersWithIDs } from "../../../../../../../../../utils/user"; +import { + GetUGPTPlaycount, + GetUsersRanking, + GetUsersRankingAndOutOf, + GetUsersWithIDs, +} from "../../../../../../../../../utils/user"; import { CheckUserPlayedGamePlaytype } from "./middleware"; import { FilterQuery } from "mongodb"; import { @@ -11,6 +16,7 @@ import { GetGamePTConfig, integer, PBScoreDocument, + UserGameStatsSnapshot, } from "tachi-common"; import { SearchGameSongsAndCharts } from "../../../../../../../../../lib/search/search"; import { FilterChartsAndSongs } from "../../../../../../../../../utils/scores"; @@ -22,7 +28,6 @@ import foldersFolderIDRouter from "./folders/_folderID/router"; import tablesRouter from "./tables/router"; import showcaseRouter from "./showcase/router"; import settingsRouter from "./settings/router"; - const router: Router = Router({ mergeParams: true }); router.use(CheckUserPlayedGamePlaytype); @@ -85,6 +90,51 @@ router.get("/", async (req, res) => { }); }); +/** + * Returns a users game-stats for the past 90 days. + * @name GET /api/v1/users/:userID/games/:game/:playtype/history + */ +router.get("/history", async (req, res) => { + const user = req[SYMBOL_TachiData]!.requestedUser!; + const stats = req[SYMBOL_TachiData]!.requestedUserGameStats!; + const game = req[SYMBOL_TachiData]!.game!; + const playtype = req[SYMBOL_TachiData]!.playtype!; + + const snapshots = (await db["game-stats-snapshots"].find( + { + userID: user.id, + game, + playtype, + }, + { + sort: { + timestamp: -1, + }, + // avoid sending so much garbage. + projection: { + userID: 0, + game: 0, + playtype: 0, + }, + limit: 90, + } + )) as Omit[]; + + const currentSnapshot: Omit = { + classes: stats.classes, + ratings: stats.ratings, + timestamp: Date.now(), // lazy, should probably be this midnight + playcount: await GetUGPTPlaycount(user.id, game, playtype), + ranking: await GetUsersRanking(stats), + }; + + return res.status(200).json({ + success: true, + description: `Successfully returned history for the past ${snapshots.length} days.`, + body: [...snapshots, currentSnapshot], + }); +}); + /** * Returns a user's set goals for this game. * @param unachieved - If set, achieved goals will be hidden. diff --git a/server/src/test-utils/mock-db/game-stats-snapshots.json b/server/src/test-utils/mock-db/game-stats-snapshots.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/server/src/test-utils/mock-db/game-stats-snapshots.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/server/src/utils/user.ts b/server/src/utils/user.ts index 136786cc5..d92b80eae 100644 --- a/server/src/utils/user.ts +++ b/server/src/utils/user.ts @@ -1,7 +1,15 @@ -import { integer, PublicUserDocument, UserGameStats, GetGamePTConfig } from "tachi-common"; +import { + integer, + PublicUserDocument, + UserGameStats, + GetGamePTConfig, + Game, + Playtypes, +} from "tachi-common"; import { FindOneResult } from "monk"; import db from "../external/mongo/db"; import CreateLogCtx from "../lib/logger/logger"; + const logger = CreateLogCtx(__filename); export const OMIT_PRIVATE_USER_RETURNS = { @@ -187,6 +195,10 @@ export async function GetUsersRanking(stats: UserGameStats) { return (aggRes[0].ranking + 1) as integer; } +export function GetUGPTPlaycount(userID: integer, game: Game, playtype: Playtypes[Game]) { + return db.scores.count({ userID, game, playtype }); +} + export async function GetUsersRankingAndOutOf(stats: UserGameStats) { const gptConfig = GetGamePTConfig(stats.game, stats.playtype); From f860b154da79b44498b5a82d720b0e3e213e968d Mon Sep 17 00:00:00 2001 From: zkldi Date: Sat, 24 Jul 2021 04:02:30 +0100 Subject: [PATCH 09/15] Fix bug where all unauthed requests just crash --- server/src/server/middleware/request-logger.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/server/middleware/request-logger.ts b/server/src/server/middleware/request-logger.ts index dabedb385..ababc2e11 100644 --- a/server/src/server/middleware/request-logger.ts +++ b/server/src/server/middleware/request-logger.ts @@ -42,7 +42,7 @@ export const RequestLoggerMiddleware: RequestHandler = (req, res, next) => { statusCode: res.statusCode, requestQuery: req.query, requestBody: safeBody, - from: req[SYMBOL_TachiAPIAuth].userID, + from: req[SYMBOL_TachiAPIAuth]?.userID ?? null, }; if (res.statusCode < 400) { From 671590127db96c87248c7dcedf89befa8969de26 Mon Sep 17 00:00:00 2001 From: zkldi Date: Sat, 24 Jul 2021 04:10:15 +0100 Subject: [PATCH 10/15] fix backwards returns in ugpt /history --- .../router/api/v1/users/_userID/games/_game/_playtype/router.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.ts index 42313dc3d..cac6f135c 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.ts @@ -131,7 +131,7 @@ router.get("/history", async (req, res) => { return res.status(200).json({ success: true, description: `Successfully returned history for the past ${snapshots.length} days.`, - body: [...snapshots, currentSnapshot], + body: [currentSnapshot, ...snapshots], }); }); From 57db7789115936487c11ea1ff04d304e509e0086 Mon Sep 17 00:00:00 2001 From: zkldi Date: Sat, 24 Jul 2021 04:10:35 +0100 Subject: [PATCH 11/15] fix order in tests, too --- .../_userID/games/_game/_playtype/router.test.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.test.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.test.ts index 50e132b60..b10e31403 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.test.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.test.ts @@ -133,13 +133,6 @@ t.test("GET /api/v1/users/:userID/games/:game/:playtype/history", (t) => { // test that nicely, so lets round it to the nearest 100 seconds. t.strictSame(res.body.body, [ - { - ranking: 5, - playcount: 100, - classes: {}, - ratings: {}, - timestamp: 1234, - }, { ranking: 1, playcount: 1, @@ -148,6 +141,13 @@ t.test("GET /api/v1/users/:userID/games/:game/:playtype/history", (t) => { // close enough, right? timestamp: Math.floor(Date.now() / 100_000), }, + { + ranking: 5, + playcount: 100, + classes: {}, + ratings: {}, + timestamp: 1234, + }, ]); t.end(); From dbe4fc22798c659c704268bed3b45e44d6e1c5a2 Mon Sep 17 00:00:00 2001 From: zkldi Date: Sat, 24 Jul 2021 04:23:28 +0100 Subject: [PATCH 12/15] log reqIp --- server/src/server/middleware/request-logger.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/server/src/server/middleware/request-logger.ts b/server/src/server/middleware/request-logger.ts index ababc2e11..eb17f2778 100644 --- a/server/src/server/middleware/request-logger.ts +++ b/server/src/server/middleware/request-logger.ts @@ -43,6 +43,7 @@ export const RequestLoggerMiddleware: RequestHandler = (req, res, next) => { requestQuery: req.query, requestBody: safeBody, from: req[SYMBOL_TachiAPIAuth]?.userID ?? null, + fromIp: req.ip, }; if (res.statusCode < 400) { From 84c529e1f41e559e0bfbc31c084735a77e945bf6 Mon Sep 17 00:00:00 2001 From: zkldi Date: Sat, 24 Jul 2021 04:26:02 +0100 Subject: [PATCH 13/15] bump slightly broken tests in showcase --- .../games/_game/_playtype/showcase/router.test.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) 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 a3fd770a3..57c48bd40 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 @@ -131,9 +131,11 @@ t.test("GET /api/v1/users/:userID/games/:game/:playtype/showcase/custom", (t) => `/api/v1/users/1/games/iidx/SP/showcase/custom?mode=folder&prop=grade>e=3&folderID=${TestingIIDXFolderSP10.folderID}` ); - t.strictSame(res.body.body, { - value: 1, - outOf: 1, + t.hasStrict(res.body.body, { + result: { + value: 1, + outOf: 1, + }, }); t.end(); @@ -144,8 +146,8 @@ t.test("GET /api/v1/users/:userID/games/:game/:playtype/showcase/custom", (t) => `/api/v1/users/1/games/iidx/SP/showcase/custom?mode=chart&prop=grade&chartID=${Testing511SPA.chartID}` ); - t.strictSame(res.body.body, { - value: IIDX_GRADES.AAA, + t.hasStrict(res.body.body, { + result: { value: IIDX_GRADES.AAA }, }); t.end(); From 9f46608799cb6665ca300ece15464fa3e6a4a7bd Mon Sep 17 00:00:00 2001 From: zkldi Date: Sat, 24 Jul 2021 05:20:11 +0100 Subject: [PATCH 14/15] fix incorrect rounding in test --- .../api/v1/users/_userID/games/_game/_playtype/router.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.test.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.test.ts index b10e31403..e172e0472 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.test.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.test.ts @@ -129,7 +129,7 @@ t.test("GET /api/v1/users/:userID/games/:game/:playtype/history", (t) => { const res = await mockApi.get("/api/v1/users/test_zkldi/games/iidx/SP/history"); - res.body.body[1].timestamp = Math.floor(res.body.body[1].timestamp / 100_000); // by default, it's set to the current time. we can't + res.body.body[0].timestamp = Math.floor(res.body.body[1].timestamp / 100_000); // by default, it's set to the current time. we can't // test that nicely, so lets round it to the nearest 100 seconds. t.strictSame(res.body.body, [ From d9f9875f4738596a0abbd57ba96d991cf338c3fb Mon Sep 17 00:00:00 2001 From: zkldi Date: Sat, 24 Jul 2021 05:46:37 +0100 Subject: [PATCH 15/15] fix another typo --- .../api/v1/users/_userID/games/_game/_playtype/router.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.test.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.test.ts index e172e0472..c179e9674 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.test.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.test.ts @@ -129,7 +129,7 @@ t.test("GET /api/v1/users/:userID/games/:game/:playtype/history", (t) => { const res = await mockApi.get("/api/v1/users/test_zkldi/games/iidx/SP/history"); - res.body.body[0].timestamp = Math.floor(res.body.body[1].timestamp / 100_000); // by default, it's set to the current time. we can't + res.body.body[0].timestamp = Math.floor(res.body.body[0].timestamp / 100_000); // by default, it's set to the current time. we can't // test that nicely, so lets round it to the nearest 100 seconds. t.strictSame(res.body.body, [