Merge pull request #330 from zkldi:zkldi/issue-139-Optimise-GET-/ir/beatoraja/chart/chartSHA256/scores

Optimise GET /ir/beatoraja/chart/:chartSHA256/scores
This commit is contained in:
zkldi
2021-09-04 13:18:57 +01:00
committed by GitHub
3 changed files with 59 additions and 189 deletions
@@ -1,11 +1,8 @@
import t from "tap";
import db from "external/mongo/db";
import ResetDBState from "test-utils/resets";
import { GetKTDataJSON } from "test-utils/test-data";
import { TachiPBScoreToBeatorajaFormat } from "./convert-scores";
import { ScoreDocument, PBScoreDocument } from "tachi-common";
import { Random20Hex } from "utils/misc";
import deepmerge from "deepmerge";
import { PBScoreDocument } from "tachi-common";
import { TachiScoreDataToBeatorajaFormat } from "./convert-scores";
const gazerChart = GetKTDataJSON("./tachi/bms-gazer-chart.json");
@@ -24,43 +21,30 @@ const pbScore = {
userID: 1,
} as unknown as PBScoreDocument<"bms:7K" | "bms:14K">;
t.test("#TachiPBScoreToBeatorajaFormat", (t) => {
t.test("#TachiScoreDataToBeatorajaFormat", (t) => {
t.beforeEach(ResetDBState);
t.beforeEach(async () => {
await db.scores.insert(
[1, 2, 3, 4, 5].map(() => ({
userID: 1,
chartID: gazerChart.chartID,
scoreID: Random20Hex(),
})) as ScoreDocument[]
t.test("Should convert score.", (t) => {
const res = TachiScoreDataToBeatorajaFormat(
pbScore,
gazerChart.data.hashSHA256,
"",
gazerChart.data.notecount,
0
);
await db.scores.insert({
scoreID: "mock_lampPB",
scoreData: {
hitMeta: {},
},
scoreMeta: {
inputDevice: "KEYBOARD",
},
} as ScoreDocument);
});
t.test("Should convert score.", async (t) => {
const res = await TachiPBScoreToBeatorajaFormat(pbScore, gazerChart, 1);
t.strictSame(
res,
{
sha256: "195fe1be5c3e74fccd04dc426e05f8a9cfa8a1059c339d0a23e99f63661f0b7d",
player: "",
playcount: 5,
playcount: 0,
clear: 5,
date: 0,
maxcombo: 0,
deviceType: "KEYBOARD",
deviceType: null,
gauge: 0,
random: 0,
random: null,
passnotes: 0,
minbp: 0,
notes: 2256,
@@ -71,85 +55,13 @@ t.test("#TachiPBScoreToBeatorajaFormat", (t) => {
t.end();
});
t.test("Should emplace username if requestingUserID is not the pbscore owner", async (t) => {
const res = await TachiPBScoreToBeatorajaFormat(pbScore, gazerChart, 2);
t.strictSame(
res,
{
sha256: "195fe1be5c3e74fccd04dc426e05f8a9cfa8a1059c339d0a23e99f63661f0b7d",
player: "test_zkldi",
playcount: 5,
clear: 5,
date: 0,
maxcombo: 0,
deviceType: "KEYBOARD",
gauge: 0,
random: 0,
passnotes: 0,
minbp: 0,
notes: 2256,
},
"Should return the beatoraja score format."
);
t.end();
});
t.test("Should return random if one is present", async (t) => {
await db.scores.remove({ scoreID: "mock_lampPB" });
await db.scores.insert({
scoreID: "mock_lampPB",
scoreData: {
hitMeta: {},
},
scoreMeta: {
inputDevice: "KEYBOARD",
random: "RANDOM",
},
} as ScoreDocument);
const res = await TachiPBScoreToBeatorajaFormat(pbScore, gazerChart, 2);
t.strictSame(
res,
{
sha256: "195fe1be5c3e74fccd04dc426e05f8a9cfa8a1059c339d0a23e99f63661f0b7d",
player: "test_zkldi",
playcount: 5,
clear: 5,
date: 0,
maxcombo: 0,
deviceType: "KEYBOARD",
gauge: 0,
random: 2,
passnotes: 0,
minbp: 0,
notes: 2256,
},
"Should return the beatoraja score format."
);
t.end();
});
t.test("Should skip random if score is 14K", async (t) => {
await db.scores.remove({ scoreID: "mock_lampPB" });
await db.scores.insert({
scoreID: "mock_lampPB",
scoreData: {
hitMeta: {},
},
scoreMeta: {
inputDevice: "KEYBOARD",
random: "RANDOM",
},
} as ScoreDocument);
const res = await TachiPBScoreToBeatorajaFormat(
deepmerge(pbScore, { playtype: "14K" }),
gazerChart,
2
t.test("Should emplace username if requestingUserID is not the pbscore owner", (t) => {
const res = TachiScoreDataToBeatorajaFormat(
pbScore,
gazerChart.data.hashSHA256,
"test_zkldi",
gazerChart.data.notecount,
0
);
t.strictSame(
@@ -157,13 +69,13 @@ t.test("#TachiPBScoreToBeatorajaFormat", (t) => {
{
sha256: "195fe1be5c3e74fccd04dc426e05f8a9cfa8a1059c339d0a23e99f63661f0b7d",
player: "test_zkldi",
playcount: 5,
playcount: 0,
clear: 5,
date: 0,
maxcombo: 0,
deviceType: "KEYBOARD",
deviceType: null,
gauge: 0,
random: 0,
random: null,
passnotes: 0,
minbp: 0,
notes: 2256,
@@ -174,15 +86,5 @@ t.test("#TachiPBScoreToBeatorajaFormat", (t) => {
t.end();
});
t.test("Should throw severe if no lampPB exists.", async (t) => {
await db.scores.remove({ scoreID: "mock_lampPB" });
t.rejects(() => TachiPBScoreToBeatorajaFormat(pbScore, gazerChart, 2), {
message: /User 1's PB on.*has no lampPB/u,
});
t.end();
});
t.end();
});
@@ -36,48 +36,6 @@ type BeatorajaPartialScoreFormat = {
export type BeatorajaIRScoreFormat = BeatorajaPartialScoreFormat & BeatorajaScoreJudgements;
const logger = CreateLogCtx(__filename);
/**
* Converts a Tachi Score PB into the beatoraja IR format.
* @param pbScore - The PB score to convert.
* @param chart - The chart document the PB score belongs to.
* @param requestingUserID - The user who requested this conversion. This is
* because beatoraja uses the empty string to dictate that the score was from
* the requesting user.
*/
export async function TachiPBScoreToBeatorajaFormat(
pbScore: PBScoreDocument<"bms:7K" | "bms:14K">,
chart: ChartDocument<"bms:7K" | "bms:14K">,
requestingUserID: integer
) {
const playcount = await db.scores.count({ userID: pbScore.userID, chartID: chart.chartID });
const username =
pbScore.userID === requestingUserID ? "" : await GetUsernameFromUserID(pbScore.userID);
const lampPB = (await db.scores.findOne({
scoreID: pbScore.composedFrom.lampPB,
})) as ScoreDocument<"bms:7K" | "bms:14K"> | null;
if (!lampPB) {
logger.severe(
`User ${pbScore.userID}'s PB on ${chart.chartID} has no lampPB, but references ${pbScore.composedFrom.lampPB}.`
);
throw new Error(
`User ${pbScore.userID}'s PB on ${chart.chartID} has no lampPB, but references ${pbScore.composedFrom.lampPB}.`
);
}
return TachiScoreDataToBeatorajaFormat(
pbScore,
chart.data.hashSHA256,
username,
chart.data.notecount,
playcount,
lampPB.scoreMeta.inputDevice,
lampPB.scoreMeta.random
);
}
/**
* Converts various data from Tachi to the beatoraja format.
* @param pbScore - The users PB Score document for this chart.
@@ -89,28 +47,15 @@ export async function TachiPBScoreToBeatorajaFormat(
* @param random - What random modifier was used.
* @returns A Beatoraja Score Document.
*/
function TachiScoreDataToBeatorajaFormat(
export function TachiScoreDataToBeatorajaFormat(
pbScore: PBScoreDocument<"bms:7K" | "bms:14K">,
sha256: string,
username: string,
notecount: integer,
playcount: integer,
inputDevice: ScoreDocument<"bms:7K" | "bms:14K">["scoreMeta"]["inputDevice"],
random: ScoreDocument<"bms:7K" | "bms:14K">["scoreMeta"]["random"]
playcount: integer
) {
const scoreData = pbScore.scoreData;
let rajaRandom = 0 as const;
// Beatoraja does not support DP randoms - it only stores them in replay files,
// and only sends the left hand random here.
if (pbScore.playtype === "7K") {
if (random) {
// @ts-expect-error Invalid indexing because playtype removes the random tuple.
rajaRandom = RAN_INDEXES[random];
}
}
const beatorajaScore: BeatorajaPartialScoreFormat = {
sha256,
player: username,
@@ -118,9 +63,9 @@ function TachiScoreDataToBeatorajaFormat(
clear: LAMP_TO_BEATORAJA[scoreData.lampIndex] ?? 0,
date: pbScore.timeAchieved ?? 0,
maxcombo: scoreData.hitMeta.maxCombo ?? 0,
deviceType: inputDevice ?? null,
gauge: scoreData.hitMeta.gauge ?? 0,
random: rajaRandom,
deviceType: null, // These two are now unsupported due to performance concerns.
random: null,
minbp: scoreData.hitMeta.bp ?? 0,
passnotes: 0,
notes: notecount,
@@ -1,8 +1,8 @@
import { ChartDocument, PBScoreDocument } from "tachi-common";
import { Router, RequestHandler } from "express";
import db from "external/mongo/db";
import { SYMBOL_TachiAPIAuth, SYMBOL_TachiData } from "lib/constants/tachi";
import { TachiPBScoreToBeatorajaFormat } from "./convert-scores";
import { SYMBOL_TachiData } from "lib/constants/tachi";
import { TachiScoreDataToBeatorajaFormat } from "./convert-scores";
import { AssignToReqTachiData } from "utils/req-tachi-data";
const router: Router = Router({ mergeParams: true });
@@ -37,14 +37,37 @@ router.get("/scores", async (req, res) => {
chartID: chart.chartID,
})) as PBScoreDocument<"bms:7K" | "bms:14K">[];
// @todo #139 Optimise GET /ir/beatoraja/chart/:chartSHA256/scores
// @optimisable - This should be solved with a couple queries and a hashmap.
const beatorajaScores = await Promise.all(
scores.map((e) =>
TachiPBScoreToBeatorajaFormat(e, chart, req[SYMBOL_TachiAPIAuth]!.userID!)
)
const userDocs = await db.users.find(
{
id: { $in: scores.map((e) => e.userID) },
},
{
projection: {
id: 1,
username: 1,
},
}
);
const userMap = new Map();
for (const user of userDocs) {
userMap.set(user.id, user);
}
const beatorajaScores = [];
for (const score of scores) {
beatorajaScores.push(
TachiScoreDataToBeatorajaFormat(
score,
chart.data.hashSHA256,
userMap.get(score.userID).username,
chart.data.notecount,
0 // Playcount is always 0 at the moment due to performance concerns.
)
);
}
return res.status(200).json({
success: true,
description: `Successfully returned ${beatorajaScores.length}`,