From 9d8bf7f421d37b15c0d6063598d9ffd22f9e0685 Mon Sep 17 00:00:00 2001 From: noelle <99384603+hewoicvewse@users.noreply.github.com> Date: Mon, 5 Sep 2022 03:26:41 +0900 Subject: [PATCH 1/9] initial sieglinde ec/hc table support --- .../_playtype/content/bms-router.test.ts | 61 +++++++ .../_game/_playtype/content/bms-router.ts | 164 ++++++++++++++++++ .../src/server/router/api/v1/games/router.ts | 3 + 3 files changed, 228 insertions(+) create mode 100644 server/src/server/router/api/v1/games/_game/_playtype/content/bms-router.test.ts create mode 100644 server/src/server/router/api/v1/games/_game/_playtype/content/bms-router.ts diff --git a/server/src/server/router/api/v1/games/_game/_playtype/content/bms-router.test.ts b/server/src/server/router/api/v1/games/_game/_playtype/content/bms-router.test.ts new file mode 100644 index 000000000..063e58db8 --- /dev/null +++ b/server/src/server/router/api/v1/games/_game/_playtype/content/bms-router.test.ts @@ -0,0 +1,61 @@ +import dm from "deepmerge"; +import db from "external/mongo/db"; +import { GetGamePTConfig } from "tachi-common"; +import t from "tap"; +import { mkFakeGameStats, mkFakeUser } from "test-utils/misc"; +import mockApi from "test-utils/mock-api"; +import ResetDBState from "test-utils/resets"; +import { FakeOtherUser } from "test-utils/test-data"; +import type { PublicUserDocument, UserGameStats } from "tachi-common"; + +t.test("GET /api/v1/games/bms/7K/content", (t) => { + t.test("Should exist.", async (t) => { + const res = await mockApi.get("/api/v1/games/bms/7K/content"); + + t.equal(res.body.success, true); + + t.end(); + }); + + t.end(); +}); + +t.test("GET /api/v1/games/bms/7K/content/sieglindeEC", (t) => { + t.test("Should point to the right file.", async (t) => { + const res = await mockApi.get("/api/v1/games/bms/7K/content/sieglindeEC/header.json"); + + t.equal(res.body.data_url, "./body.json"); + + t.end(); + }); + + t.test("`body.json` should return an array.", async (t) => { + const res = await mockApi.get("/api/v1/games/bms/7K/content/sieglindeEC/body.json"); + + t.equal(Array.isArray(res.body), true); + + t.end(); + }); + + t.end(); +}); + +t.test("GET /api/v1/games/bms/7K/content/sieglindeHC", (t) => { + t.test("Should point to the right file.", async (t) => { + const res = await mockApi.get("/api/v1/games/bms/7K/content/sieglindeHC/header.json"); + + t.equal(res.body.data_url, "./body.json"); + + t.end(); + }); + + t.test("`body.json` should return an array.", async (t) => { + const res = await mockApi.get("/api/v1/games/bms/7K/content/sieglindeHC/body.json"); + + t.equal(Array.isArray(res.body), true); + + t.end(); + }); + + t.end(); +}); diff --git a/server/src/server/router/api/v1/games/_game/_playtype/content/bms-router.ts b/server/src/server/router/api/v1/games/_game/_playtype/content/bms-router.ts new file mode 100644 index 000000000..a06ffa19c --- /dev/null +++ b/server/src/server/router/api/v1/games/_game/_playtype/content/bms-router.ts @@ -0,0 +1,164 @@ +import { Router } from "express"; +import db from "external/mongo/db"; +import CreateLogCtx from "lib/logger/logger"; +import { GetFolderCharts, GetFoldersFromTable } from "utils/folder"; +import { GetTachiData } from "utils/req-tachi-data"; +import type { Request, Response } from "express"; +import type { ChartDocument } from "tachi-common/types"; + +const logger = CreateLogCtx(__filename); + +const router: Router = Router({ mergeParams: true }); + + +/** + * Returns "Endpoint exists" + * + * @name GET /api/v1/games/bms/7K/content + */ +router.get("/", (req, res) => { + return res.status(200).json({ + success: true, + description: "Endpoint exists", + }); +}); + +function CreateAndMountTable( + router: Router, + tableName: string, + headerHandler: (req: Request, res: Response) => any, + bodyHandler: (req: Request, res: Response) => any +) { + router.get(`/${tableName}`, (req, res) => { + return res.status(200).send(` + + + + This is a stub page for beatoraja compatibility. No useful information here! + `); + }); + + router.get(`/${tableName}/header.json`, headerHandler); + + router.get(`/${tableName}/body.json`, bodyHandler); +} + +/** + * Returns a difficulty table of all charts rated according to their Sieglinde values for easy clears + * + * @name GET /api/v1/games/bms/7k/content/sieglindeEC + */ +CreateAndMountTable( + router, + "sieglindeEC", + /* header.json */(_req, res) => { + return res.status(200).send({ + name: "Sieglinde EC", + symbol: "sgl-", + data_url: "./body.json", + }); + }, + /* body.json */ async (req, res) => { + const game = GetTachiData(req, "game"); + const table = await db.tables.findOne({ + tableID: "bms-7K-sgl-EC", + game, + playtype: "7K", + }); + + if (!table) { + logger.error("Could not find table bms-7K-sgl-EC (even though it should be there...)"); + return res.status(500).json([]); + } + + const folders = await GetFoldersFromTable(table); + const response = []; + + for (const folder of folders) { + const data = await GetFolderCharts(folder, {}, true); + const charts = data.charts as Array>; + const songs = data.songs; + + for (const chart of charts) { + const song = songs.find((song) => song.id === chart.songID); + + if (!song) { + response.push({ + level: folder.title, + md5: chart.data.hashMD5, + }); + } else { + response.push({ + level: folder.title, + title: song.title, + artist: song.artist, + md5: chart.data.hashMD5, + }); + } + } + } + + return res.status(200).json(response); + } +); + +/** + * Returns a difficulty table of all charts rated according to their Sieglinde values for hard clears + * + * @name GET /api/v1/games/bms/7k/content/sieglindeHC + */ +CreateAndMountTable( + router, + "sieglindeHC", + /* header.json */(_req, res) => { + return res.status(200).send({ + name: "Sieglinde HC", + symbol: "sgl-", + data_url: "./body.json", + }); + }, + /* body.json */ async (req, res) => { + const game = GetTachiData(req, "game"); + const table = await db.tables.findOne({ + tableID: "bms-7K-sgl-HC", + game, + playtype: "7K", + }); + + if (!table) { + logger.error("Could not find table bms-7K-sgl-HC (even though it should be there...)"); + return res.status(500).json([]); + } + + const folders = await GetFoldersFromTable(table); + const response = []; + + for (const folder of folders) { + const data = await GetFolderCharts(folder, {}, true); + const charts = data.charts as Array>; + const songs = data.songs; + + for (const chart of charts) { + const song = songs.find((song) => song.id === chart.songID); + + if (!song) { + response.push({ + level: folder.title, + md5: chart.data.hashMD5, + }); + } else { + response.push({ + level: folder.title, + title: song.title, + artist: song.artist, + md5: chart.data.hashMD5, + }); + } + } + } + + return res.status(200).json(response); + } +); + +export default router; diff --git a/server/src/server/router/api/v1/games/router.ts b/server/src/server/router/api/v1/games/router.ts index 46cfdc403..619342edc 100644 --- a/server/src/server/router/api/v1/games/router.ts +++ b/server/src/server/router/api/v1/games/router.ts @@ -1,3 +1,4 @@ +import bmsContentRouter from "./_game/_playtype/content/bms-router"; import gameRouter from "./_game/router"; import { Router } from "express"; import { TachiConfig } from "lib/setup/config"; @@ -27,4 +28,6 @@ router.get("/", (req, res) => { router.use("/:game", gameRouter); +router.use("/bms/7K/content", bmsContentRouter); + export default router; From 9c687c230ebfcb8d94b993ae4abf8a2eb3e87d5e Mon Sep 17 00:00:00 2001 From: noelle <99384603+hewoicvewse@users.noreply.github.com> Date: Mon, 5 Sep 2022 03:29:00 +0900 Subject: [PATCH 2/9] remove unused imports from test --- .../v1/games/_game/_playtype/content/bms-router.test.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/server/src/server/router/api/v1/games/_game/_playtype/content/bms-router.test.ts b/server/src/server/router/api/v1/games/_game/_playtype/content/bms-router.test.ts index 063e58db8..40d82b731 100644 --- a/server/src/server/router/api/v1/games/_game/_playtype/content/bms-router.test.ts +++ b/server/src/server/router/api/v1/games/_game/_playtype/content/bms-router.test.ts @@ -1,12 +1,5 @@ -import dm from "deepmerge"; -import db from "external/mongo/db"; -import { GetGamePTConfig } from "tachi-common"; import t from "tap"; -import { mkFakeGameStats, mkFakeUser } from "test-utils/misc"; import mockApi from "test-utils/mock-api"; -import ResetDBState from "test-utils/resets"; -import { FakeOtherUser } from "test-utils/test-data"; -import type { PublicUserDocument, UserGameStats } from "tachi-common"; t.test("GET /api/v1/games/bms/7K/content", (t) => { t.test("Should exist.", async (t) => { From c1507eb94c740e30d848084edd87546d6c927caf Mon Sep 17 00:00:00 2001 From: zkldi Date: Sun, 4 Sep 2022 19:48:38 +0100 Subject: [PATCH 3/9] fix: explicitly document gpt-specifity of content endpoints we also don't want to take advantage of an express gimmick, which is that it likes to prefer the most "specific" route to a path. I'd rather be far more explicit and mount these routes first. --- server/src/server/router/api/v1/games/router.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/server/src/server/router/api/v1/games/router.ts b/server/src/server/router/api/v1/games/router.ts index 619342edc..dfb0ba97e 100644 --- a/server/src/server/router/api/v1/games/router.ts +++ b/server/src/server/router/api/v1/games/router.ts @@ -26,8 +26,14 @@ router.get("/", (req, res) => { }); }); -router.use("/:game", gameRouter); +// "Content" endpoints are game-playtype specific. +// as such, we want to mount them *before* any generic endpoints +// so that they'll guaranteeably get hit. +// This is a bit unorthodox, but arguably necessary, as all /content +// endpoints really are *entirely* GPT specific. router.use("/bms/7K/content", bmsContentRouter); +router.use("/:game", gameRouter); + export default router; From 7b174fb3ad688956d86adfb238373a914ee53963 Mon Sep 17 00:00:00 2001 From: zkldi Date: Sun, 4 Sep 2022 19:51:56 +0100 Subject: [PATCH 4/9] docs: improve documentation for CreateAndMountTable --- .../v1/games/_game/_playtype/content/bms-router.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/server/src/server/router/api/v1/games/_game/_playtype/content/bms-router.ts b/server/src/server/router/api/v1/games/_game/_playtype/content/bms-router.ts index a06ffa19c..e34d03bb7 100644 --- a/server/src/server/router/api/v1/games/_game/_playtype/content/bms-router.ts +++ b/server/src/server/router/api/v1/games/_game/_playtype/content/bms-router.ts @@ -10,7 +10,6 @@ const logger = CreateLogCtx(__filename); const router: Router = Router({ mergeParams: true }); - /** * Returns "Endpoint exists" * @@ -23,6 +22,13 @@ router.get("/", (req, res) => { }); }); +/** + * Utility function for mounting a BMS-style Table. This defines + * a header.json endpoint, and a body.json endpoint. + * + * It also defines a HTML-emitting endpoint that contains a pointer + * to said header.json file. + */ function CreateAndMountTable( router: Router, tableName: string, @@ -51,7 +57,7 @@ function CreateAndMountTable( CreateAndMountTable( router, "sieglindeEC", - /* header.json */(_req, res) => { + /* header.json */ (_req, res) => { return res.status(200).send({ name: "Sieglinde EC", symbol: "sgl-", @@ -110,7 +116,7 @@ CreateAndMountTable( CreateAndMountTable( router, "sieglindeHC", - /* header.json */(_req, res) => { + /* header.json */ (_req, res) => { return res.status(200).send({ name: "Sieglinde HC", symbol: "sgl-", From c560df37d8e477c8666407b70094490157e28381 Mon Sep 17 00:00:00 2001 From: zkldi Date: Sun, 4 Sep 2022 20:00:24 +0100 Subject: [PATCH 5/9] fix: formalise logged errmsgs --- .../api/v1/games/_game/_playtype/content/bms-router.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/server/src/server/router/api/v1/games/_game/_playtype/content/bms-router.ts b/server/src/server/router/api/v1/games/_game/_playtype/content/bms-router.ts index e34d03bb7..a6427bae1 100644 --- a/server/src/server/router/api/v1/games/_game/_playtype/content/bms-router.ts +++ b/server/src/server/router/api/v1/games/_game/_playtype/content/bms-router.ts @@ -73,7 +73,9 @@ CreateAndMountTable( }); if (!table) { - logger.error("Could not find table bms-7K-sgl-EC (even though it should be there...)"); + logger.error( + "Could not find table bms-7K-sgl-EC. Cannot convert sieglinde tables into BMS form!" + ); return res.status(500).json([]); } @@ -132,7 +134,9 @@ CreateAndMountTable( }); if (!table) { - logger.error("Could not find table bms-7K-sgl-HC (even though it should be there...)"); + logger.error( + "Could not find table bms-7K-sgl-HC. Cannot convert sieglinde tables into BMS form!" + ); return res.status(500).json([]); } From 67485e98373de0eaa69d87d16db4f1db5f49dc4c Mon Sep 17 00:00:00 2001 From: zkldi Date: Sun, 4 Sep 2022 20:03:31 +0100 Subject: [PATCH 6/9] refactor: improve performance of song lookups use lookup tables to optimise looking up songs; the former solution was O(n^2), this one is O(1) with a small hit to memory. --- common/src/utils/util.ts | 10 ++++++++++ .../api/v1/games/_game/_playtype/content/bms-router.ts | 10 ++++++---- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/common/src/utils/util.ts b/common/src/utils/util.ts index 376c30834..8e6dd1ba5 100644 --- a/common/src/utils/util.ts +++ b/common/src/utils/util.ts @@ -253,3 +253,13 @@ export function FormatSieglindePMS(sgl: number): string { return `●${(sgl - 12).toFixed(2)}`; } + +export function CreateSongMap(songs: Array>) { + const songMap = new Map>(); + + for (const song of songs) { + songMap.set(song.id, song); + } + + return songMap; +} diff --git a/server/src/server/router/api/v1/games/_game/_playtype/content/bms-router.ts b/server/src/server/router/api/v1/games/_game/_playtype/content/bms-router.ts index a6427bae1..e3ab53774 100644 --- a/server/src/server/router/api/v1/games/_game/_playtype/content/bms-router.ts +++ b/server/src/server/router/api/v1/games/_game/_playtype/content/bms-router.ts @@ -1,6 +1,8 @@ import { Router } from "express"; import db from "external/mongo/db"; import CreateLogCtx from "lib/logger/logger"; +import { CreateSongMap } from "tachi-common"; +import { integer, SongDocument } from "tachi-common/types"; import { GetFolderCharts, GetFoldersFromTable } from "utils/folder"; import { GetTachiData } from "utils/req-tachi-data"; import type { Request, Response } from "express"; @@ -85,10 +87,10 @@ CreateAndMountTable( for (const folder of folders) { const data = await GetFolderCharts(folder, {}, true); const charts = data.charts as Array>; - const songs = data.songs; + const songMap = CreateSongMap(data.songs); for (const chart of charts) { - const song = songs.find((song) => song.id === chart.songID); + const song = songMap.get(chart.songID); if (!song) { response.push({ @@ -146,10 +148,10 @@ CreateAndMountTable( for (const folder of folders) { const data = await GetFolderCharts(folder, {}, true); const charts = data.charts as Array>; - const songs = data.songs; + const songMap = CreateSongMap(data.songs); for (const chart of charts) { - const song = songs.find((song) => song.id === chart.songID); + const song = songMap.get(chart.songID); if (!song) { response.push({ From 73d2190f0e9d8bb0cf76b3709499695339046a70 Mon Sep 17 00:00:00 2001 From: zkldi Date: Sun, 4 Sep 2022 20:21:49 +0100 Subject: [PATCH 7/9] fix: make symbols unique not sure what they're actually used for tbh. --- .../api/v1/games/_game/_playtype/content/bms-router.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/server/src/server/router/api/v1/games/_game/_playtype/content/bms-router.ts b/server/src/server/router/api/v1/games/_game/_playtype/content/bms-router.ts index e3ab53774..611986bd3 100644 --- a/server/src/server/router/api/v1/games/_game/_playtype/content/bms-router.ts +++ b/server/src/server/router/api/v1/games/_game/_playtype/content/bms-router.ts @@ -2,7 +2,6 @@ import { Router } from "express"; import db from "external/mongo/db"; import CreateLogCtx from "lib/logger/logger"; import { CreateSongMap } from "tachi-common"; -import { integer, SongDocument } from "tachi-common/types"; import { GetFolderCharts, GetFoldersFromTable } from "utils/folder"; import { GetTachiData } from "utils/req-tachi-data"; import type { Request, Response } from "express"; @@ -62,7 +61,7 @@ CreateAndMountTable( /* header.json */ (_req, res) => { return res.status(200).send({ name: "Sieglinde EC", - symbol: "sgl-", + symbol: "sgl-EC", data_url: "./body.json", }); }, @@ -123,7 +122,7 @@ CreateAndMountTable( /* header.json */ (_req, res) => { return res.status(200).send({ name: "Sieglinde HC", - symbol: "sgl-", + symbol: "sgl-HC", data_url: "./body.json", }); }, From 3ce11d88440d12204920b116eccae27bd0626f1e Mon Sep 17 00:00:00 2001 From: zkldi Date: Sun, 4 Sep 2022 20:38:21 +0100 Subject: [PATCH 8/9] fix: don't depend on reqTachiData for game the only possibility for game here is "bms". we dont actually have access to ReqTachiData here because of the bizarre mounting stuff. --- .../api/v1/games/_game/_playtype/content/bms-router.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/server/src/server/router/api/v1/games/_game/_playtype/content/bms-router.ts b/server/src/server/router/api/v1/games/_game/_playtype/content/bms-router.ts index 611986bd3..6569d99a6 100644 --- a/server/src/server/router/api/v1/games/_game/_playtype/content/bms-router.ts +++ b/server/src/server/router/api/v1/games/_game/_playtype/content/bms-router.ts @@ -66,7 +66,8 @@ CreateAndMountTable( }); }, /* body.json */ async (req, res) => { - const game = GetTachiData(req, "game"); + const game = "bms"; + const table = await db.tables.findOne({ tableID: "bms-7K-sgl-EC", game, @@ -127,7 +128,8 @@ CreateAndMountTable( }); }, /* body.json */ async (req, res) => { - const game = GetTachiData(req, "game"); + const game = "bms"; + const table = await db.tables.findOne({ tableID: "bms-7K-sgl-HC", game, From 6e4986a91f791a690c6a4b5cbd4331df6dab60e5 Mon Sep 17 00:00:00 2001 From: zkldi Date: Mon, 5 Sep 2022 00:07:25 +0100 Subject: [PATCH 9/9] Revert "fix: make symbols unique" This reverts commit 73d2190f0e9d8bb0cf76b3709499695339046a70. This actually results in table names in-game being displayed as "sgl-EC EC ...", which sucks. Mistake on my part. --- .../api/v1/games/_game/_playtype/content/bms-router.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/server/src/server/router/api/v1/games/_game/_playtype/content/bms-router.ts b/server/src/server/router/api/v1/games/_game/_playtype/content/bms-router.ts index 6569d99a6..cedb417e9 100644 --- a/server/src/server/router/api/v1/games/_game/_playtype/content/bms-router.ts +++ b/server/src/server/router/api/v1/games/_game/_playtype/content/bms-router.ts @@ -2,6 +2,7 @@ import { Router } from "express"; import db from "external/mongo/db"; import CreateLogCtx from "lib/logger/logger"; import { CreateSongMap } from "tachi-common"; +import { integer, SongDocument } from "tachi-common/types"; import { GetFolderCharts, GetFoldersFromTable } from "utils/folder"; import { GetTachiData } from "utils/req-tachi-data"; import type { Request, Response } from "express"; @@ -61,7 +62,7 @@ CreateAndMountTable( /* header.json */ (_req, res) => { return res.status(200).send({ name: "Sieglinde EC", - symbol: "sgl-EC", + symbol: "sgl-", data_url: "./body.json", }); }, @@ -123,7 +124,7 @@ CreateAndMountTable( /* header.json */ (_req, res) => { return res.status(200).send({ name: "Sieglinde HC", - symbol: "sgl-HC", + symbol: "sgl-", data_url: "./body.json", }); },