mirror of
https://github.com/zkldi/Tachi.git
synced 2026-09-30 19:07:58 +03:00
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:
@@ -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}`,
|
||||
|
||||
Reference in New Issue
Block a user