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",
});
},