Merge pull request #336 from TeamNewGuys:zkldi/issue-141-Import-charts-into-the-Database-for-BMS-if-it-isnt-already-present

Define OrphanQueue for TachiIR
This commit is contained in:
zkldi
2021-09-07 07:28:29 +01:00
committed by GitHub
18 changed files with 419 additions and 150 deletions
+1 -1
View File
@@ -73,7 +73,7 @@
"redis": "3.1.2",
"rimraf": "3.0.2",
"safe-json-stringify": "1.2.0",
"tachi-common": "0.2.3",
"tachi-common": "0.2.4",
"typescript": "4.3.4",
"winston": "3.3.3"
},
+4 -4
View File
@@ -54,7 +54,7 @@ specifiers:
rimraf: 3.0.2
safe-json-stringify: 1.2.0
supertest: 6.1.3
tachi-common: 0.2.3
tachi-common: 0.2.4
tap: 15.0.9
ts-node: 10.0.0
tsconfig-paths: 3.10.1
@@ -88,7 +88,7 @@ dependencies:
redis: 3.1.2
rimraf: 3.0.2
safe-json-stringify: 1.2.0
tachi-common: 0.2.3_ts-node@10.0.0+typescript@4.3.4
tachi-common: 0.2.4_ts-node@10.0.0+typescript@4.3.4
typescript: 4.3.4
winston: 3.3.3
@@ -3681,8 +3681,8 @@ packages:
strip-ansi: 6.0.0
dev: true
/tachi-common/0.2.3_ts-node@10.0.0+typescript@4.3.4:
resolution: {integrity: sha512-l1w5IQfaZejgl/+opB2K5mRkeQstreOq4+6+9A1kkGNVFu0lxvLYZdqSlJwbqYVAPOO7ssYZsi+FSUf81zowpg==}
/tachi-common/0.2.4_ts-node@10.0.0+typescript@4.3.4:
resolution: {integrity: sha512-5Q6c0qGhQyPKKiCg+BvuYbt9k5q/B1c3aBNR3KaX+aEKSQ8ZbuYTcKPKnQlDccjDfcGNj2OAmELx3oHz4UJdGA==}
dependencies:
monk: 7.3.4
tap: 15.0.9_ts-node@10.0.0+typescript@4.3.4
+5
View File
@@ -3,6 +3,7 @@ import db from "external/mongo/db";
(async () => {
const uscSongCount = await db.songs.usc.find({});
const bmsSongCount = await db.songs.bms.find({});
const Counters: CounterDocument[] = [
{
@@ -13,6 +14,10 @@ import db from "external/mongo/db";
counterName: "usc-song-id",
value: uscSongCount.length + 1,
},
{
counterName: "bms-song-id",
value: bmsSongCount.length + 1,
},
];
db.counters.insert(Counters).then(() => {
+3 -3
View File
@@ -34,7 +34,7 @@ import {
OAuth2ApplicationDocument,
integer,
FervidexSettingsDocument,
USCOrphanChart,
OrphanChart,
} from "tachi-common";
import monk, { TMiddleware } from "monk";
import CreateLogCtx from "lib/logger/logger";
@@ -178,7 +178,7 @@ const db = {
// i've inlined this one because i don't see it appearing anywhere else.
monkDB.get<{ code: string; userID: integer; createdOn: number }>("oauth2-auth-codes"),
"fer-settings": monkDB.get<FervidexSettingsDocument>("fer-settings"),
"usc-orphan-chart-queue": monkDB.get<USCOrphanChart>("usc-orphan-chart-queue"),
"orphan-chart-queue": monkDB.get<OrphanChart>("orphan-chart-queue"),
};
export type StaticDatabases =
@@ -210,7 +210,7 @@ export type StaticDatabases =
| "oauth2-clients"
| "oauth2-auth-codes"
| "fer-settings"
| "usc-orphan-chart-queue"
| "orphan-chart-queue"
| "user-settings";
export type Databases = StaticDatabases | `songs-${Game}` | `charts-${Game}`;
+104
View File
@@ -0,0 +1,104 @@
import db from "external/mongo/db";
import CreateLogCtx from "lib/logger/logger";
import {
ChartDocument,
IDStrings,
IDStringToGame,
integer,
OrphanChart,
SongDocument,
} from "tachi-common";
import { GetNextCounterValue } from "utils/db";
import { FilterQuery } from "mongodb";
const logger = CreateLogCtx(__filename);
/**
* Handles an orphan queue request.
*
* If the chart has never been seen before, add it to the orphan queue
* and return null.
*
* If the chart has been seen before, and has less than N unique players
* who have played it, return null.
*
* If the chart has been seen before, and has >= N unique players who have
* played it, unorphan the chart, and return it.
*/
export async function HandleOrphanQueue<I extends IDStrings>(
idString: I,
game: IDStringToGame[I],
chartDoc: ChartDocument<I>,
songDoc: SongDocument<IDStringToGame[I]>,
orphanMatchCriteria: FilterQuery<OrphanChart<I>>,
queueSize: integer,
userID: integer,
chartName: string
) {
logger.debug(`Recieved orphanqueue request for ${chartName}.`);
const orphanChart = await db["orphan-chart-queue"].findOne(
Object.assign({ idString }, orphanMatchCriteria),
{
projectID: true,
}
);
if (!orphanChart) {
logger.verbose(`Recieved unknown chart ${chartName}, orphaning.`);
await db["orphan-chart-queue"].insert({
idString,
chartDoc,
songDoc,
userIDs: [userID],
});
return null;
}
orphanChart.userIDs.push(userID);
const uniqueUsersArr = [...new Set(orphanChart.userIDs)];
const playcount = uniqueUsersArr.length;
// If N or more people have played this chart while orphaned, unorphan
// it.
if (playcount >= queueSize) {
logger.info(
`Song ${chartName} was unorphaned by ${uniqueUsersArr.join(", ")} and ${userID}.`
);
const songID = await GetNextCounterValue(`${game}-song-id`);
logger.verbose(`${chartName} has been assigned songID ${songID}.`);
const { songDoc, chartDoc } = orphanChart;
songDoc.id = songID;
chartDoc.songID = songID;
await db.songs[game].insert(songDoc);
await db.charts[game].insert(chartDoc);
await db["orphan-chart-queue"].remove({
_id: orphanChart._id,
});
return chartDoc as ChartDocument<I>;
}
// otherwise, update the state of this orphan.
logger.verbose(`UserID ${userID} played ${chartName}, which is now at ${playcount} plays.`);
await db["orphan-chart-queue"].update(
{
_id: orphanChart._id,
},
{
$set: {
userIDs: uniqueUsersArr,
},
}
);
return null;
}
@@ -8,8 +8,11 @@ import {
import { GenericGetGradeAndPercent } from "../../../framework/common/score-utils";
import { DryScore } from "../../../framework/common/types";
import { ConverterFunction } from "../../common/types";
import { BeatorajaContext, BeatorajaScore } from "./types";
import { ChartDocument } from "tachi-common";
import { BeatorajaContext, BeatorajaScore, BeatorajaChart } from "./types";
import { ChartDocument, SongDocument } from "tachi-common";
import { HandleOrphanQueue } from "lib/orphan-queue/orphan-queue";
import { Random20Hex } from "utils/misc";
import { ServerConfig, ServerTypeInfo } from "lib/setup/config";
const LAMP_LOOKUP = {
NoPlay: "NO PLAY",
@@ -41,19 +44,50 @@ export const ConverterIRBeatoraja: ConverterFunction<BeatorajaScore, BeatorajaCo
throw new InvalidScoreFailure("CN or HCN mode is not supported by this IR.");
}
const chart = (await FindChartOnSHA256("bms", data.sha256)) as ChartDocument<
let chart = (await FindChartOnSHA256("bms", data.sha256)) as ChartDocument<
"bms:7K" | "bms:14K"
> | null;
if (!chart) {
// @todo #141 Import charts into the Kamaitachi Database if the chart
// doesn't exist.
throw new KTDataNotFoundFailure(
`Could not find chart ${context.chart.title}.`,
importType,
data,
context
const chartName = `${context.chart.artist} (${context.chart.subartist})- ${context.chart.title} (${context.chart.subtitle})`;
if (context.chart.hasRandom) {
// If you're someone forking tachi looking to remove this
// check, remember to change the entire score import
// framework and database to be able to handle variable notecounts.
logger.verbose(`Declined to orphan chart ${chartName} as it has #RANDOM declarations.`);
throw new InvalidScoreFailure(
`${ServerTypeInfo.name} will not support #RANDOM charts.`
);
}
const idString = context.chart.mode === "BEAT_7K" ? "bms:7K" : "bms:14K";
const { chartDoc, songDoc } = ConvertBeatorajaChartToTachi(context.chart);
chart = await HandleOrphanQueue(
idString,
"bms",
chartDoc,
songDoc,
{
"chartDoc.data.hashSHA256": context.chart.sha256,
},
ServerConfig.BEATORAJA_QUEUE_SIZE,
context.userID,
chartName
);
// If chart wasn't unorphaned as a result of this request
// orphan this score and return ktdnf
if (!chart) {
throw new KTDataNotFoundFailure(
`This chart is orphaned.`,
"ir/beatoraja",
data,
context
);
}
}
const song = await FindSongOnID("bms", chart.songID);
@@ -127,3 +161,39 @@ export const ConverterIRBeatoraja: ConverterFunction<BeatorajaScore, BeatorajaCo
return { song, chart, dryScore };
};
function ConvertBeatorajaChartToTachi(chart: BeatorajaChart) {
const chartDoc: ChartDocument<"bms:14K" | "bms:7K"> = {
chartID: Random20Hex(),
difficulty: "CHART",
isPrimary: true,
level: "?",
levelNum: 0,
playtype: chart.mode === "BEAT_7K" ? "7K" : "14K",
rgcID: null,
songID: 0,
versions: [],
data: {
hashMD5: chart.md5,
hashSHA256: chart.sha256,
notecount: chart.notes,
tableFolders: [],
},
};
const songDoc: SongDocument<"bms"> = {
artist: chart.artist,
title: chart.title,
firstVersion: "",
id: 0,
"alt-titles": [],
"search-titles": [],
data: {
genre: chart.genre,
subartist: chart.subartist,
subtitle: chart.subtitle,
},
};
return { songDoc, chartDoc };
}
@@ -4,6 +4,7 @@ import ScoreImportFatalError from "../../../framework/score-importing/score-impo
import { FormatPrError } from "utils/prudence";
import { BeatorajaChart, BeatorajaContext, BeatorajaScore } from "./types";
import { ParserFunctionReturns } from "../../common/types";
import { integer } from "tachi-common";
const PR_BeatorajaScore = {
sha256: "string",
@@ -63,6 +64,7 @@ const PR_BeatorajaChart = {
export function ParseBeatorajaSingle(
body: Record<string, unknown>,
userID: integer,
logger: KtLogger
): ParserFunctionReturns<BeatorajaScore, BeatorajaContext> {
const err = p(
@@ -104,6 +106,7 @@ export function ParseBeatorajaSingle(
context: {
client,
chart: body.chart as BeatorajaChart,
userID,
},
game: "bms",
iterable: [body.score] as unknown as BeatorajaScore[],
@@ -3,6 +3,7 @@ import { integer } from "tachi-common";
export interface BeatorajaContext {
client: "lr2oraja";
chart: BeatorajaChart;
userID: integer; // unexpectedly necessary for orphan code!
}
export interface BeatorajaChart {
@@ -18,6 +19,7 @@ export interface BeatorajaChart {
mode: "BEAT_7K" | "BEAT_14K";
judge: number;
notes: integer;
hasRandom: boolean;
}
export interface BeatorajaScore {
+3
View File
@@ -73,6 +73,7 @@ export interface TachiConfig {
SENDMAIL_BIN?: string;
};
USC_QUEUE_SIZE: integer;
BEATORAJA_QUEUE_SIZE: integer;
}
const isValidOauth2 = p.optional({
@@ -109,6 +110,7 @@ const err = p(config, {
SENDMAIL_BIN: "*string",
}),
USC_QUEUE_SIZE: p.optional(p.gteInt(2)),
BEATORAJA_QUEUE_SIZE: p.optional(p.gteInt(2)),
});
if (err) {
@@ -129,6 +131,7 @@ const tachiConfig = config as TachiConfig;
tachiConfig.RATE_LIMIT ??= 500;
tachiConfig.OAUTH_CLIENT_CAP ??= 15;
tachiConfig.USC_QUEUE_SIZE ??= 3;
tachiConfig.BEATORAJA_QUEUE_SIZE ??= 3;
if (tachiConfig.EMAIL_CONFIG) {
tachiConfig.EMAIL_CONFIG.SENDMAIL_BIN ??= "/usr/bin/sendmail";
@@ -28,6 +28,7 @@ router.use(GetChartDocument);
/**
* Retrieves scores for the given chart.
*
* @name GET /ir/beatoraja/chart/:chartSHA256/scores
*/
router.get("/scores", async (req, res) => {
@@ -5,6 +5,7 @@ import mockApi from "test-utils/mock-api";
import ResetDBState from "test-utils/resets";
import { GetKTDataJSON } from "test-utils/test-data";
import deepmerge from "deepmerge";
import { PublicUserDocument } from "tachi-common";
t.test("POST /ir/beatoraja/submit-score", (t) => {
t.beforeEach(ResetDBState);
@@ -103,6 +104,132 @@ t.test("POST /ir/beatoraja/submit-score", (t) => {
t.end();
});
t.test("Should defer a chart to the orphan queue if not found.", async (t) => {
const res = await mockApi
.post("/ir/beatoraja/submit-score")
.set("X-TachiIR-Version", "2.0.0")
.set("Authorization", "Bearer mock_token")
.send(
deepmerge(scoreReq, {
chart: { sha256: "new_chart", md5: "new_md5" },
score: { sha256: "new_chart", md5: "new_md5" },
})
);
t.equal(res.status, 202);
t.equal(res.body.success, true);
t.match(res.body.description, /Chart and score have been orphaned/u);
const orphanChart = await db["orphan-chart-queue"].findOne({
"chartDoc.data.hashSHA256": "new_chart",
});
t.hasStrict(orphanChart?.chartDoc, {
data: {
hashSHA256: "new_chart",
hashMD5: "new_md5",
},
});
t.end();
});
t.test("Should eventually unorphan a chart.", async (t) => {
await db["api-tokens"].insert([
{
userID: 2,
identifier: "token2",
permissions: { submit_score: true },
token: "token2",
},
{
userID: 3,
identifier: "token3",
permissions: { submit_score: true },
token: "token3",
},
]);
await db.users.insert([
{
id: 2,
username: "foo",
usernameLowercase: "foo",
},
{
id: 3,
username: "bar",
usernameLowercase: "bar",
},
] as PublicUserDocument[]);
const res = await mockApi
.post("/ir/beatoraja/submit-score")
.set("X-TachiIR-Version", "2.0.0")
.set("Authorization", "Bearer mock_token")
.send(
deepmerge(scoreReq, {
chart: { sha256: "new_chart", md5: "new_md5" },
score: { sha256: "new_chart", md5: "new_md5" },
})
);
t.equal(res.statusCode, 202);
const res2 = await mockApi
.post("/ir/beatoraja/submit-score")
.set("X-TachiIR-Version", "2.0.0")
.set("Authorization", "Bearer token2")
.send(
deepmerge(scoreReq, {
chart: { sha256: "new_chart", md5: "new_md5" },
score: { sha256: "new_chart", md5: "new_md5" },
})
);
t.equal(res2.statusCode, 202);
const orphanData = await db["orphan-chart-queue"].findOne({
"chartDoc.data.hashSHA256": "new_chart",
});
t.strictSame(orphanData?.userIDs, [1, 2]);
const res3 = await mockApi
.post("/ir/beatoraja/submit-score")
.set("X-TachiIR-Version", "2.0.0")
.set("Authorization", "Bearer token3")
.send(
deepmerge(scoreReq, {
chart: { sha256: "new_chart", md5: "new_md5" },
score: { sha256: "new_chart", md5: "new_md5" },
})
);
t.equal(res3.statusCode, 200);
const orphanData2 = await db["orphan-chart-queue"].findOne({
"chartDoc.data.hashSHA256": "new_chart",
});
t.equal(orphanData2, null, "Orphan data should be removed from the database.");
const score = await db.scores.findOne({
game: "bms",
userID: 3,
});
t.hasStrict(score, {
scoreData: {
score: 1004,
},
importType: "ir/beatoraja",
});
t.end();
});
t.end();
});
@@ -4,6 +4,7 @@ import { SYMBOL_TachiAPIAuth } from "lib/constants/tachi";
import CreateLogCtx, { KtLogger } from "lib/logger/logger";
import { ExpressWrappedScoreImportMain } from "lib/score-import/framework/express-wrapper";
import { ParseBeatorajaSingle } from "lib/score-import/import-types/ir/beatoraja/parser";
import { ServerConfig } from "lib/setup/config";
import { UpdateClassIfGreater } from "utils/class";
import { GetUserWithIDGuaranteed } from "utils/user";
import { ValidateIRClientVersion } from "./auth";
@@ -16,14 +17,14 @@ const router: Router = Router({ mergeParams: true });
router.use(ValidateIRClientVersion);
/**
* Submits a beatoraja score to Kamaitachi. If the chart is unavailable,
* store it as a new chart alongside the new score.
* Submits a beatoraja score to Tachi.
*
* @name POST /ir/beatoraja/submit-score
*/
router.post("/submit-score", async (req, res) => {
const userDoc = await GetUserWithIDGuaranteed(req[SYMBOL_TachiAPIAuth]!.userID!);
const ParserFunction = (logger: KtLogger) => ParseBeatorajaSingle(req.body, logger);
const ParserFunction = (logger: KtLogger) => ParseBeatorajaSingle(req.body, userDoc.id, logger);
const importRes = await ExpressWrappedScoreImportMain(
userDoc,
@@ -35,6 +36,13 @@ router.post("/submit-score", async (req, res) => {
if (!importRes.body.success) {
return res.status(400).json(importRes.body);
} else if (importRes.body.body.errors.length !== 0) {
if (importRes.body.body.errors[0].type === "KTDataNotFound") {
return res.status(202).json({
success: true,
description: `Chart and score have been orphaned. This score will reify when atleast ${ServerConfig.BEATORAJA_QUEUE_SIZE} players have played the chart.`,
});
}
// since we're only ever importing one score, we can guarantee
// that this means the score we tried to import was skipped.
return res.status(400).json({
@@ -78,8 +86,9 @@ router.post("/submit-score", async (req, res) => {
});
/**
* Submits a course result to Kamaitachi. This only accepts a limited set of
* Submits a course result to Tachi. This only accepts a limited set of
* courses - all of which are dans.
*
* @name POST /ir/beatoraja/submit-course
*/
router.post("/submit-course", async (req, res) => {
@@ -1,118 +0,0 @@
import db from "external/mongo/db";
import CreateLogCtx from "lib/logger/logger";
import { ServerConfig } from "lib/setup/config";
import { ChartDocument, integer, SongDocument } from "tachi-common";
import { GetNextCounterValue } from "utils/db";
import { Random20Hex } from "utils/misc";
import { USCClientChart } from "./types";
const logger = CreateLogCtx(__filename);
/**
* Handles the orphan queue for USC charts. If the provided chart was just
* unorphaned, returns the chart. Else, returns null.
*/
export async function HandleOrphanQueue(uscChartDoc: USCClientChart, userID: integer) {
const chartName = `${uscChartDoc.artist} - ${uscChartDoc.title} (${IndexToDiff(
uscChartDoc.difficulty
)})`;
logger.debug(`Recieved orphanqueue request for ${chartName}.`);
const orphanChart = await db["usc-orphan-chart-queue"].findOne({
"chartDoc.data.hashSHA1": uscChartDoc.chartHash,
});
if (!orphanChart) {
logger.verbose(`Recieved unknown chart ${chartName}, orphaning.`);
const { song, chart } = ConvertUSCChart(uscChartDoc);
await db["usc-orphan-chart-queue"].insert({
chartDoc: chart,
songDoc: song,
userIDs: [userID],
});
return null;
}
orphanChart.userIDs.push(userID);
const uniqueUsersArr = [...new Set(orphanChart.userIDs)];
const playcount = uniqueUsersArr.length;
// If N or more people have played this chart while orphaned, unorphan
// it.
if (playcount >= ServerConfig.USC_QUEUE_SIZE) {
logger.info(
`Song ${chartName} was unorphaned by ${uniqueUsersArr.join(", ")} and ${userID}.`
);
const songID = await GetNextCounterValue("usc-song-id");
logger.verbose(`${chartName} has been assigned songID ${songID}.`);
const { songDoc, chartDoc } = orphanChart;
songDoc.id = songID;
chartDoc.songID = songID;
await db.songs.usc.insert(songDoc);
await db.charts.usc.insert(chartDoc);
await db["usc-orphan-chart-queue"].remove({
"chartDoc.data.hashSHA1": chartDoc.data.hashSHA1,
});
return chartDoc;
}
// otherwise, update the state of this orphan.
logger.verbose(`UserID ${userID} played ${chartName}, which is now at ${playcount} plays.`);
await db["usc-orphan-chart-queue"].update(
{
"chartDoc.data.hashSHA1": uscChartDoc.chartHash,
},
{
$set: {
userIDs: uniqueUsersArr,
},
}
);
return null;
}
function ConvertUSCChart(uscChartDoc: USCClientChart) {
const chart: ChartDocument<"usc:Single"> = {
chartID: Random20Hex(),
difficulty: IndexToDiff(uscChartDoc.difficulty),
isPrimary: true,
level: "?",
levelNum: 0,
playtype: "Single",
rgcID: null,
songID: 0,
versions: [],
data: {
hashSHA1: uscChartDoc.chartHash,
isOfficial: false,
},
};
const song: SongDocument<"usc"> = {
title: uscChartDoc.title,
artist: uscChartDoc.artist,
firstVersion: null,
id: 0,
data: {},
"alt-titles": [],
"search-titles": [],
};
return { chart, song };
}
function IndexToDiff(index: 0 | 1 | 2 | 3): ChartDocument<"usc:Single">["difficulty"] {
return (["NOV", "ADV", "EXH", "INF"] as const)[index];
}
@@ -551,7 +551,7 @@ t.test("POST /scores", async (t) => {
t.equal(dbScore, null, "Should not exist in db.");
const orphanData = await db["usc-orphan-chart-queue"].findOne({
const orphanData = await db["orphan-chart-queue"].findOne({
"chartDoc.data.hashSHA1": "NEW_CHART",
});
@@ -636,7 +636,7 @@ t.test("POST /scores", async (t) => {
t.equal(res2.body.statusCode, 22);
const orphanData = await db["usc-orphan-chart-queue"].findOne({
const orphanData = await db["orphan-chart-queue"].findOne({
"chartDoc.data.hashSHA1": "NEW_CHART",
});
@@ -655,7 +655,7 @@ t.test("POST /scores", async (t) => {
t.equal(res3.body.statusCode, 20);
const orphanData2 = await db["usc-orphan-chart-queue"].findOne({
const orphanData2 = await db["orphan-chart-queue"].findOne({
"chartDoc.data.hashSHA1": "NEW_CHART",
});
+26 -4
View File
@@ -1,6 +1,5 @@
import { Router, RequestHandler } from "express";
import p from "prudence";
import { FindChartOnSHA256 } from "utils/queries/charts";
import { SYMBOL_TachiAPIAuth, SYMBOL_TachiData } from "lib/constants/tachi";
import db from "external/mongo/db";
import {
@@ -11,7 +10,12 @@ import {
} from "tachi-common";
import { AssertStrAsPositiveNonZeroInt } from "lib/score-import/framework/common/string-asserts";
import CreateLogCtx, { KtLogger } from "lib/logger/logger";
import { CreatePOSTScoresResponseBody, TachiScoreToServerScore } from "./usc";
import {
ConvertUSCChart,
CreatePOSTScoresResponseBody,
TachiScoreToServerScore,
USCChartIndexToDiff,
} from "./usc";
import { ExpressWrappedScoreImportMain } from "lib/score-import/framework/express-wrapper";
import { GetUserWithID } from "utils/user";
import { ParseIRUSC } from "lib/score-import/import-types/ir/usc/parser";
@@ -24,7 +28,8 @@ import { RequirePermissions } from "server/middleware/auth";
import { GetUSCIRReplayURL } from "lib/cdn/url-format";
import { FormatPrError } from "utils/prudence";
import { USCClientChart } from "./types";
import { HandleOrphanQueue } from "./orphan-queue";
import { HandleOrphanQueue } from "lib/orphan-queue/orphan-queue";
import { ServerConfig } from "lib/setup/config";
const logger = CreateLogCtx(__filename);
@@ -280,7 +285,24 @@ router.post("/scores", RequirePermissions("submit_score"), async (req, res) => {
// If this chart is already orphaned, increase its unique player
// playcount.
if (!chartDoc) {
chartDoc = await HandleOrphanQueue(uscChart, req[SYMBOL_TachiAPIAuth].userID!);
const { song, chart } = ConvertUSCChart(uscChart);
const uscChartName = `${uscChart.artist} - ${uscChart.title} (${USCChartIndexToDiff(
uscChart.difficulty
)})`;
chartDoc = await HandleOrphanQueue(
"usc:Single",
"usc",
chart,
song,
{
"chartDoc.data.hashSHA1": uscChart.chartHash,
},
ServerConfig.USC_QUEUE_SIZE,
req[SYMBOL_TachiAPIAuth].userID!,
uscChartName
);
}
const userDoc = await GetUserWithID(req[SYMBOL_TachiAPIAuth]!.userID!);
+39 -3
View File
@@ -1,10 +1,10 @@
import { integer, PBScoreDocument, ScoreDocument, ChartDocument } from "tachi-common";
import { integer, PBScoreDocument, ScoreDocument, ChartDocument, SongDocument } from "tachi-common";
import CreateLogCtx from "lib/logger/logger";
import { GetPBOnChart, GetServerRecordOnChart } from "utils/scores";
import { MStoS } from "utils/misc";
import { MStoS, Random20Hex } from "utils/misc";
import { USCIR_ADJACENT_SCORE_N } from "lib/constants/usc-ir";
import db from "external/mongo/db";
import { USCServerScore } from "./types";
import { USCClientChart, USCServerScore } from "./types";
const logger = CreateLogCtx(__filename);
@@ -201,3 +201,39 @@ export interface POSTScoresResponseBody {
isServerRecord: boolean;
sendReplay: string;
}
export function ConvertUSCChart(uscChartDoc: USCClientChart) {
const chart: ChartDocument<"usc:Single"> = {
chartID: Random20Hex(),
difficulty: USCChartIndexToDiff(uscChartDoc.difficulty),
isPrimary: true,
level: "?",
levelNum: 0,
playtype: "Single",
rgcID: null,
songID: 0,
versions: [],
data: {
hashSHA1: uscChartDoc.chartHash,
isOfficial: false,
},
};
const song: SongDocument<"usc"> = {
title: uscChartDoc.title,
artist: uscChartDoc.artist,
firstVersion: null,
id: 0,
data: {},
"alt-titles": [],
"search-titles": [],
};
return { chart, song };
}
export function USCChartIndexToDiff(
index: 0 | 1 | 2 | 3
): ChartDocument<"usc:Single">["difficulty"] {
return (["NOV", "ADV", "EXH", "INF"] as const)[index];
}
@@ -10,5 +10,9 @@
{
"counterName": "usc-song-id",
"value": 2
},
{
"counterName": "bms-song-id",
"value": 2
}
]
@@ -0,0 +1 @@
[]