feat: start actionising on the west coast

This commit is contained in:
zk
2026-03-19 20:20:11 +00:00
parent bfe5fad78f
commit c80e2ea2ef
695 changed files with 6132 additions and 49249 deletions
+9
View File
@@ -0,0 +1,9 @@
NODE_ENV="dev"
REDIS_URL="tachi-redis"
MONGO_URL="tachi-mongo"
PORT=8080
LOG_LEVEL="debug"
MSGPACKR_NATIVE_ACCELERATION_DISABLED=true
POSTGRES_URL="postgresql://tachi:tachi@tachi-postgres/tachi_dev"
MIGRATIONS_DIR="/tachi/db/migrations"
-1
View File
@@ -1,5 +1,4 @@
# Test stuffs
.env
.setup_env
failed-tests.txt
-6
View File
@@ -8,9 +8,3 @@ Tachi-Server is the backend for Tachi.
- Opens a public API on `/api`.
- Supports various IR frameworks under `/ir`
- Powers a ridiculously overengineered score import framework.
## Setup
Make sure you've ran the bootstrap script in `../_scripts`. You can then start the server with
`pnpm start` (in this directory) or `pnpm start-server` at the top level.
+10
View File
@@ -0,0 +1,10 @@
version: v2
inputs:
- directory: src/proto/myt
plugins:
- local: protoc-gen-es
out: src/proto/generated
opt: target=ts
- local: protoc-gen-connect-es
out: src/proto/generated
opt: target=ts
-6
View File
@@ -1,6 +0,0 @@
NODE_ENV="dev"
REDIS_URL="redis"
MONGO_URL="mongo"
PORT=8080
LOG_LEVEL="debug"
MSGPACKR_NATIVE_ACCELERATION_DISABLED=true
-98
View File
@@ -1,98 +0,0 @@
// Config file that the CI deployment uses. This is moved to $pwd/conf.json5 before CI tests are ran.
{
MONGO_DATABASE_NAME: "localdb",
CAPTCHA_SECRET_KEY: "unused",
SESSION_SECRET: "unused",
FLO_API_URL: "https://flo.example.com",
EAG_API_URL: "https://eag.example.com",
MIN_API_URL: "https://min.example.com",
MYT_API_HOST: "myt.example.com",
FLO_OAUTH2_INFO: {
CLIENT_ID: "DUMMY_CLIENT_ID",
CLIENT_SECRET: "DUMMY_CLIENT_SECRET",
REDIRECT_URI: "https://example.com",
},
EAG_OAUTH2_INFO: {
CLIENT_ID: "DUMMY_CLIENT_ID",
CLIENT_SECRET: "DUMMY_CLIENT_SECRET",
REDIRECT_URI: "https://example.com",
},
ARC_AUTH_TOKEN: "unused",
MYT_AUTH_TOKEN: "unused",
OUR_URL: "https://127.0.0.1:8080",
CLIENT_DEV_SERVER: "http://127.0.0.1:3000",
ENABLE_SERVER_HTTPS: true,
OPTIONS_ALWAYS_SUCCEEDS: true,
CDN_CONFIG: {
WEB_LOCATION: "https://127.0.0.1:8080/cdn",
SAVE_LOCATION: {
TYPE: "LOCAL_FILESYSTEM",
LOCATION: "./local-cdn",
SERVE_OWN_CDN: true,
},
},
TACHI_CONFIG: {
TYPE: "omni",
NAME: "Tachi Local Dev",
GAMES: [
"iidx",
"museca",
"sdvx",
"bms",
"chunithm",
"usc",
"wacca",
"popn",
"jubeat",
"pms",
"gitadora",
"maimai",
"maimaidx",
"itg",
"ongeki",
"ddr"
],
IMPORT_TYPES: [
"file/eamusement-iidx-csv",
"file/batch-manual",
"file/solid-state-squad",
"file/pli-iidx-csv",
"file/mypagescraper-records-csv",
"file/mypagescraper-player-csv",
"ir/direct-manual",
"ir/barbatos",
"ir/fervidex",
"ir/fervidex-static",
"ir/beatoraja",
"ir/usc",
"ir/kshook-sv6c",
"ir/lr2hook",
"api/eag-iidx",
"api/eag-sdvx",
"api/flo-iidx",
"api/flo-sdvx",
"api/min-sdvx",
"api/cg-dev-sdvx",
"api/cg-dev-popn",
"api/cg-dev-museca",
"api/cg-dev-jubeat",
"api/cg-nag-sdvx",
"api/cg-nag-popn",
"api/cg-nag-jubeat",
"api/cg-nag-museca",
"api/cg-gan-sdvx",
"api/cg-gan-popn",
"api/cg-gan-museca",
"api/cg-gan-jubeat",
"api/myt-chunithm",
"api/myt-maimaidx",
"api/myt-ongeki",
"api/myt-wacca",
],
},
SEEDS_CONFIG: {
TYPE: "LOCAL_FILES",
PATH: "../seeds/collections",
},
}
-6
View File
@@ -1,6 +0,0 @@
{
"watch": ["src"],
"ext": "ts,json",
"ignore": ["src/**/*.spec.ts"],
"exec": "ts-node ./src/main.ts"
}
+7 -8
View File
@@ -6,9 +6,7 @@
"type": "module",
"scripts": {
"dev": "bun --watch src/main.ts",
"test": "tap",
"snap": "TAP_SNAPSHOT=1 tap",
"watchtest": "tap --watch",
"test": "vitest",
"build": "tsgo -b tsconfig.build.json -v",
"typecheck": "tsgo --noEmit",
"lint": "eslint ./src",
@@ -21,8 +19,8 @@
"@types/google-protobuf": "catalog:",
"@types/pg": "catalog:",
"@types/supertest": "catalog:",
"@types/tap": "catalog:",
"@typescript/native-preview": "catalog:",
"@vitest/coverage-v8": "catalog:",
"eslint-config-tachi": "workspace:*",
"glob": "catalog:",
"google-protobuf": "catalog:",
@@ -30,9 +28,9 @@
"node-mocks-http": "catalog:",
"nodemon": "catalog:",
"supertest": "catalog:",
"tap": "workspace:*",
"ts-protoc-gen": "catalog:",
"tsconfig-paths": "catalog:"
"tsconfig-paths": "catalog:",
"vitest": "catalog:"
},
"dependencies": {
"@aws-sdk/client-s3": "catalog:",
@@ -59,6 +57,7 @@
"@types/semver": "catalog:",
"@valuabletouch/winston-seq": "catalog:",
"bcryptjs": "catalog:",
"bliss": "workspace:*",
"bms-table-loader": "catalog:",
"bullmq": "catalog:",
"bunyan": "catalog:",
@@ -77,6 +76,7 @@
"html-entities": "catalog:",
"json5": "catalog:",
"kysely": "catalog:",
"lodash": "^4.17.23",
"mkdirp": "catalog:",
"mongodb": "catalog:",
"monk": "catalog:",
@@ -86,7 +86,6 @@
"node-html-parser": "catalog:",
"nodemailer": "catalog:",
"pg": "catalog:",
"bliss": "workspace:*",
"prudence": "catalog:",
"rate-limit-redis": "catalog:",
"redis": "catalog:",
@@ -115,7 +114,7 @@
"src/*/!(test-utils)/**/*.ts"
],
"exclude": [
"src/**/*.test.ts",
"src/**/*.oldtest.ts",
"src/external/mongo/schemas.ts"
]
},
@@ -0,0 +1,67 @@
import { MakeAction } from "#lib/actions/actions.js";
import DB from "#services/pg/db.js";
import { Random20Hex } from "#utils/misc.js";
import { IsUserAdmin } from "#utils/user.js";
import { ExpectedErr } from "bliss";
export const ACTION_InstallBuiltinClient = MakeAction(
"INSTALL_BUILTIN_CLIENT",
async (
taker,
{ clientID, name, permissions, apiKeyFilename, apiKeyTemplate, redirectUri, webhookUri },
) => {
// verify taker is admin
if (!(await IsUserAdmin(taker.acct.id))) {
throw new ExpectedErr(403, "You are not authorized to perform this action.");
}
// I honestly think this is unused for builtin clients
// as they don't need to auth with anyone!
const clientSecret = `CS${Random20Hex()}`;
// create client
await DB.insertInto("priv_api_client")
.values({
client_id: clientID,
client_secret: clientSecret,
name: name,
author: taker.acct.id,
pm_customise_profile: permissions.customise_profile,
pm_customise_score: permissions.customise_score,
pm_customise_session: permissions.customise_session,
pm_delete_score: permissions.delete_score,
pm_manage_rivals: permissions.manage_rivals,
pm_manage_targets: permissions.manage_targets,
pm_submit_score: permissions.submit_score,
pm_manage_challenges: permissions.manage_challenges,
api_key_filename: apiKeyFilename,
api_key_template: apiKeyTemplate,
webhook_uri: webhookUri,
redirect_uri: redirectUri,
is_builtin: true,
})
.onConflict((oc) =>
oc.doUpdateSet({
client_secret: clientSecret,
name: name,
author: taker.acct.id,
pm_customise_profile: permissions.customise_profile,
pm_customise_score: permissions.customise_score,
pm_customise_session: permissions.customise_session,
pm_delete_score: permissions.delete_score,
pm_manage_rivals: permissions.manage_rivals,
pm_manage_targets: permissions.manage_targets,
pm_submit_score: permissions.submit_score,
pm_manage_challenges: permissions.manage_challenges,
api_key_filename: apiKeyFilename,
api_key_template: apiKeyTemplate,
webhook_uri: webhookUri,
redirect_uri: redirectUri,
is_builtin: true,
}),
)
.execute();
return {};
},
);
@@ -2,7 +2,7 @@ import type { DeepPartial } from "#utils/types";
import { log } from "#lib/log/log.js";
import { CreatePBDoc } from "#lib/score-import/framework/pb/create-pb-doc";
import db from "#services/mongo/db";
import MONGODB_KILL from "#services/mongo/db";
import { dmf, mkMockPB, mkMockScore } from "#test-utils/misc";
import ResetDBState from "#test-utils/resets";
import { TestSnapshot } from "#test-utils/single-process-snapshot";
@@ -146,8 +146,8 @@ t.test("Arcaea Implementation", (t) => {
t.beforeEach(ResetDBState);
t.test("Should join best lamp", async (t) => {
await db.scores.insert(mockScore);
await db.scores.insert(
await MONGODB_KILL.scores.insert(mockScore);
await MONGODB_KILL.scores.insert(
dmf(mockScore, {
scoreID: "bestLamp",
scoreData: {
@@ -1,6 +1,6 @@
import { log } from "#lib/log/log.js";
import { CreatePBDoc } from "#lib/score-import/framework/pb/create-pb-doc";
import db from "#services/mongo/db";
import MONGODB_KILL from "#services/mongo/db";
import { dmf, mkMockPB, mkMockScore } from "#test-utils/misc";
import ResetDBState from "#test-utils/resets";
import { TestSnapshot } from "#test-utils/single-process-snapshot";
@@ -297,10 +297,10 @@ for (const [game, playtype, impl] of [
t.test("PB Mergers", (t) => {
t.beforeEach(ResetDBState);
t.beforeEach(() => db.scores.insert(mockScore));
t.beforeEach(() => MONGODB_KILL.scores.insert(mockScore));
t.test("Should join best lamp", async (t) => {
await db.scores.insert(
await MONGODB_KILL.scores.insert(
dmf(mockScore, {
scoreID: "bestLamp",
scoreData: {
@@ -326,7 +326,7 @@ for (const [game, playtype, impl] of [
});
t.test("Should join best BP", async (t) => {
await db.scores.insert([
await MONGODB_KILL.scores.insert([
dmf(mockScore, {
scoreID: "whateverBP",
scoreData: { optional: { bp: 100 } },
@@ -1,6 +1,6 @@
import { log } from "#lib/log/log.js";
import { CreatePBDoc } from "#lib/score-import/framework/pb/create-pb-doc";
import db from "#services/mongo/db";
import MONGODB_KILL from "#services/mongo/db";
import { dmf, mkMockPB, mkMockScore } from "#test-utils/misc";
import ResetDBState from "#test-utils/resets";
import { CHUNITHMBBKKChart, TestingChunithmScorePB } from "#test-utils/test-data";
@@ -83,7 +83,7 @@ t.test("CHUNITHM Implementation", (t) => {
const mockPBs = async (ratings: Array<number>) => {
await Promise.all(
ratings.map((rating, idx) =>
db["personal-bests"].insert({
MONGODB_KILL["personal-bests"].insert({
...TestingChunithmScorePB,
chartID: `TEST${idx}`,
calculatedData: {
@@ -192,8 +192,8 @@ t.test("CHUNITHM Implementation", (t) => {
t.beforeEach(ResetDBState);
t.test("Should join best lamp", async (t) => {
await db.scores.insert(mockScore);
await db.scores.insert(
await MONGODB_KILL.scores.insert(mockScore);
await MONGODB_KILL.scores.insert(
dmf(mockScore, {
scoreID: "bestNoteLamp",
scoreData: {
@@ -203,7 +203,7 @@ t.test("CHUNITHM Implementation", (t) => {
},
}),
);
await db.scores.insert(
await MONGODB_KILL.scores.insert(
dmf(mockScore, {
scoreID: "bestClearLamp",
scoreData: {
@@ -6,7 +6,7 @@ import type {
ScoreValidator,
} from "#game-implementations/types";
import db from "#services/mongo/db";
import MONGODB_KILL from "#services/mongo/db";
import { DDRFlare } from "rg-stats";
import {
type ChartDocument,
@@ -157,7 +157,7 @@ const DDR_CALCULATE_FLARE_SKILL: GPTNewProfileCalcs<"ddr:DP" | "ddr:SP"> = async
playtype,
userID,
) => {
const sc: Array<PBScoreDocumentWithSong> = await db["personal-bests"].aggregate([
const sc: Array<PBScoreDocumentWithSong> = await MONGODB_KILL["personal-bests"].aggregate([
{
$match: {
userID,
@@ -1,6 +1,6 @@
import { log } from "#lib/log/log.js";
import { CreatePBDoc } from "#lib/score-import/framework/pb/create-pb-doc";
import db from "#services/mongo/db";
import MONGODB_KILL from "#services/mongo/db";
import { dmf, mkMockPB, mkMockScore } from "#test-utils/misc";
import ResetDBState from "#test-utils/resets";
import { TestingGitadoraChart } from "#test-utils/test-data";
@@ -170,8 +170,8 @@ for (const [playtype, impl] of [
t.beforeEach(ResetDBState);
t.test("Should join best lamp", async (t) => {
await db.scores.insert(mockScore);
await db.scores.insert(
await MONGODB_KILL.scores.insert(mockScore);
await MONGODB_KILL.scores.insert(
dmf(mockScore, {
scoreID: "bestLamp",
scoreData: {
@@ -1,6 +1,6 @@
import { log } from "#lib/log/log.js";
import { CreatePBDoc } from "#lib/score-import/framework/pb/create-pb-doc";
import db from "#services/mongo/db";
import MONGODB_KILL from "#services/mongo/db";
import { dmf, mkFakePBIIDXSP, mkFakeScoreIIDXSP } from "#test-utils/misc";
import ResetDBState from "#test-utils/resets";
import { TestSnapshot } from "#test-utils/single-process-snapshot";
@@ -399,7 +399,7 @@ t.test("IIDX Implementation", (t) => {
for (const gptStr of ["iidx:SP", "iidx:DP"] as const) {
t.test("Should join best lamp", async (t) => {
await db.scores.insert(
await MONGODB_KILL.scores.insert(
mkFakeScoreIIDXSP({
scoreID: "bestLamp",
scoreData: {
@@ -428,7 +428,7 @@ t.test("IIDX Implementation", (t) => {
});
t.test("Should join best BP", async (t) => {
await db.scores.insert([
await MONGODB_KILL.scores.insert([
mkFakeScoreIIDXSP({
scoreID: "whateverBP",
scoreData: { optional: { bp: 100 } },
@@ -2,7 +2,7 @@ import type { ProvidedMetrics, ScoreData } from "tachi-common";
import { log } from "#lib/log/log.js";
import { CreatePBDoc } from "#lib/score-import/framework/pb/create-pb-doc";
import db from "#services/mongo/db";
import MONGODB_KILL from "#services/mongo/db";
import { dmf, mkMockPB, mkMockScore } from "#test-utils/misc";
import ResetDBState from "#test-utils/resets";
import { TestSnapshot } from "#test-utils/single-process-snapshot";
@@ -204,8 +204,8 @@ t.test("Jubeat Implementation", (t) => {
t.beforeEach(ResetDBState);
t.test("Should join best lamp", async (t) => {
await db.scores.insert(mockScore);
await db.scores.insert(
await MONGODB_KILL.scores.insert(mockScore);
await MONGODB_KILL.scores.insert(
dmf(mockScore, {
scoreID: "bestLamp",
scoreData: {
@@ -234,8 +234,8 @@ t.test("Jubeat Implementation", (t) => {
});
t.test("Should join best score", async (t) => {
await db.scores.insert(mockScore);
await db.scores.insert(
await MONGODB_KILL.scores.insert(mockScore);
await MONGODB_KILL.scores.insert(
dmf(mockScore, {
scoreID: "bestScore",
scoreData: {
@@ -3,7 +3,7 @@ import type { GPTServerImplementation } from "#game-implementations/types";
import { CreatePBMergeFor } from "#game-implementations/utils/pb-merge";
import { ProfileSumBestN } from "#game-implementations/utils/profile-calc";
import { SessionAvgBest10For } from "#game-implementations/utils/session-calc";
import db from "#services/mongo/db";
import MONGODB_KILL from "#services/mongo/db";
import { IsNullish } from "#utils/misc";
import { p } from "prudence";
import { Jubility } from "rg-stats";
@@ -27,7 +27,7 @@ async function GetBestJubilityOnSongs(
playtype: Playtype,
limit: integer,
): Promise<Array<PBScoreDocument>> {
const r: Array<{ doc: PBScoreDocument }> = await db["personal-bests"].aggregate([
const r: Array<{ doc: PBScoreDocument }> = await MONGODB_KILL["personal-bests"].aggregate([
{
$match: {
game,
@@ -107,14 +107,14 @@ async function GetBestJubilityOnSongs(
const CURRENT_JUBEAT_HOT_VERSION: Versions["jubeat:Single"] = "ave";
export async function GetPBsForJubility(userID: integer) {
const hotSongs = await db.songs.jubeat.find(
const hotSongs = await MONGODB_KILL.songs.jubeat.find(
{ "data.displayVersion": CURRENT_JUBEAT_HOT_VERSION },
{ projection: { id: 1 } },
);
const hotSongIDs = hotSongs.map((e) => e.id);
const coldSongs = await db.songs.jubeat.find(
const coldSongs = await MONGODB_KILL.songs.jubeat.find(
{ "data.displayVersion": { $ne: CURRENT_JUBEAT_HOT_VERSION } },
{ projection: { id: 1 } },
);
@@ -2,7 +2,7 @@ import type { integer, ProvidedMetrics, ScoreData } from "tachi-common";
import { log } from "#lib/log/log.js";
import { CreatePBDoc } from "#lib/score-import/framework/pb/create-pb-doc";
import db from "#services/mongo/db";
import MONGODB_KILL from "#services/mongo/db";
import { dmf, mkMockPB, mkMockScore } from "#test-utils/misc";
import ResetDBState from "#test-utils/resets";
import { TestSnapshot } from "#test-utils/single-process-snapshot";
@@ -167,8 +167,8 @@ t.test("maimai Implementation", (t) => {
t.beforeEach(ResetDBState);
t.test("Should join best lamp", async (t) => {
await db.scores.insert(mockScore);
await db.scores.insert(
await MONGODB_KILL.scores.insert(mockScore);
await MONGODB_KILL.scores.insert(
dmf(mockScore, {
scoreID: "bestLamp",
scoreData: {
@@ -2,7 +2,7 @@ import type { ProvidedMetrics, ScoreData } from "tachi-common";
import { log } from "#lib/log/log.js";
import { CreatePBDoc } from "#lib/score-import/framework/pb/create-pb-doc";
import db from "#services/mongo/db";
import MONGODB_KILL from "#services/mongo/db";
import { dmf, mkMockPB, mkMockScore } from "#test-utils/misc";
import ResetDBState from "#test-utils/resets";
import { TestingMaimaiDXChart } from "#test-utils/test-data";
@@ -134,8 +134,8 @@ t.test("Maimai DX Implementation", (t) => {
t.beforeEach(ResetDBState);
t.test("Should join best lamp", async (t) => {
await db.scores.insert(mockScore);
await db.scores.insert(
await MONGODB_KILL.scores.insert(mockScore);
await MONGODB_KILL.scores.insert(
dmf(mockScore, {
scoreID: "bestLamp",
scoreData: {
@@ -1,6 +1,6 @@
import { log } from "#lib/log/log.js";
import { CreatePBDoc } from "#lib/score-import/framework/pb/create-pb-doc";
import db from "#services/mongo/db";
import MONGODB_KILL from "#services/mongo/db";
import { dmf, mkMockPB, mkMockScore } from "#test-utils/misc";
import ResetDBState from "#test-utils/resets";
import { TestingMusecaChart } from "#test-utils/test-data";
@@ -100,8 +100,8 @@ t.test("MUSECA Implementation", (t) => {
t.beforeEach(ResetDBState);
t.test("Should join best lamp", async (t) => {
await db.scores.insert(mockScore);
await db.scores.insert(
await MONGODB_KILL.scores.insert(mockScore);
await MONGODB_KILL.scores.insert(
dmf(mockScore, {
scoreID: "bestLamp",
scoreData: {
@@ -2,7 +2,7 @@
import { log } from "#lib/log/log.js";
import { CreatePBDoc, UpdateChartRanking } from "#lib/score-import/framework/pb/create-pb-doc";
import db from "#services/mongo/db";
import MONGODB_KILL from "#services/mongo/db";
import { dmf, mkMockPB, mkMockScore } from "#test-utils/misc";
import ResetDBState from "#test-utils/resets";
import { TestingOngekiChart, TestingOngekiScorePB } from "#test-utils/test-data";
@@ -136,7 +136,7 @@ t.test("ONGEKI Implementation", (t: any) => {
? { rating: 0, scoreRating: rating, starRating: 0 }
: { rating: 0, scoreRating: 0, starRating: rating };
return db["personal-bests"].insert({
return MONGODB_KILL["personal-bests"].insert({
...TestingOngekiScorePB,
chartID: `TEST${field}${idx}`,
calculatedData: {
@@ -282,8 +282,8 @@ t.test("ONGEKI Implementation", (t: any) => {
t.beforeEach(ResetDBState);
t.test("Should join best lamp", async (t: any) => {
await db.scores.insert(mockScore);
await db.scores.insert(
await MONGODB_KILL.scores.insert(mockScore);
await MONGODB_KILL.scores.insert(
dmf(mockScore, {
scoreID: "bestLamp",
scoreData: {
@@ -314,8 +314,8 @@ t.test("ONGEKI Implementation", (t: any) => {
});
t.test("Should join platinum score", async (t: any) => {
await db.scores.insert(mockScore);
await db.scores.insert(
await MONGODB_KILL.scores.insert(mockScore);
await MONGODB_KILL.scores.insert(
dmf(mockScore, {
scoreID: "bestPlatinum",
scoreData: {
@@ -348,7 +348,7 @@ t.test("ONGEKI Implementation", (t: any) => {
t.beforeEach(ResetDBState);
t.test("Should tiebreak according to platinum score", async (t: any) => {
await db["personal-bests"].insert({
await MONGODB_KILL["personal-bests"].insert({
...TestingOngekiScorePB,
scoreData: {
...TestingOngekiScorePB.scoreData,
@@ -358,7 +358,7 @@ t.test("ONGEKI Implementation", (t: any) => {
userID: 1,
});
await db["personal-bests"].insert({
await MONGODB_KILL["personal-bests"].insert({
...TestingOngekiScorePB,
scoreData: {
...TestingOngekiScorePB.scoreData,
@@ -368,7 +368,7 @@ t.test("ONGEKI Implementation", (t: any) => {
userID: 2,
});
await db["personal-bests"].insert({
await MONGODB_KILL["personal-bests"].insert({
...TestingOngekiScorePB,
scoreData: {
...TestingOngekiScorePB.scoreData,
@@ -380,7 +380,7 @@ t.test("ONGEKI Implementation", (t: any) => {
await UpdateChartRanking("ongeki", "Single", TestingOngekiChart.chartID);
const pbs = (await db["personal-bests"].find({
const pbs = (await MONGODB_KILL["personal-bests"].find({
chartID: TestingOngekiChart.chartID,
})) as Array<PBScoreDocument<"ongeki:Single">>;
@@ -401,7 +401,7 @@ t.test("ONGEKI Implementation", (t: any) => {
});
t.test("Should not tiebreak if tech scores differ", async (t: any) => {
await db["personal-bests"].insert({
await MONGODB_KILL["personal-bests"].insert({
...TestingOngekiScorePB,
scoreData: {
...TestingOngekiScorePB.scoreData,
@@ -411,7 +411,7 @@ t.test("ONGEKI Implementation", (t: any) => {
userID: 1,
});
await db["personal-bests"].insert({
await MONGODB_KILL["personal-bests"].insert({
...TestingOngekiScorePB,
scoreData: {
...TestingOngekiScorePB.scoreData,
@@ -423,7 +423,7 @@ t.test("ONGEKI Implementation", (t: any) => {
await UpdateChartRanking("ongeki", "Single", TestingOngekiChart.chartID);
const [pb1, pb2] = (await db["personal-bests"].find(
const [pb1, pb2] = (await MONGODB_KILL["personal-bests"].find(
{
chartID: TestingOngekiChart.chartID,
},
@@ -2,7 +2,7 @@ import type { ProvidedMetrics, ScoreData } from "tachi-common";
import { log } from "#lib/log/log.js";
import { CreatePBDoc } from "#lib/score-import/framework/pb/create-pb-doc";
import db from "#services/mongo/db";
import MONGODB_KILL from "#services/mongo/db";
import { dmf, mkMockPB, mkMockScore } from "#test-utils/misc";
import ResetDBState from "#test-utils/resets";
import { TestingPopnChart } from "#test-utils/test-data";
@@ -159,8 +159,8 @@ t.test("Pop'n Implementation", (t) => {
t.beforeEach(ResetDBState);
t.test("Should join best clear medal", async (t) => {
await db.scores.insert(mockScore);
await db.scores.insert(
await MONGODB_KILL.scores.insert(mockScore);
await MONGODB_KILL.scores.insert(
dmf(mockScore, {
scoreID: "bestClearMedal",
scoreData: {
@@ -1,6 +1,6 @@
import { log } from "#lib/log/log.js";
import { CreatePBDoc } from "#lib/score-import/framework/pb/create-pb-doc";
import db from "#services/mongo/db";
import MONGODB_KILL from "#services/mongo/db";
import { dmf, mkMockPB, mkMockScore } from "#test-utils/misc";
import ResetDBState from "#test-utils/resets";
import { TestSnapshot } from "#test-utils/single-process-snapshot";
@@ -172,8 +172,8 @@ t.test("SDVX Implementation", (t) => {
t.beforeEach(ResetDBState);
t.test("Should join best lamp", async (t) => {
await db.scores.insert(mockScore);
await db.scores.insert(
await MONGODB_KILL.scores.insert(mockScore);
await MONGODB_KILL.scores.insert(
dmf(mockScore, {
scoreID: "bestLamp",
scoreData: {
@@ -197,8 +197,8 @@ t.test("SDVX Implementation", (t) => {
});
t.test("Should join best EX SCore", async (t) => {
await db.scores.insert(mockScore);
await db.scores.insert(
await MONGODB_KILL.scores.insert(mockScore);
await MONGODB_KILL.scores.insert(
dmf(mockScore, {
scoreID: "bestLamp",
scoreData: {
@@ -1,6 +1,6 @@
import { log } from "#lib/log/log.js";
import { CreatePBDoc } from "#lib/score-import/framework/pb/create-pb-doc";
import db from "#services/mongo/db";
import MONGODB_KILL from "#services/mongo/db";
import { dmf, mkMockPB, mkMockScore } from "#test-utils/misc";
import ResetDBState from "#test-utils/resets";
import { TestingUSCChart } from "#test-utils/test-data";
@@ -166,8 +166,8 @@ for (const [playtype, impl] of [
t.beforeEach(ResetDBState);
t.test("Should join best lamp", async (t) => {
await db.scores.insert(mockScore);
await db.scores.insert(
await MONGODB_KILL.scores.insert(mockScore);
await MONGODB_KILL.scores.insert(
dmf(mockScore, {
scoreID: "bestLamp",
scoreData: {
@@ -3,7 +3,7 @@ import type { ProvidedMetrics, ScoreData, ScoreDocument } from "tachi-common";
import { log } from "#lib/log/log.js";
import { CreatePBDoc } from "#lib/score-import/framework/pb/create-pb-doc";
import db from "#services/mongo/db";
import MONGODB_KILL from "#services/mongo/db";
import { dmf, mkMockPB, mkMockScore } from "#test-utils/misc";
import ResetDBState from "#test-utils/resets";
import { TestSnapshot } from "#test-utils/single-process-snapshot";
@@ -144,8 +144,8 @@ t.test("WACCA Implementation", (t) => {
t.beforeEach(ResetDBState);
t.test("Should join best lamp", async (t) => {
await db.scores.insert(mockScore);
await db.scores.insert(
await MONGODB_KILL.scores.insert(mockScore);
await MONGODB_KILL.scores.insert(
dmf(mockScore, {
scoreID: "bestLamp",
scoreData: {
@@ -10,7 +10,7 @@ import type {
} from "tachi-common";
import type { ExtractEnumMetricNames } from "tachi-common/types/metrics";
import db from "#services/mongo/db";
import MONGODB_KILL from "#services/mongo/db";
// insane typemagic to get mongodb-safe names for this GPT's metrics.
type MetricKeys<GPT extends GPTString> = Exclude<
@@ -55,7 +55,7 @@ export function CreatePBMergeFor<GPT extends GPTString>(
applicator: (base: PBScoreDocumentNoRank<GPT>, score: ScoreDocument<GPT>) => void,
): PBMergeFunction<GPT> {
return async (userID, chartID, asOfTimestamp, base) => {
const bestScoreFor = (await db.scores.findOne(
const bestScoreFor = (await MONGODB_KILL.scores.findOne(
HandleAsOf(
{
userID,
@@ -7,7 +7,7 @@ import type {
ScoreRatingAlgorithms,
} from "tachi-common";
import db from "#services/mongo/db";
import MONGODB_KILL from "#services/mongo/db";
/**
* Curries a function that returns the sum of N best ratings on `key`.
@@ -29,7 +29,7 @@ function CalcN<GPT extends GPTString>(
multiplier = 1,
) {
return async (game: GameGroup, playtype: Playtype, userID: integer) => {
const sc = await db["personal-bests"].find(
const sc = await MONGODB_KILL["personal-bests"].find(
{
game,
playtype,
@@ -100,7 +100,7 @@ export async function GetBestRatingOnSongs(
ratingProp: "skill",
limit: integer,
): Promise<Array<PBScoreDocument>> {
const r: Array<{ doc: PBScoreDocument }> = await db["personal-bests"].aggregate([
const r: Array<{ doc: PBScoreDocument }> = await MONGODB_KILL["personal-bests"].aggregate([
{
$match: {
game,
+27 -3
View File
@@ -1,6 +1,7 @@
import db from "#services/pg/db";
import DB from "#services/pg/db";
import { type ActionTaker, type AnonActionTaker, MakeActionGuts } from "bliss";
import { type ActionSignature } from "bliss/actions";
import { zodPermission } from "tachi-common";
import { z } from "zod";
const APP_NAME = "TACHI_SERVER";
@@ -13,6 +14,29 @@ export const ActionSignatures = {
input: z.object({}),
output: z.object({}),
},
INSTALL_BUILTIN_CLIENT: {
input: z.object({
clientID: z.string(),
name: z.string(),
webhookUri: z.url().nullable(),
redirectUri: z.url().nullable(),
permissions: z
.object({
customise_profile: z.boolean(),
customise_score: z.boolean(),
customise_session: z.boolean(),
delete_score: z.boolean(),
manage_rivals: z.boolean(),
manage_targets: z.boolean(),
submit_score: z.boolean(),
manage_challenges: z.boolean(),
})
.partial(),
apiKeyTemplate: z.string().nullable(),
apiKeyFilename: z.string().nullable(),
}),
output: z.object({}),
},
} satisfies Record<string, ActionSignature>;
export const AnonActionSignatures = {
@@ -30,7 +54,7 @@ export function MakeAnonAction<A extends AnonActionName>(
fn: AnonActionFn<A>,
): AnonActionFn<A> {
return MakeActionGuts({
db,
db: DB,
appName: APP_NAME,
kind,
// @ts-expect-error we're being creative with the types here
@@ -44,7 +68,7 @@ export function MakeAnonAction<A extends AnonActionName>(
*/
export function MakeAction<A extends ActionName>(kind: A, fn: ActionFn<A>): ActionFn<A> {
return MakeActionGuts({
db,
db: DB,
appName: APP_NAME,
kind,
// @ts-expect-error we're being creative with the types here
@@ -16,7 +16,7 @@ import type {
UserDocument,
} from "tachi-common";
import db from "#services/mongo/db";
import MONGODB_KILL from "#services/mongo/db";
import {
GetRecentlyAchievedGoals,
GetRecentlyAchievedQuests,
@@ -79,7 +79,7 @@ export async function GetRecentActivity(
timeStarted: { $lt: startFrom === null ? Infinity : startFrom },
};
const recentSessions = await db.sessions.find(initialSessionQuery, {
const recentSessions = await MONGODB_KILL.sessions.find(initialSessionQuery, {
sort: {
timeStarted: -1,
},
@@ -108,7 +108,7 @@ export async function GetRecentActivity(
{ goals, goalSubs },
{ quests, questSubs },
] = await Promise.all([
db["class-achievements"].find(
MONGODB_KILL["class-achievements"].find(
{
...baseQuery,
timeAchieved: timeConstraint,
@@ -119,7 +119,7 @@ export async function GetRecentActivity(
},
},
),
db.scores.find(
MONGODB_KILL.scores.find(
{
...baseQuery,
highlight: true,
@@ -1,13 +1,11 @@
import type { PrudenceError } from "prudence";
import { ACTION_InstallBuiltinClient } from "#actions/install-builtin-client.js";
import { log } from "#lib/log/log.js";
import { ServerConfig, TachiConfig } from "#lib/setup/config";
import { type TachiAPIClientDocument, UserAuthLevels } from "tachi-common";
import _ from "lodash";
import { type TachiAPIClientDocument } from "tachi-common";
/* eslint-disable no-await-in-loop */
import db from "#services/mongo/db";
import { DatabaseSchemas } from "#services/mongo/schemas";
import { Random20Hex } from "#utils/misc";
import { FormatPrError } from "#utils/prudence";
import { GetClientByID } from "#utils/queries/api-clients.js";
import { GetFirstAdmin } from "#utils/user.js";
import fjsh from "fast-json-stable-hash";
type DefaultClients = Array<Omit<TachiAPIClientDocument, "author" | "clientSecret">>;
@@ -307,56 +305,48 @@ export async function LoadDefaultClients() {
}
async function LoadClients(clients: DefaultClients) {
const firstAdmin = await db.users.findOne({
authLevel: UserAuthLevels.ADMIN,
});
if (!firstAdmin) {
log.warn(`There are no admins on this instance of tachi-server. We cannot create default API Clients!
Chances are, you're seeing this message because you just bootstrapped Tachi.
You'll need to set a user's authLevel to ${UserAuthLevels.ADMIN}.
If you have no users, go create an account using the frontend, then run pnpm make-user-admin 1.`);
return;
}
const firstAdmin = await GetFirstAdmin();
for (const client of clients) {
const exists = await db["api-clients"].findOne(
const exists = await GetClientByID(client.clientID);
if (exists) {
const existsOmitted = _.omit(exists, "author", "clientSecret");
// Skip if nothing has changed.
if (fjsh.hash(existsOmitted, "sha256") === fjsh.hash(client, "sha256")) {
continue;
}
}
await ACTION_InstallBuiltinClient(
{
clientID: client.clientID,
},
{
projection: {
clientSecret: 0,
author: 0,
ip: null,
acct: {
id: firstAdmin.id,
username: firstAdmin.username,
},
},
{
clientID: client.clientID,
name: client.name,
permissions: {
customise_profile: false,
customise_score: false,
customise_session: false,
delete_score: false,
manage_rivals: false,
manage_targets: false,
submit_score: false,
manage_challenges: false,
},
apiKeyFilename: client.apiKeyFilename,
apiKeyTemplate: client.apiKeyTemplate,
redirectUri: client.redirectUri,
webhookUri: client.webhookUri,
},
);
// Skip if nothing has changed.
if (fjsh.hash(exists, "sha256") === fjsh.hash(client, "sha256")) {
continue;
}
const realClient: TachiAPIClientDocument = {
...client,
clientSecret: `CS${Random20Hex()}`,
author: 1,
};
try {
DatabaseSchemas["api-clients"](realClient);
} catch (err) {
log.error(`Invalid API Client ${client.name}: ${FormatPrError(err as PrudenceError)}.`);
continue;
}
// No replaceOne support in monk -- have to do this.
await db["api-clients"].remove({
clientID: client.clientID,
});
await db["api-clients"].insert(realClient);
log.info(`Loaded/Modified new built-in client ${client.name}.`);
}
}
@@ -0,0 +1,64 @@
import { type Selection } from "kysely";
import { type APIPermissions, type TachiAPIClientDocument } from "tachi-common";
import { type Database } from "tachi-db";
export const SELECT_API_CLIENT = [
"priv_api_client.client_id",
"priv_api_client.client_secret",
"priv_api_client.name",
"priv_api_client.author",
"priv_api_client.pm_customise_profile",
"priv_api_client.pm_customise_score",
"priv_api_client.pm_customise_session",
"priv_api_client.pm_delete_score",
"priv_api_client.pm_manage_rivals",
"priv_api_client.pm_manage_targets",
"priv_api_client.pm_submit_score",
"priv_api_client.pm_manage_challenges",
"priv_api_client.api_key_filename",
"priv_api_client.api_key_template",
"priv_api_client.webhook_uri",
"priv_api_client.redirect_uri",
] as const;
export function ToAPIClientDocument(
row: Selection<Database, "priv_api_client", (typeof SELECT_API_CLIENT)[number]>,
): TachiAPIClientDocument {
const requestedPermissions: Array<APIPermissions> = [];
if (row.pm_customise_profile) {
requestedPermissions.push("customise_profile");
}
if (row.pm_customise_score) {
requestedPermissions.push("customise_score");
}
if (row.pm_customise_session) {
requestedPermissions.push("customise_session");
}
if (row.pm_delete_score) {
requestedPermissions.push("delete_score");
}
if (row.pm_manage_rivals) {
requestedPermissions.push("manage_rivals");
}
if (row.pm_manage_targets) {
requestedPermissions.push("manage_targets");
}
if (row.pm_submit_score) {
requestedPermissions.push("submit_score");
}
if (row.pm_manage_challenges) {
requestedPermissions.push("manage_challenges");
}
return {
clientID: row.client_id,
clientSecret: row.client_secret,
name: row.name,
author: row.author,
apiKeyFilename: row.api_key_filename,
apiKeyTemplate: row.api_key_template,
redirectUri: row.redirect_uri,
requestedPermissions,
webhookUri: row.webhook_uri,
};
}
@@ -0,0 +1,29 @@
import { type Selection } from "kysely";
import { type integer, type UserSettingsDocument } from "tachi-common";
import { type Database } from "tachi-db";
export const SELECT_USER_SETTINGS = [
"account_settings.user_id",
"account_settings.pf_invisible",
"account_settings.pf_developer_mode",
"account_settings.pf_advanced_mode",
"account_settings.pf_contentious_content",
"account_settings.pf_deletable_scores",
] as const;
export function ToUserSettingsDocument(
following: Array<integer>,
row: Selection<Database, "account_settings", (typeof SELECT_USER_SETTINGS)[number]>,
): UserSettingsDocument {
return {
userID: row.user_id,
following,
preferences: {
invisible: row.pf_invisible,
developerMode: row.pf_developer_mode,
advancedMode: row.pf_advanced_mode,
contentiousContent: row.pf_contentious_content,
deletableScores: row.pf_deletable_scores,
},
};
}
@@ -0,0 +1,67 @@
import { AuthLevelToInt } from "#utils/conversion.js";
import { ISO8601ToUnixMilliseconds } from "#utils/time.js";
import { type Selection } from "kysely";
import { type UserBadges, type UserDocument } from "tachi-common";
import { type Database } from "tachi-db";
export const SELECT_USER = [
"account.id",
"account.bd_alpha",
"account.bd_beta",
"account.bd_dev_team",
"account.about",
"account.auth_level",
"account.custom_banner_location",
"account.custom_pfp_location",
"account.joined",
"account.last_seen",
"account.sm_discord",
"account.sm_twitter",
"account.sm_github",
"account.sm_steam",
"account.sm_youtube",
"account.sm_twitch",
"account.status",
"account.username",
"account.normalized_username",
"account.is_supporter",
] as const;
export function ToUserDocument(
row: Selection<Database, "account", (typeof SELECT_USER)[number]>,
): UserDocument {
const badges: Array<UserBadges> = [];
if (row.bd_alpha) {
badges.push("alpha");
}
if (row.bd_beta) {
badges.push("beta");
}
if (row.bd_dev_team) {
badges.push("dev-team");
}
return {
about: row.about,
authLevel: AuthLevelToInt(row.auth_level),
badges,
customBannerLocation: row.custom_banner_location,
customPfpLocation: row.custom_pfp_location,
id: row.id,
joinDate: ISO8601ToUnixMilliseconds(row.joined),
lastSeen: ISO8601ToUnixMilliseconds(row.last_seen),
socialMedia: {
discord: row.sm_discord,
twitter: row.sm_twitter,
github: row.sm_github,
steam: row.sm_steam,
youtube: row.sm_youtube,
twitch: row.sm_twitch,
},
status: row.status,
username: row.username,
usernameLowercase: row.normalized_username,
isSupporter: row.is_supporter,
};
}
@@ -1,4 +1,4 @@
import db from "#services/mongo/db";
import MONGODB_KILL from "#services/mongo/db";
import ResetDBState from "#test-utils/resets";
import t from "tap";
@@ -10,7 +10,7 @@ t.test("#CreateGameSettings", (t) => {
t.test("Should create a new user's settings.", async (t) => {
await CreateGameSettings(1, "bms", "7K");
const data = await db["game-settings"].findOne({
const data = await MONGODB_KILL["game-settings"].findOne({
userID: 1,
game: "bms",
playtype: "7K",
@@ -1,13 +1,13 @@
import type { GameGroup, integer, Playtype } from "tachi-common";
import { log } from "#lib/log/log.js";
import db from "#services/mongo/db";
import MONGODB_KILL from "#services/mongo/db";
/**
* Create GameSettings for a UGPT (which contains their preferences).
*/
export async function CreateGameSettings(userID: integer, game: GameGroup, playtype: Playtype) {
const exists = await db["game-settings"].findOne({
const exists = await MONGODB_KILL["game-settings"].findOne({
userID,
game,
playtype,
@@ -32,7 +32,7 @@ export async function CreateGameSettings(userID: integer, game: GameGroup, playt
};
}
await db["game-settings"].insert({
await MONGODB_KILL["game-settings"].insert({
userID,
game,
playtype,
@@ -1,7 +1,7 @@
import type { ChartDocument, integer, Playtypes } from "tachi-common";
import { log } from "#lib/log/log.js";
import db from "#services/mongo/db";
import MONGODB_KILL from "#services/mongo/db";
import { PoyashiBPI } from "rg-stats";
interface PlaylistEntry {
@@ -110,7 +110,7 @@ export const CUSTOM_TACHI_IIDX_PLAYLISTS: Array<TachiIIDXPlaylist> = [
bounds.push([cutoffs[i], cutoffs[i + 1]] as [number, number]);
}
const charts = (await db.charts.iidx.find({
const charts = (await MONGODB_KILL.charts.iidx.find({
"data.kaidenAverage": { $ne: null },
playtype,
})) as Array<ChartDocument<"iidx:DP" | "iidx:SP">>;
@@ -1,4 +1,4 @@
import db from "#services/mongo/db";
import MONGODB_KILL from "#services/mongo/db";
import { dmf, mkFakeImport } from "#test-utils/misc";
import ResetDBState from "#test-utils/resets";
import { TestingIIDXSPScore } from "#test-utils/test-data";
@@ -9,7 +9,7 @@ import { RevertImport } from "./imports";
t.test("#RevertImport", (t) => {
t.beforeEach(ResetDBState);
t.beforeEach(() =>
db.scores.insert([
MONGODB_KILL.scores.insert([
dmf(TestingIIDXSPScore, { scoreID: "score_1" }),
dmf(TestingIIDXSPScore, { scoreID: "score_2" }),
dmf(TestingIIDXSPScore, { scoreID: "score_3" }),
@@ -21,16 +21,16 @@ t.test("#RevertImport", (t) => {
scoreIDs: ["score_1", "score_2"],
});
await db.imports.insert(importDoc);
await MONGODB_KILL.imports.insert(importDoc);
await RevertImport(importDoc);
const dbRes = await db.imports.findOne({ importID: "fake_import" });
const dbRes = await MONGODB_KILL.imports.findOne({ importID: "fake_import" });
t.equal(dbRes, null, "Should have removed the import from the DB.");
t.resolveMatch(
db.scores.findOne({ userID: 1, scoreID: "score_1" }),
MONGODB_KILL.scores.findOne({ userID: 1, scoreID: "score_1" }),
// @ts-expect-error https://github.com/DefinitelyTyped/DefinitelyTyped/pull/60020
null,
@@ -38,7 +38,7 @@ t.test("#RevertImport", (t) => {
);
t.resolveMatch(
db.scores.findOne({ userID: 1, scoreID: "score_2" }),
MONGODB_KILL.scores.findOne({ userID: 1, scoreID: "score_2" }),
// @ts-expect-error see above
null,
@@ -46,7 +46,7 @@ t.test("#RevertImport", (t) => {
);
t.resolveMatch(
db.scores.findOne({ userID: 1, scoreID: "score_3" }),
MONGODB_KILL.scores.findOne({ userID: 1, scoreID: "score_3" }),
// @ts-expect-error see above
{ scoreID: "score_3" },
+3 -3
View File
@@ -6,7 +6,7 @@ import {
UnsetOngoingImportLock,
} from "#lib/score-import/framework/import-locks/lock";
import { DeleteMultipleScores } from "#lib/score-mutation/delete-scores";
import db from "#services/mongo/db";
import MONGODB_KILL from "#services/mongo/db";
interface OngoingImportError {
tag: "ONGOING_IMPORT";
@@ -43,7 +43,7 @@ export async function RevertImport(importDoc: ImportDocument): Promise<OngoingIm
);
try {
await db.imports.remove({ importID: importDoc.importID });
await MONGODB_KILL.imports.remove({ importID: importDoc.importID });
log.info(`Reverted and deleted import '${importDoc.importID}'.`);
} catch (err) {
@@ -63,5 +63,5 @@ export async function RevertImport(importDoc: ImportDocument): Promise<OngoingIm
* Retrieve the scores inside this import.
*/
export function GetImportScores(importDoc: ImportDocument) {
return db.scores.find({ scoreID: { $in: importDoc.scoreIDs } });
return MONGODB_KILL.scores.find({ scoreID: { $in: importDoc.scoreIDs } });
}
@@ -1,7 +1,7 @@
import { log } from "#lib/log/log.js";
import { PullDatabaseSeeds } from "#lib/seeds/repo";
/* eslint-disable no-await-in-loop */
import db from "#services/mongo/db";
import MONGODB_KILL from "#services/mongo/db";
import { WrapScriptPromise } from "#utils/misc";
/**
@@ -15,7 +15,7 @@ export async function BacksyncBMSPMSSongsAndCharts() {
log.info(`Fetching ${game} songs from DB.`);
// did you know, this code is liable to blow up in my face and OOM one day?
let songs = await db.anySongs[game].find({});
let songs = await MONGODB_KILL.anySongs[game].find({});
log.info(`Found ${songs.length} ${game} songs.`);
@@ -27,7 +27,7 @@ export async function BacksyncBMSPMSSongsAndCharts() {
songs = null;
log.info(`Fetching ${game} charts from DB.`);
let charts = await db.anyCharts[game].find({});
let charts = await MONGODB_KILL.anyCharts[game].find({});
log.info(`Found ${charts.length} ${game} charts.`);
@@ -3,7 +3,7 @@ import type { FilterQuery } from "mongodb";
import { log } from "#lib/log/log.js";
import { DeorphanIfInQueue } from "#lib/orphan-queue/orphan-queue";
import db from "#services/mongo/db";
import MONGODB_KILL from "#services/mongo/db";
import { InitaliseFolderChartLookup } from "#utils/folder";
import { FormatBMSTables, WrapScriptPromise } from "#utils/misc";
import { type BMSTableEntry, LoadBMSTable } from "bms-table-loader";
@@ -34,7 +34,7 @@ async function HandleTableRemovals(
// *unless this script crashes, in which case it's
// no longer temporary
const existingCharts = (await db.charts.bms.find({
const existingCharts = (await MONGODB_KILL.charts.bms.find({
playtype,
"data.tableFolders.table": prefix,
})) as unknown as Array<ChartDocument<"bms:7K" | "bms:14K">>;
@@ -85,7 +85,7 @@ async function HandleTableRemovals(
// remove this table info from all of the charts that no longer
// exist in the table.
await db.charts.bms.update(
await MONGODB_KILL.charts.bms.update(
{
chartID: { $in: toRemove },
},
@@ -115,7 +115,7 @@ async function ImportTableLevels(
.filter((e) => e.checksum.type === "md5")
.map((e) => e.checksum.value);
await db.charts.bms.update(
await MONGODB_KILL.charts.bms.update(
{
"data.hashMD5": { $in: md5s },
},
@@ -127,7 +127,7 @@ async function ImportTableLevels(
{ multi: true },
);
await db.charts.bms.update(
await MONGODB_KILL.charts.bms.update(
{
"data.hashSHA256": { $in: sha256s },
},
@@ -154,7 +154,8 @@ async function ImportTableLevels(
}
}
let chart: ChartDocument<"bms:7K" | "bms:14K"> | null = await db.charts.bms.findOne(query);
let chart: ChartDocument<"bms:7K" | "bms:14K"> | null =
await MONGODB_KILL.charts.bms.findOne(query);
if (!chart) {
// didn't find it in the DB?
@@ -195,7 +196,7 @@ async function ImportTableLevels(
return a.level.localeCompare(b.level);
});
await db.charts.bms.update(
await MONGODB_KILL.charts.bms.update(
{
chartID: chart.chartID,
},
@@ -206,7 +207,7 @@ async function ImportTableLevels(
},
);
await db.songs.bms.update(
await MONGODB_KILL.songs.bms.update(
{
id: chart.songID,
},
+13 -7
View File
@@ -1,7 +1,7 @@
import type { UserGameStats, UserGameStatsSnapshotDocument } from "tachi-common";
import { log } from "#lib/log/log.js";
import db from "#services/mongo/db";
import MONGODB_KILL from "#services/mongo/db";
import { GetMillisecondsSince } from "#utils/misc";
import { GetAllRankings } from "#utils/user";
@@ -16,7 +16,9 @@ let batchWrite: Array<UserGameStatsSnapshotDocument> = [];
export async function UGSSnapshot() {
const timeStart = process.hrtime.bigint();
const alreadyExists = await db["game-stats-snapshots"].findOne({ timestamp: currentTime });
const alreadyExists = await MONGODB_KILL["game-stats-snapshots"].findOne({
timestamp: currentTime,
});
if (alreadyExists) {
log.warn(
@@ -31,7 +33,7 @@ export async function UGSSnapshot() {
log.info(`Snapshotting UserGameStats.`);
try {
await db["game-stats"]
await MONGODB_KILL["game-stats"]
.find({})
// @ts-expect-error faulty TS types
@@ -41,7 +43,11 @@ export async function UGSSnapshot() {
log.debug(`Snapshotting ${ugs.userID} ${ugs.playtype} ${ugs.game}.`);
const [playcount, rankings] = await Promise.all([
db.scores.count({ userID: ugs.userID, playtype: ugs.playtype, game: ugs.game }),
MONGODB_KILL.scores.count({
userID: ugs.userID,
playtype: ugs.playtype,
game: ugs.game,
}),
GetAllRankings(ugs),
]);
@@ -56,7 +62,7 @@ export async function UGSSnapshot() {
if (batchWrite.length >= 500) {
log.debug(`Flushed batch.`);
await db["game-stats-snapshots"].insert(batchWrite);
await MONGODB_KILL["game-stats-snapshots"].insert(batchWrite);
batchWrite = [];
}
@@ -65,7 +71,7 @@ export async function UGSSnapshot() {
});
if (batchWrite.length) {
await db["game-stats-snapshots"].insert(batchWrite);
await MONGODB_KILL["game-stats-snapshots"].insert(batchWrite);
}
log.info(
@@ -84,7 +90,7 @@ export async function UGSSnapshot() {
log.info(`Removing all snapshots at this timestamp (${currentTime}).`);
await db["game-stats-snapshots"].remove({ timestamp: currentTime });
await MONGODB_KILL["game-stats-snapshots"].remove({ timestamp: currentTime });
log.info(`Removed.`);
@@ -9,7 +9,7 @@ import type {
import { log } from "#lib/log/log.js";
import { BacksyncCollection, PullDatabaseSeeds } from "#lib/seeds/repo";
import db from "#services/mongo/db";
import MONGODB_KILL from "#services/mongo/db";
import { RecalcAllScores } from "#utils/calculations/recalc-scores";
import fetch from "#utils/fetch";
import { WrapScriptPromise } from "#utils/misc";
@@ -173,7 +173,7 @@ export async function UpdatePoyashiData() {
await repo.Destroy();
await BacksyncCollection("charts-iidx", db.charts.iidx, "Update BPI Data");
await BacksyncCollection("charts-iidx", MONGODB_KILL.charts.iidx, "Update BPI Data");
}
if (require.main === module) {
@@ -1,7 +1,7 @@
import { log } from "#lib/log/log.js";
import { BacksyncCollection } from "#lib/seeds/repo";
/* eslint-disable no-await-in-loop */
import db from "#services/mongo/db";
import MONGODB_KILL from "#services/mongo/db";
import { RecalcAllScores } from "#utils/calculations/recalc-scores";
import fetch from "#utils/fetch";
import { WrapScriptPromise } from "#utils/misc";
@@ -56,7 +56,7 @@ export async function UpdateDPTiers() {
continue;
}
const result = await db.charts.iidx.update(
const result = await MONGODB_KILL.charts.iidx.update(
{
versions: "29",
playtype: "DP",
@@ -92,7 +92,7 @@ export async function UpdateDPTiers() {
log.info(`Recalced those scores.`);
await BacksyncCollection("charts-iidx", db.charts.iidx, "Update DP Tierlist");
await BacksyncCollection("charts-iidx", MONGODB_KILL.charts.iidx, "Update DP Tierlist");
}
log.info("Done.");
@@ -4,7 +4,7 @@ import type { ChartDocument, Difficulties, integer } from "tachi-common";
import { log } from "#lib/log/log.js";
import { BacksyncCollection } from "#lib/seeds/repo";
import db from "#services/mongo/db";
import MONGODB_KILL from "#services/mongo/db";
import { RecalcAllScores } from "#utils/calculations/recalc-scores";
import { WrapScriptPromise } from "#utils/misc";
import { FindChartWithPTDF } from "#utils/queries/charts";
@@ -172,7 +172,7 @@ async function FetchSP12Data() {
updatedChartIDs.push(chart.chartID);
await db.charts.iidx.update(
await MONGODB_KILL.charts.iidx.update(
{
chartID: chart.chartID,
},
@@ -203,7 +203,7 @@ async function FetchSP12Data() {
log.info(`Finished recalcing scores.`);
await BacksyncCollection("charts-iidx", db.charts.iidx, "Update SP12 Tierlist");
await BacksyncCollection("charts-iidx", MONGODB_KILL.charts.iidx, "Update SP12 Tierlist");
}
process.exit(0);
@@ -1,4 +1,4 @@
import db from "#services/mongo/db";
import MONGODB_KILL from "#services/mongo/db";
import {
FormatGameGroup,
type GameGroup,
@@ -23,7 +23,7 @@ export async function SendSetRivalNotification(
game: GameGroup,
playtype: Playtype,
) {
const alreadyBeenPinged = await db.notifications.findOne({
const alreadyBeenPinged = await MONGODB_KILL.notifications.findOne({
sentTo: toUserID,
"body.type": "RIVALED_BY",
"body.content": {
@@ -60,13 +60,13 @@ export async function SendSiteAnnouncementNotification(
if (maybeGame && maybePlaytype) {
toUserIDs = (
await db["game-stats"].find(
await MONGODB_KILL["game-stats"].find(
{ game: maybeGame, playtype: maybePlaytype },
{ projection: { userID: 1 } },
)
).map((e) => e.userID);
} else {
toUserIDs = (await db.users.find({}, { projection: { id: 1 } })).map((e) => e.id);
toUserIDs = (await MONGODB_KILL.users.find({}, { projection: { id: 1 } })).map((e) => e.id);
}
return BulkSendNotification(title, toUserIDs, {
@@ -1,4 +1,4 @@
import db from "#services/mongo/db";
import MONGODB_KILL from "#services/mongo/db";
import ResetDBState from "#test-utils/resets";
import t from "tap";
@@ -13,7 +13,7 @@ t.test("#SendNotification", (t) => {
content: { questID: "foo", game: "iidx", playtype: "SP" },
});
const dbRes = await db.notifications.findOne({
const dbRes = await MONGODB_KILL.notifications.findOne({
sentTo: 1,
});
@@ -42,7 +42,7 @@ t.test("#BulkSendNotification", (t) => {
content: { questID: "foo", game: "iidx", playtype: "SP" },
});
const dbRes = await db.notifications.findOne({
const dbRes = await MONGODB_KILL.notifications.findOne({
sentTo: 1,
});
@@ -56,7 +56,7 @@ t.test("#BulkSendNotification", (t) => {
},
});
const dbRes2 = await db.notifications.findOne({
const dbRes2 = await MONGODB_KILL.notifications.findOne({
sentTo: 2,
});
@@ -1,6 +1,6 @@
import type { integer, NotificationBody, NotificationDocument } from "tachi-common";
import db from "#services/mongo/db";
import MONGODB_KILL from "#services/mongo/db";
import { Random20Hex } from "#utils/misc";
function ConstructNotificationDoc(
@@ -28,7 +28,7 @@ function ConstructNotificationDoc(
export function SendNotification(title: string, toUserID: integer, body: NotificationBody) {
const notification = ConstructNotificationDoc(title, toUserID, body);
return db.notifications.insert(notification);
return MONGODB_KILL.notifications.insert(notification);
}
/**
@@ -42,5 +42,5 @@ export function BulkSendNotification(
) {
const notifications = toUserIDs.map((e) => ConstructNotificationDoc(title, e, body));
return db.notifications.insert(notifications);
return MONGODB_KILL.notifications.insert(notifications);
}
@@ -9,7 +9,7 @@ import type {
} from "tachi-common";
import { log } from "#lib/log/log.js";
import db from "#services/mongo/db";
import MONGODB_KILL from "#services/mongo/db";
import { GetNextCounterValue } from "#utils/db";
import { DedupeArr } from "#utils/misc";
@@ -37,7 +37,7 @@ export async function HandleOrphanQueue<GPT extends GPTString>(
) {
log.debug(`Received orphanqueue request for ${chartName}.`);
const orphanChart = await db["orphan-chart-queue"].findOne(
const orphanChart = await MONGODB_KILL["orphan-chart-queue"].findOne(
{ gptString, ...orphanMatchCriteria },
{
projectID: true,
@@ -47,7 +47,7 @@ export async function HandleOrphanQueue<GPT extends GPTString>(
if (!orphanChart) {
log.debug(`Received unknown chart ${chartName}, orphaning.`);
await db["orphan-chart-queue"].insert({
await MONGODB_KILL["orphan-chart-queue"].insert({
gptString,
chartDoc,
songDoc,
@@ -77,10 +77,10 @@ export async function HandleOrphanQueue<GPT extends GPTString>(
songDoc.id = songID;
chartDoc.songID = songID;
await db.anySongs[game].insert(songDoc);
await db.anyCharts[game].insert(chartDoc);
await MONGODB_KILL.anySongs[game].insert(songDoc);
await MONGODB_KILL.anyCharts[game].insert(chartDoc);
await db["orphan-chart-queue"].remove({
await MONGODB_KILL["orphan-chart-queue"].remove({
_id: orphanChart._id,
});
@@ -90,7 +90,7 @@ export async function HandleOrphanQueue<GPT extends GPTString>(
// otherwise, update the state of this orphan.
log.debug(`UserID ${userID} played ${chartName}, which is now at ${playcount} plays.`);
await db["orphan-chart-queue"].update(
await MONGODB_KILL["orphan-chart-queue"].update(
{
_id: orphanChart._id,
},
@@ -115,7 +115,7 @@ export async function DeorphanIfInQueue<GPT extends GPTString>(
game: GPTStringToGame[GPT],
orphanMatchCriteria: FilterQuery<OrphanChartDocument<GPT>>,
): Promise<ChartDocument<GPT> | null> {
const orphanChart = await db["orphan-chart-queue"].findOne(
const orphanChart = await MONGODB_KILL["orphan-chart-queue"].findOne(
{ gptString, ...orphanMatchCriteria },
{
projectID: true,
@@ -136,9 +136,9 @@ export async function DeorphanIfInQueue<GPT extends GPTString>(
songDoc.id = songID;
chartDoc.songID = songID;
await db.anySongs[game].insert(songDoc);
await db.anyCharts[game].insert(chartDoc);
await db["orphan-chart-queue"].remove({
await MONGODB_KILL.anySongs[game].insert(songDoc);
await MONGODB_KILL.anyCharts[game].insert(chartDoc);
await MONGODB_KILL["orphan-chart-queue"].remove({
_id: orphanChart._id,
});
+10 -10
View File
@@ -4,7 +4,7 @@ import { SetRivalsFailReasons } from "#lib/constants/err-codes";
import { log } from "#lib/log/log.js";
import { SendSetRivalNotification } from "#lib/notifications/notification-wrappers";
import { ServerConfig } from "#lib/setup/config";
import db from "#services/mongo/db";
import MONGODB_KILL from "#services/mongo/db";
import { ArrayDiff } from "#utils/misc";
import { GetUsersWithIDs, GetUserWithIDGuaranteed } from "#utils/user";
import {
@@ -21,7 +21,7 @@ import {
* Throws if the user hasn't played the GPT in question.
*/
export async function GetRivalIDs(userID: integer, game: GameGroup, playtype: Playtype) {
const gameSettings = await db["game-settings"].findOne(
const gameSettings = await MONGODB_KILL["game-settings"].findOne(
{
userID,
game,
@@ -63,7 +63,7 @@ export async function GetEveryonesRivalIDs(
game: GameGroup,
playtype: Playtype,
): Promise<Record<number, Array<number>>> {
const allGameSettings = await db["game-settings"].find(
const allGameSettings = await MONGODB_KILL["game-settings"].find(
{
game,
playtype,
@@ -104,7 +104,7 @@ export async function SetRivals(
return SetRivalsFailReasons.RIVALED_SELF;
}
const playedGPTCount = await db["game-settings"].count({
const playedGPTCount = await MONGODB_KILL["game-settings"].count({
userID: { $in: newRivals },
game,
playtype,
@@ -115,7 +115,7 @@ export async function SetRivals(
return SetRivalsFailReasons.RIVALS_HAVENT_PLAYED_GPT;
}
const currentGameSettings = await db["game-settings"].findOne({
const currentGameSettings = await MONGODB_KILL["game-settings"].findOne({
userID,
game,
playtype,
@@ -145,7 +145,7 @@ export async function SetRivals(
newSubs.map((toUserID) => SendSetRivalNotification(toUserID, user, game, playtype)),
);
await db["game-settings"].update(
await MONGODB_KILL["game-settings"].update(
{
userID,
game,
@@ -206,7 +206,7 @@ export async function RemoveRival(
* Get all of the userIDs of people who rival the userID for this GPT.
*/
export async function GetChallengerIDs(userID: integer, game: GameGroup, playtype: Playtype) {
const result = await db["game-settings"].find(
const result = await MONGODB_KILL["game-settings"].find(
{
game,
playtype,
@@ -252,7 +252,7 @@ export async function UpdatePlayersRivalRankings(
const rivalIDs = await GetRivalIDs(userID, game, playtype);
// get all of this user's chartIDs so we know what to update
const userPBs = (await db["personal-bests"].find(
const userPBs = (await MONGODB_KILL["personal-bests"].find(
{ userID, game, playtype },
{ projection: { chartID: 1, [`scoreData.${gptConfig.defaultMetric}`]: 1 } },
)) as Array<{ chartID: string; scoreData: { percent: number } }>;
@@ -262,7 +262,7 @@ export async function UpdatePlayersRivalRankings(
await Promise.all(
userPBs.map(async (pb) => {
const rivalRank =
(await db["personal-bests"].count({
(await MONGODB_KILL["personal-bests"].count({
chartID: pb.chartID,
userID: { $in: rivalIDs },
[`scoreData.${gptConfig.defaultMetric}`]: { $gt: pb.scoreData.percent },
@@ -285,5 +285,5 @@ export async function UpdatePlayersRivalRankings(
return;
}
await db["personal-bests"].bulkWrite(bwrite, { ordered: false });
await MONGODB_KILL["personal-bests"].bulkWrite(bwrite, { ordered: false });
}
@@ -1,7 +1,7 @@
import type { GoalDocument, GoalSubscriptionDocument } from "tachi-common";
import { log } from "#lib/log/log.js";
import db from "#services/mongo/db";
import MONGODB_KILL from "#services/mongo/db";
import { dmf } from "#test-utils/misc.js";
import ResetDBState from "#test-utils/resets";
import {
@@ -51,10 +51,10 @@ t.test("#UpdateGoalsForUser", (t) => {
};
t.test("Should correctly update goals when user achieves goal.", async (t) => {
await db.goals.insert(baseGoalDocument);
await MONGODB_KILL.goals.insert(baseGoalDocument);
await db["goal-subs"].insert(baseGoalSubscriptionDocument);
await db["personal-bests"].insert(TestingIIDXSPScorePB);
await MONGODB_KILL["goal-subs"].insert(baseGoalSubscriptionDocument);
await MONGODB_KILL["personal-bests"].insert(TestingIIDXSPScorePB);
const ugMap = new Map([["FAKE_GOAL_ID", baseGoalSubscriptionDocument]]);
@@ -80,7 +80,7 @@ t.test("#UpdateGoalsForUser", (t) => {
},
]);
const r = await db["goal-subs"].findOne({ goalID: "FAKE_GOAL_ID", userID: 1 });
const r = await MONGODB_KILL["goal-subs"].findOne({ goalID: "FAKE_GOAL_ID", userID: 1 });
t.hasStrict(
r,
@@ -100,19 +100,21 @@ t.test("#UpdateGoalsForUser", (t) => {
t.test("Should correctly update goals when user does not achieve goal.", async (t) => {
const goal = dmf(baseGoalDocument, { criteria: { value: 2 } });
await db.goals.insert(goal);
await MONGODB_KILL.goals.insert(goal);
const goalSub = deepmerge(baseGoalSubscriptionDocument, {
outOf: 2,
outOfHuman: "2",
}) as unknown as GoalSubscriptionDocument;
await db["goal-subs"].insert(goalSub);
await MONGODB_KILL["goal-subs"].insert(goalSub);
// we dont delete _id here because updategoalsforuser
// depends on usergoal _id
await db["personal-bests"].insert(dmf(TestingIIDXSPScorePB, { scoreData: { score: 1 } }));
await MONGODB_KILL["personal-bests"].insert(
dmf(TestingIIDXSPScorePB, { scoreData: { score: 1 } }),
);
const ugMap = new Map([["FAKE_GOAL_ID", goalSub]]);
@@ -138,7 +140,7 @@ t.test("#UpdateGoalsForUser", (t) => {
},
]);
const r = await db["goal-subs"].findOne({ goalID: "FAKE_GOAL_ID", userID: 1 });
const r = await MONGODB_KILL["goal-subs"].findOne({ goalID: "FAKE_GOAL_ID", userID: 1 });
t.hasStrict(
r,
@@ -196,14 +198,14 @@ t.todo("#GetAndUpdateUsersGoals", (t) => {
t.test("#ProcessGoal", (t) => {
t.beforeEach(ResetDBState);
t.beforeEach(async () => {
await db.goals.insert(HC511Goal);
await MONGODB_KILL.goals.insert(HC511Goal);
});
t.test("Should process the users goal if a score has changed.", async (t) => {
await db["goal-subs"].insert(HC511UserGoal);
await MONGODB_KILL["goal-subs"].insert(HC511UserGoal);
// score is EX HARD CLEAR by default.
await db["personal-bests"].insert(TestingIIDXSPScorePB);
await MONGODB_KILL["personal-bests"].insert(TestingIIDXSPScorePB);
const res = await ProcessGoal(HC511Goal, HC511UserGoal, 1, log);
@@ -251,7 +253,7 @@ t.test("#ProcessGoal", (t) => {
userID: 1,
};
await db["goal-subs"].insert(achievedGoalSub);
await MONGODB_KILL["goal-subs"].insert(achievedGoalSub);
const res = await ProcessGoal(HC511Goal, achievedGoalSub, 1, log);
@@ -285,7 +287,7 @@ t.test("#ProcessGoal", (t) => {
});
t.test("Should return undefined if there's no score.", async (t) => {
await db["goal-subs"].insert(HC511UserGoal);
await MONGODB_KILL["goal-subs"].insert(HC511UserGoal);
const res = await ProcessGoal(HC511Goal, HC511UserGoal, 1, log);
@@ -295,17 +297,20 @@ t.test("#ProcessGoal", (t) => {
});
t.test("Should return undefined if the progress has not changed.", async (t) => {
await db["goal-subs"].insert(HC511UserGoal);
await db["personal-bests"].insert(TestingIIDXSPScorePB);
await MONGODB_KILL["goal-subs"].insert(HC511UserGoal);
await MONGODB_KILL["personal-bests"].insert(TestingIIDXSPScorePB);
const firstUpdate = await ProcessGoal(HC511Goal, HC511UserGoal, 1, log);
// ignore this one
t.not(firstUpdate, undefined, "Should NOT return undefined.");
await db["goal-subs"].bulkWrite([firstUpdate!.bwrite]);
await MONGODB_KILL["goal-subs"].bulkWrite([firstUpdate!.bwrite]);
const goalSub = await db["goal-subs"].findOne({ userID: 1, goalID: HC511Goal.goalID });
const goalSub = await MONGODB_KILL["goal-subs"].findOne({
userID: 1,
goalID: HC511Goal.goalID,
});
const secondUpdate = await ProcessGoal(HC511Goal, goalSub!, 1, log);
@@ -3,7 +3,7 @@ import type { GameGroup, GoalDocument, GoalSubscriptionDocument, integer } from
import { EvaluateGoalForUser, GetRelevantGoals } from "#lib/targets/goals";
import { EmitWebhookEvent } from "#lib/webhooks/webhooks";
import db from "#services/mongo/db";
import MONGODB_KILL from "#services/mongo/db";
/**
* Update a user's progress on all of their set goals.
@@ -90,7 +90,7 @@ export async function UpdateGoalsForUser(
});
}
await db["goal-subs"].bulkWrite(bulkWrite, { ordered: false });
await MONGODB_KILL["goal-subs"].bulkWrite(bulkWrite, { ordered: false });
return importInfo;
}
@@ -209,14 +209,14 @@ export async function ProcessGoal(
}
export async function UpdateGoalsInFolder(folderID: string, log: KtLogger) {
const goals = await db.goals.find({
const goals = await MONGODB_KILL.goals.find({
"charts.type": "folder",
"charts.data": folderID,
});
log.info(`Updating ${goals.length} goals for ${folderID}`);
const goalSubs = await db["goal-subs"].find({
const goalSubs = await MONGODB_KILL["goal-subs"].find({
goalID: { $in: goals.map((e) => e.goalID) },
});
@@ -2,7 +2,7 @@ import type { integer } from "tachi-common";
import { ONE_DAY, ONE_HOUR } from "#lib/constants/time";
import { log } from "#lib/log/log.js";
import db from "#services/mongo/db";
import MONGODB_KILL from "#services/mongo/db";
/**
* If a user has no ongoing import, enable the import lock and return true.
@@ -13,19 +13,19 @@ import db from "#services/mongo/db";
* has a lock.
*/
export async function CheckAndSetOngoingImportLock(userID: integer) {
const lockExists = await db["import-locks"].findOne({
const lockExists = await MONGODB_KILL["import-locks"].findOne({
userID,
});
if (!lockExists) {
await db["import-locks"].insert({
await MONGODB_KILL["import-locks"].insert({
userID,
locked: false,
lockedAt: null,
});
} else if (lockExists.locked && lockExists.lockedAt! + ONE_DAY < Date.now()) {
log.warn(`Removed import lock for ${userID} as it is ostensibly stuck.`);
await db["import-locks"].update(
await MONGODB_KILL["import-locks"].update(
{
userID,
},
@@ -38,7 +38,7 @@ export async function CheckAndSetOngoingImportLock(userID: integer) {
);
}
const lockWasSet = await db["import-locks"].findOneAndUpdate(
const lockWasSet = await MONGODB_KILL["import-locks"].findOneAndUpdate(
{
userID,
locked: false,
@@ -68,7 +68,7 @@ export async function CheckAndSetOngoingImportLock(userID: integer) {
* Disable a users import lock.
*/
export function UnsetOngoingImportLock(userID: integer) {
return db["import-locks"].findOneAndUpdate(
return MONGODB_KILL["import-locks"].findOneAndUpdate(
{
userID,
locked: true,
@@ -1,7 +1,7 @@
import type { BatchManualScore } from "tachi-common";
import { log } from "#lib/log/log.js";
import db from "#services/mongo/db";
import MONGODB_KILL from "#services/mongo/db";
import ResetDBState from "#test-utils/resets";
import deepmerge from "deepmerge";
import fjsh from "fast-json-stable-hash";
@@ -56,7 +56,7 @@ t.test("#OrphanScore", (t) => {
"Should be a checksum of the orphan's contents.",
);
const dbCheck = await db["orphan-scores"].findOne({
const dbCheck = await MONGODB_KILL["orphan-scores"].findOne({
orphanID: res.orphanID,
});
@@ -101,7 +101,7 @@ t.test("#OrphanScore", (t) => {
t.equal(res2.success, false);
t.equal(res1.orphanID, res2.orphanID);
const dbCheck = await db["orphan-scores"].count();
const dbCheck = await MONGODB_KILL["orphan-scores"].count();
t.equal(dbCheck, 1);
});
@@ -163,7 +163,9 @@ t.test("#ReprocessOrphan", (t) => {
"Should successfully import the score.",
);
const orphan = await db["orphan-scores"].findOne({ orphanID: orphanDoc.orphanID });
const orphan = await MONGODB_KILL["orphan-scores"].findOne({
orphanID: orphanDoc.orphanID,
});
t.equal(orphan, null, "Should remove the orphan document.");
@@ -171,7 +173,7 @@ t.test("#ReprocessOrphan", (t) => {
});
t.test("Should delete the orphan doc and return null if the score is invalid.", async (t) => {
await db["orphan-scores"].insert(orphanDoc);
await MONGODB_KILL["orphan-scores"].insert(orphanDoc);
const res = await ReprocessOrphan(
deepmerge(orphanDoc, {
data: {
@@ -184,7 +186,9 @@ t.test("#ReprocessOrphan", (t) => {
t.equal(res, null);
const orphan = await db["orphan-scores"].findOne({ orphanID: orphanDoc.orphanID });
const orphan = await MONGODB_KILL["orphan-scores"].findOne({
orphanID: orphanDoc.orphanID,
});
t.equal(orphan, null, "Should remove the orphan document.");
@@ -194,7 +198,7 @@ t.test("#ReprocessOrphan", (t) => {
t.test(
"Should keep the orphan doc and return false if no parents could be found.",
async (t) => {
await db["orphan-scores"].insert(orphanDoc);
await MONGODB_KILL["orphan-scores"].insert(orphanDoc);
const res = await ReprocessOrphan(
deepmerge(orphanDoc, {
data: {
@@ -207,7 +211,9 @@ t.test("#ReprocessOrphan", (t) => {
t.equal(res, false);
const orphan = await db["orphan-scores"].findOne({ orphanID: orphanDoc.orphanID });
const orphan = await MONGODB_KILL["orphan-scores"].findOne({
orphanID: orphanDoc.orphanID,
});
t.not(orphan, null, "Should not remove the orphan document.");
@@ -218,12 +224,12 @@ t.test("#ReprocessOrphan", (t) => {
t.test(
"Should keep the orphan doc and return false if an internal failure was triggered.",
async (t) => {
await db["orphan-scores"].insert(orphanDoc);
await MONGODB_KILL["orphan-scores"].insert(orphanDoc);
const GAZER_SHA256 = "195fe1be5c3e74fccd04dc426e05f8a9cfa8a1059c339d0a23e99f63661f0b7d";
// force an internal failure by removing all bms songs and then
// leaving a valid bms chart to match with.
await db.songs.bms.remove({});
await MONGODB_KILL.songs.bms.remove({});
const res = await ReprocessOrphan(
deepmerge(orphanDoc, {
@@ -242,7 +248,9 @@ t.test("#ReprocessOrphan", (t) => {
t.equal(res, false);
const orphan = await db["orphan-scores"].findOne({ orphanID: orphanDoc.orphanID });
const orphan = await MONGODB_KILL["orphan-scores"].findOne({
orphanID: orphanDoc.orphanID,
});
t.not(orphan, null, "Should not remove the orphan document.");
@@ -2,7 +2,7 @@ import type { KtLogger } from "#lib/log/log.js";
import type { FilterQuery } from "mongodb";
import type { GameGroup, ImportTypes, integer } from "tachi-common";
import db from "#services/mongo/db";
import MONGODB_KILL from "#services/mongo/db";
import { GetBlacklist } from "#utils/queries/blacklist";
import { GetUserWithID } from "#utils/user";
import fjsh from "fast-json-stable-hash";
@@ -53,7 +53,7 @@ export async function OrphanScore<T extends ImportTypes = ImportTypes>(
throw new Error(`Failed to orphan score. ${(err as Error).message}`);
}
const exists = await db["orphan-scores"].findOne({ orphanID });
const exists = await MONGODB_KILL["orphan-scores"].findOne({ orphanID });
if (exists) {
log.debug(`Skipped orphaning score ${orphanID} because it already exists.`);
@@ -70,7 +70,7 @@ export async function OrphanScore<T extends ImportTypes = ImportTypes>(
log.debug(orphanScoreDoc, `Inserting orphanScoreDoc...`);
await db["orphan-scores"].insert(orphanScoreDoc);
await MONGODB_KILL["orphan-scores"].insert(orphanScoreDoc);
return { success: true, orphanID };
}
@@ -136,7 +136,7 @@ export async function ReprocessOrphan(
// @danger - This could go terribly, if there's a mistake in the converterFN we might accidentally
// remove a users score.
await db["orphan-scores"].remove({ orphanID: orphan.orphanID });
await MONGODB_KILL["orphan-scores"].remove({ orphanID: orphan.orphanID });
return null;
}
@@ -155,7 +155,7 @@ export async function ReprocessOrphan(
);
} catch (err) {
if (IsConverterFailure(err) && err.failureType === "InvalidScore") {
await db["orphan-scores"].remove({ orphanID: orphan.orphanID });
await MONGODB_KILL["orphan-scores"].remove({ orphanID: orphan.orphanID });
return null;
}
@@ -165,7 +165,7 @@ export async function ReprocessOrphan(
}
if (converterReturns === null || !converterReturns.success) {
await db["orphan-scores"].remove({ orphanID: orphan.orphanID });
await MONGODB_KILL["orphan-scores"].remove({ orphanID: orphan.orphanID });
return null;
}
@@ -175,7 +175,7 @@ export async function ReprocessOrphan(
log.error(
`Orphan ${orphan.orphanID} belongs to ${orphan.userID}, but that user no longer exists in the database. Going to skip this and remove the orphan.`,
);
await db["orphan-scores"].remove({ orphanID: orphan.orphanID });
await MONGODB_KILL["orphan-scores"].remove({ orphanID: orphan.orphanID });
return null;
}
@@ -189,12 +189,12 @@ export async function ReprocessOrphan(
undefined,
);
await db["orphan-scores"].remove({ orphanID: orphan.orphanID });
await MONGODB_KILL["orphan-scores"].remove({ orphanID: orphan.orphanID });
return converterReturns;
}
export async function DeorphanScores(query: FilterQuery<OrphanScoreDocument>, log: KtLogger) {
const orphans = await db["orphan-scores"].find(query);
const orphans = await MONGODB_KILL["orphan-scores"].find(query);
// ScoreIDs are essentially userID dependent, so this is fine.
const blacklist = await GetBlacklist();
@@ -1,5 +1,5 @@
import { type KtLogger, log } from "#lib/log/log.js";
import db from "#services/mongo/db";
import MONGODB_KILL from "#services/mongo/db";
import ResetDBState from "#test-utils/resets";
import {
BMSGazerChart,
@@ -56,8 +56,8 @@ t.test("#CreatePBDoc", (t) => {
t.test(
"(IIDX) Should use the Server Impl Merge FNs to also join the BP PB if necessary.",
async (t) => {
await db.scores.remove({});
await db.scores.insert([
await MONGODB_KILL.scores.remove({});
await MONGODB_KILL.scores.insert([
IIDXScore,
deepmerge(IIDXScore, {
scoreData: {
@@ -130,8 +130,8 @@ t.test("#CreatePBDoc", (t) => {
scoreID: "LAMP_PB_ID",
});
await db.scores.remove({});
await db.scores.insert([IIDXScore, d]);
await MONGODB_KILL.scores.remove({});
await MONGODB_KILL.scores.insert([IIDXScore, d]);
const res = await CreatePBDoc("iidx:SP", 1, Testing511SPA, log);
@@ -151,7 +151,7 @@ t.test("#CreatePBDoc", (t) => {
let warnCalled = false;
await db.scores.remove({});
await MONGODB_KILL.scores.remove({});
const res = await CreatePBDoc("iidx:SP", 1, Testing511SPA, fakeLogger);
@@ -163,8 +163,8 @@ t.test("#CreatePBDoc", (t) => {
});
t.test("(BMS) Should inherit BP from the best BP score.", async (t) => {
await db.scores.remove({});
await db.scores.insert([
await MONGODB_KILL.scores.remove({});
await MONGODB_KILL.scores.insert([
TestingBMS7KScore,
deepmerge(TestingBMS7KScore, {
scoreData: {
@@ -215,8 +215,8 @@ t.test("#CreatePBDoc", (t) => {
});
t.test("(BMS) Should inherit graph data from the best lamp.", async (t) => {
await db.scores.remove({});
await db.scores.insert([
await MONGODB_KILL.scores.remove({});
await MONGODB_KILL.scores.insert([
TestingBMS7KScore,
deepmerge(TestingBMS7KScore, {
scoreData: {
@@ -3,7 +3,7 @@ import type { BulkWriteUpdateOneOperation, FilterQuery, SortOptionObject } from
import { GPT_SERVER_IMPLEMENTATIONS } from "#game-implementations/game-implementations";
import { GetEveryonesRivalIDs } from "#lib/rivals/rivals";
import db from "#services/mongo/db";
import MONGODB_KILL from "#services/mongo/db";
import { DeleteUndefinedProps } from "#utils/misc";
import {
type ChartDocument,
@@ -49,7 +49,7 @@ export async function CreatePBDoc(
const gptConfig = GetGPTConfig(gpt);
const defaultMetricPB = await db.scores.findOne(query, {
const defaultMetricPB = await MONGODB_KILL.scores.findOne(query, {
sort: {
[`scoreData.${gptConfig.defaultMetric}`]: -1,
},
@@ -177,7 +177,7 @@ export async function UpdateChartRanking(game: GameGroup, playtype: Playtype, ch
return;
}
await db["personal-bests"].bulkWrite(bwrite, { ordered: false });
await MONGODB_KILL["personal-bests"].bulkWrite(bwrite, { ordered: false });
}
async function GetSortedPBs(game: GameGroup, playtype: Playtype, chartID: string) {
@@ -199,7 +199,7 @@ async function GetSortedPBs(game: GameGroup, playtype: Playtype, chartID: string
};
}
return db["personal-bests"].aggregate([
return MONGODB_KILL["personal-bests"].aggregate([
{
$match: {
chartID,
@@ -1,5 +1,5 @@
import { log } from "#lib/log/log.js";
import db from "#services/mongo/db";
import MONGODB_KILL from "#services/mongo/db";
import ResetDBState from "#test-utils/resets";
import { Testing511SPA, TestingIIDXSPScore } from "#test-utils/test-data";
import crypto from "crypto";
@@ -12,12 +12,12 @@ t.test("#ProcessPBs", (t) => {
t.beforeEach(ResetDBState);
t.test("Should successfully insert a pb into the score-pb database", async (t) => {
await db["personal-bests"].remove({});
await MONGODB_KILL["personal-bests"].remove({});
// scores on 511 SPA are pre-loaded into the database
await ProcessPBs("iidx", "SP", 1, new Set([Testing511SPA.chartID]), log);
const pbs = await db["personal-bests"].find({});
const pbs = await MONGODB_KILL["personal-bests"].find({});
t.equal(pbs.length, 1, "Should match the amount of PBs inserted into the DB.");
@@ -25,9 +25,9 @@ t.test("#ProcessPBs", (t) => {
});
t.test("Should successfully insert multiple pbs into the score-pb database", async (t) => {
await db["personal-bests"].remove({});
await MONGODB_KILL["personal-bests"].remove({});
await db.charts.iidx.insert([
await MONGODB_KILL.charts.iidx.insert([
// @ts-expect-error lol
deepmerge(Testing511SPA, {
chartID: "test1",
@@ -45,7 +45,7 @@ t.test("#ProcessPBs", (t) => {
}),
]);
await db.scores.insert([
await MONGODB_KILL.scores.insert([
// @ts-expect-error lol
deepmerge(TestingIIDXSPScore, {
chartID: "test1",
@@ -73,7 +73,7 @@ t.test("#ProcessPBs", (t) => {
log,
);
const pbs = await db["personal-bests"].find({});
const pbs = await MONGODB_KILL["personal-bests"].find({});
t.equal(pbs.length, 4, "Should match the amount of PBs inserted into the DB.");
@@ -1,6 +1,6 @@
import type { KtLogger } from "#lib/log/log.js";
import db from "#services/mongo/db";
import MONGODB_KILL from "#services/mongo/db";
import { GetChartForIDGuaranteed } from "#utils/db";
import { type GameGroup, GetGPTString, type integer, type Playtype } from "tachi-common";
@@ -53,7 +53,7 @@ export async function ProcessPBs(
// this *is* bad behaviour, but I don't have a nice way to fix it.
// This should be fixed in the future to avoid crashes between these two
// calls - but that is unlikely.
await db["personal-bests"].bulkWrite(
await MONGODB_KILL["personal-bests"].bulkWrite(
pbDocs.map((e) => ({
updateOne: {
filter: { chartID: e.chartID, userID: e.userID },
@@ -1,7 +1,7 @@
import type { KtLogger } from "#lib/log/log.js";
import { EmitWebhookEvent } from "#lib/webhooks/webhooks";
import db from "#services/mongo/db";
import MONGODB_KILL from "#services/mongo/db";
import { ReturnClassIfGreater } from "#utils/class";
import deepmerge from "deepmerge";
import {
@@ -163,7 +163,7 @@ export async function ProcessClassDeltas(
}
}
await db["class-achievements"].insert(achievementOps);
await MONGODB_KILL["class-achievements"].insert(achievementOps);
return deltas;
}
@@ -12,7 +12,7 @@ import type {
import { EvaluateQuestProgress } from "#lib/targets/quests";
import { EmitWebhookEvent } from "#lib/webhooks/webhooks";
import db from "#services/mongo/db";
import MONGODB_KILL from "#services/mongo/db";
export async function UpdateUsersQuests(
importGoalInfo: Array<GoalImportInfo>,
@@ -114,7 +114,7 @@ export async function UpdateQuestsForUser(
);
if (bwrite.length !== 0) {
await db["quest-subs"].bulkWrite(bwrite, { ordered: false });
await MONGODB_KILL["quest-subs"].bulkWrite(bwrite, { ordered: false });
}
return importQuestInfo;
@@ -127,7 +127,7 @@ async function GetRelevantQuests(
userID: integer,
log: KtLogger,
) {
const questSubs = await db["quest-subs"].find({
const questSubs = await MONGODB_KILL["quest-subs"].find({
game,
playtype: { $in: playtypes },
userID,
@@ -135,7 +135,7 @@ async function GetRelevantQuests(
log.debug(`Found ${questSubs.length} quest-subs.`);
const quests = await db.quests.find({
const quests = await MONGODB_KILL.quests.find({
questID: { $in: questSubs.map((e) => e.questID) },
"questData.goals.goalID": { $in: goalIDs },
});
@@ -1,5 +1,5 @@
import { CDNRetrieve } from "#lib/cdn/cdn";
import db from "#services/mongo/db";
import MONGODB_KILL from "#services/mongo/db";
import ResetDBState from "#test-utils/resets";
import { FakeSmallBatchManual } from "#test-utils/test-data";
import t from "tap";
@@ -22,7 +22,7 @@ t.test("#MakeScoreImport", (t) => {
importID: "mockImportID",
});
const dbRes = await db.imports.findOne(
const dbRes = await MONGODB_KILL.imports.findOne(
{ importID: "mockImportID" },
{ projection: { _id: 0 } },
);
@@ -57,7 +57,9 @@ t.test("#MakeScoreImport", (t) => {
t.fail(err as any);
}
const importTrack = await db["import-trackers"].findOne({ importID: "mockImportID" });
const importTrack = await MONGODB_KILL["import-trackers"].findOne({
importID: "mockImportID",
});
t.equal(
importTrack,
@@ -1,6 +1,6 @@
import type { ScoreDocument } from "tachi-common";
import db from "#services/mongo/db";
import MONGODB_KILL from "#services/mongo/db";
import ResetDBState from "#test-utils/resets";
import t from "tap";
@@ -12,7 +12,7 @@ t.test("#QueueScoreInsert, #InsertQueue", async (t) => {
// empty scoreDB after
t.afterEach(async () => {
await db.scores.remove({});
await MONGODB_KILL.scores.remove({});
});
t.test("Single Queue Test", async (t) => {
@@ -33,7 +33,7 @@ t.test("#QueueScoreInsert, #InsertQueue", async (t) => {
t.equal(flushSize, 1, "QueueScoreInsert should append the score to the queue.");
const dbRes = await db.scores.find({
const dbRes = await MONGODB_KILL.scores.find({
scoreID: "foo",
});
@@ -75,7 +75,7 @@ t.test("#QueueScoreInsert, #InsertQueue", async (t) => {
t.equal(flushRes, 0, "The queue should now be empty.");
const dbRes = await db.scores.find({
const dbRes = await MONGODB_KILL.scores.find({
scoreID: { $exists: true },
chartID: "test",
});
@@ -112,7 +112,7 @@ t.test("#QueueScoreInsert, #InsertQueue", async (t) => {
t.equal(flushRes, 1, "Should flush 1 score document");
const dbRes = await db.scores.find({
const dbRes = await MONGODB_KILL.scores.find({
chartID: "foo",
});
@@ -1,7 +1,7 @@
import type { integer, ScoreDocument } from "tachi-common";
import { log } from "#lib/log/log.js";
import db from "#services/mongo/db";
import MONGODB_KILL from "#services/mongo/db";
const MAX_PIPELINE_LENGTH = 500;
@@ -64,7 +64,7 @@ export async function InsertQueue(userID: integer) {
delete ScoreQueues[userID];
try {
await db.scores.insert(queuedScores);
await MONGODB_KILL.scores.insert(queuedScores);
} catch (err) {
log.warn(
{ err },
@@ -1,6 +1,6 @@
import type { KtLogger } from "#lib/log/log.js";
import db from "#services/mongo/db";
import MONGODB_KILL from "#services/mongo/db";
import fjsh from "fast-json-stable-hash";
import {
GetGPTConfig,
@@ -58,7 +58,7 @@ export function CreateScoreID(
}
export function GetWithScoreID(scoreID: string) {
return db.scores.findOne({
return MONGODB_KILL.scores.findOne({
scoreID,
});
}
@@ -1,7 +1,7 @@
import type { KtLogger } from "#lib/log/log";
import type { ScoreImportJob } from "#lib/score-import/worker/types";
import db from "#services/mongo/db";
import MONGODB_KILL from "#services/mongo/db";
import { GetMillisecondsSince } from "#utils/misc";
import { GetUserWithID } from "#utils/user";
import {
@@ -133,7 +133,7 @@ export default async function ScoreImportMain<D, C>(
);
} catch (err) {
// Remove all scores from the database for this user which were imported after our timer started.
const r = await db.scores.remove({
const r = await MONGODB_KILL.scores.remove({
userID: user.id,
timeAdded: { $gte: startOfImportingScores },
});
@@ -222,11 +222,11 @@ export default async function ScoreImportMain<D, C>(
log.debug(logMessage);
}
await db.imports.insert(ImportDocument);
await MONGODB_KILL.imports.insert(ImportDocument);
// we don't await this because we don't
// particularly care about waiting for it.
void db["import-timings"].insert({
void MONGODB_KILL["import-timings"].insert({
importID,
timestamp: Date.now(),
total: ImportDocument.timeFinished - timeStarted,
@@ -1,7 +1,7 @@
import type { ScoreImportJob } from "#lib/score-import/worker/types";
import { AppendLogCtx, type KtLogger } from "#lib/log/log.js";
import db from "#services/mongo/db";
import MONGODB_KILL from "#services/mongo/db";
import { ClassToObject } from "#utils/misc";
import {
type ChartDocument,
@@ -52,7 +52,7 @@ export async function ImportAllIterableData<D, C>(
// @optimisable: could filter harder with score.game and score.playtype
// stuff.
const blacklist = (
await db["score-blacklist"].find({
await MONGODB_KILL["score-blacklist"].find({
userID,
})
).map((e) => e.scoreID);
@@ -345,7 +345,7 @@ async function HydrateCheckAndInsertScore(
return null;
}
const existingScore = await db.scores.findOne(
const existingScore = await MONGODB_KILL.scores.findOne(
{
scoreID,
},
@@ -377,7 +377,7 @@ async function HydrateCheckAndInsertScore(
let res;
if (force) {
res = await db.scores.insert(score);
res = await MONGODB_KILL.scores.insert(score);
} else {
res = await QueueScoreInsert(score);
}
@@ -1,6 +1,6 @@
import type { ScoreDocument, SessionDocument, UserDocument } from "tachi-common";
import db from "#services/mongo/db";
import MONGODB_KILL from "#services/mongo/db";
import ResetDBState from "#test-utils/resets";
import { TestingIIDXSPScore, TestingIIDXSPScorePB } from "#test-utils/test-data";
import deepmerge from "deepmerge";
@@ -17,7 +17,7 @@ const log = CreateScoreLogger(
t.test("#CreateSessions", (t) => {
t.beforeEach(ResetDBState);
t.beforeEach(() => db.sessions.remove({}));
t.beforeEach(() => MONGODB_KILL.sessions.remove({}));
t.test("Should compose sessions from one timestamped score provided.", async (t) => {
const res = await CreateSessions(1, "iidx", { SP: [TestingIIDXSPScore] }, log);
@@ -31,9 +31,17 @@ t.test("#CreateSessions", (t) => {
t.equal(res.length, 1);
const session = await db.sessions.findOne({ userID: 1, game: "iidx", playtype: "SP" });
const session = await MONGODB_KILL.sessions.findOne({
userID: 1,
game: "iidx",
playtype: "SP",
});
const sessionCount = await db.sessions.count({ userID: 1, game: "iidx", playtype: "SP" });
const sessionCount = await MONGODB_KILL.sessions.count({
userID: 1,
game: "iidx",
playtype: "SP",
});
t.equal(sessionCount, 1);
@@ -66,7 +74,11 @@ t.test("#CreateSessions", (t) => {
t.strictSame(res, []);
const sessionCount = await db.sessions.count({ userID: 1, game: "iidx", playtype: "SP" });
const sessionCount = await MONGODB_KILL.sessions.count({
userID: 1,
game: "iidx",
playtype: "SP",
});
t.equal(sessionCount, 0);
@@ -83,11 +95,19 @@ t.test("#CreateSessions", (t) => {
t.equal(res.length, 2);
const sessionCount = await db.sessions.count({ userID: 1, game: "iidx", playtype: "SP" });
const sessionCount = await MONGODB_KILL.sessions.count({
userID: 1,
game: "iidx",
playtype: "SP",
});
t.equal(sessionCount, 1);
const sessionCountDP = await db.sessions.count({ userID: 1, game: "iidx", playtype: "DP" });
const sessionCountDP = await MONGODB_KILL.sessions.count({
userID: 1,
game: "iidx",
playtype: "DP",
});
t.equal(sessionCountDP, 1);
@@ -101,7 +121,7 @@ const start = 1619454485988;
t.test("#LoadScoresIntoSessions", (t) => {
t.beforeEach(ResetDBState);
t.beforeEach(() => db.sessions.remove({}));
t.beforeEach(() => MONGODB_KILL.sessions.remove({}));
t.test("Should create sessions from scores.", async (t) => {
const res = await LoadScoresIntoSessions(
@@ -121,7 +141,11 @@ t.test("#LoadScoresIntoSessions", (t) => {
},
]);
const sessions = await db.sessions.find({ game: "iidx", playtype: "SP", userID: 1 });
const sessions = await MONGODB_KILL.sessions.find({
game: "iidx",
playtype: "SP",
userID: 1,
});
t.equal(sessions.length, 1);
@@ -154,7 +178,11 @@ t.test("#LoadScoresIntoSessions", (t) => {
},
]);
const sessions = await db.sessions.find({ game: "iidx", playtype: "SP", userID: 1 });
const sessions = await MONGODB_KILL.sessions.find({
game: "iidx",
playtype: "SP",
userID: 1,
});
t.equal(sessions.length, 1);
@@ -201,7 +229,11 @@ t.test("#LoadScoresIntoSessions", (t) => {
},
]);
const sessions = await db.sessions.find({ game: "iidx", playtype: "SP", userID: 1 });
const sessions = await MONGODB_KILL.sessions.find({
game: "iidx",
playtype: "SP",
userID: 1,
});
t.equal(sessions.length, 2);
@@ -212,7 +244,7 @@ t.test("#LoadScoresIntoSessions", (t) => {
});
t.test("Should append to existing sessions.", async (t) => {
await db.sessions.insert({
await MONGODB_KILL.sessions.insert({
sessionID: "EXAMPLE_SESSION_ID",
userID: 1,
game: "iidx",
@@ -239,7 +271,11 @@ t.test("#LoadScoresIntoSessions", (t) => {
},
]);
const sessions = await db.sessions.find({ game: "iidx", playtype: "SP", userID: 1 });
const sessions = await MONGODB_KILL.sessions.find({
game: "iidx",
playtype: "SP",
userID: 1,
});
t.equal(sessions.length, 1);
@@ -256,7 +292,7 @@ t.test("#LoadScoresIntoSessions", (t) => {
});
t.test("Should change the timeStarted of existing sessions if prepending.", async (t) => {
await db.sessions.insert({
await MONGODB_KILL.sessions.insert({
sessionID: "EXAMPLE_SESSION_ID",
userID: 1,
game: "iidx",
@@ -283,7 +319,11 @@ t.test("#LoadScoresIntoSessions", (t) => {
},
]);
const sessions = await db.sessions.find({ game: "iidx", playtype: "SP", userID: 1 });
const sessions = await MONGODB_KILL.sessions.find({
game: "iidx",
playtype: "SP",
userID: 1,
});
t.equal(sessions.length, 1);
@@ -300,7 +340,7 @@ t.test("#LoadScoresIntoSessions", (t) => {
});
t.test("Should calculate pbDifferences if a scorePB exists.", async (t) => {
await db["personal-bests"].insert(TestingIIDXSPScorePB);
await MONGODB_KILL["personal-bests"].insert(TestingIIDXSPScorePB);
const res = await LoadScoresIntoSessions(1, [TestingIIDXSPScore], "iidx", "SP", log);
@@ -311,7 +351,11 @@ t.test("#LoadScoresIntoSessions", (t) => {
},
]);
const sessions = await db.sessions.find({ game: "iidx", playtype: "SP", userID: 1 });
const sessions = await MONGODB_KILL.sessions.find({
game: "iidx",
playtype: "SP",
userID: 1,
});
t.equal(sessions.length, 1);
@@ -1,6 +1,6 @@
import { ONE_HOUR } from "#lib/constants/time";
import { AppendLogCtx, type KtLogger, log } from "#lib/log/log.js";
import db from "#services/mongo/db";
import MONGODB_KILL from "#services/mongo/db";
import { GetChartForIDGuaranteed } from "#utils/db";
import { GetScoresFromSession } from "#utils/session";
import crypto from "crypto";
@@ -99,7 +99,7 @@ function ScoreToSessionScoreInfo(
export async function GetSessionScoreInfo(
session: SessionDocument,
): Promise<Array<SessionScoreInfo>> {
const scores = await db.scores.find({
const scores = await MONGODB_KILL.scores.find({
scoreID: { $in: session.scoreIDs },
});
@@ -253,7 +253,7 @@ export async function LoadScoresIntoSessions(
// Find any sessions with +/-2hrs of this group. This is rather exhaustive, and could result in some issues
// if this query returns more than one session. We could account for that by smushing sessions together.
// This is not possible however, so this is now just a known tachi oddity.
const nearbySession = await db.sessions.findOne({
const nearbySession = await MONGODB_KILL.sessions.findOne({
userID,
game,
playtype,
@@ -276,7 +276,7 @@ export async function LoadScoresIntoSessions(
infoReturn = { sessionID: session.sessionID, type: "Appended" };
await db.sessions.update(
await MONGODB_KILL.sessions.update(
{
sessionID: session.sessionID,
},
@@ -292,7 +292,7 @@ export async function LoadScoresIntoSessions(
const session = CreateSession(userID, scoreIDs, groupScores, game, playtype);
infoReturn = { sessionID: session.sessionID, type: "Created" };
await db.sessions.insert(session);
await MONGODB_KILL.sessions.insert(session);
}
sessionInfoReturns.push(infoReturn);
@@ -4,7 +4,7 @@ import type { ImportTypes } from "tachi-common";
import { CDNStoreOrOverwrite } from "#lib/cdn/cdn";
import { GetScoreImportInputURL } from "#lib/cdn/url-format";
import { log } from "#lib/log/log.js";
import db from "#services/mongo/db";
import MONGODB_KILL from "#services/mongo/db";
import type ScoreImportFatalError from "../score-importing/score-import-error";
@@ -37,7 +37,7 @@ function SerialiseJobData(jobData: ScoreImportJobData<ImportTypes>): string {
* awaited when you await this function, it happens in the background.
*/
export async function StartTrackingImport(jobData: ScoreImportJobData<ImportTypes>) {
await db["import-trackers"].insert({
await MONGODB_KILL["import-trackers"].insert({
type: "ONGOING",
importID: jobData.importID,
importType: jobData.importType,
@@ -63,7 +63,7 @@ export async function StartTrackingImport(jobData: ScoreImportJobData<ImportType
}
export async function MarkImportAsFailed(importID: string, error: Error | ScoreImportFatalError) {
await db["import-trackers"].update(
await MONGODB_KILL["import-trackers"].update(
{
importID,
},
@@ -84,5 +84,5 @@ export async function MarkImportAsFailed(importID: string, error: Error | ScoreI
* in the tracker.
*/
export async function EndTrackingImport(importID: string) {
await db["import-trackers"].remove({ importID });
await MONGODB_KILL["import-trackers"].remove({ importID });
}
@@ -1,5 +1,5 @@
import { log } from "#lib/log/log.js";
import db from "#services/mongo/db";
import MONGODB_KILL from "#services/mongo/db";
import ResetDBState from "#test-utils/resets";
import { TestingIIDXSPScorePB } from "#test-utils/test-data";
import crypto from "crypto";
@@ -12,20 +12,20 @@ import { UpdateUsersGamePlaytypeStats } from "./update-ugpt-stats";
t.test("#UpdateUsersGamePlaytypeStats", (t) => {
t.beforeEach(ResetDBState);
t.beforeEach(async () => {
await db["personal-bests"].insert(TestingIIDXSPScorePB);
await MONGODB_KILL["personal-bests"].insert(TestingIIDXSPScorePB);
});
t.test(
"Should create new UserGameStats and UserGameSettings if the user has none",
async (t) => {
await db["game-stats"].remove({});
await db["game-settings"].remove({});
await MONGODB_KILL["game-stats"].remove({});
await MONGODB_KILL["game-settings"].remove({});
const res = await UpdateUsersGamePlaytypeStats("iidx", "SP", 1, null, log);
t.strictSame(res, [], "Should return an empty object");
const gs = await db["game-stats"].findOne();
const gs = await MONGODB_KILL["game-stats"].findOne();
t.hasStrict(
gs,
@@ -39,7 +39,7 @@ t.test("#UpdateUsersGamePlaytypeStats", (t) => {
"Should insert an appropriate game-stats object",
);
const settings = await db["game-settings"].findOne();
const settings = await MONGODB_KILL["game-settings"].findOne();
t.hasStrict(settings, {
game: "iidx",
@@ -53,9 +53,9 @@ t.test("#UpdateUsersGamePlaytypeStats", (t) => {
);
t.test("Should update UserGameStats if the user has one", async (t) => {
await db["game-stats"].remove({});
await MONGODB_KILL["game-stats"].remove({});
await db["game-stats"].insert({
await MONGODB_KILL["game-stats"].insert({
game: "iidx",
playtype: "SP",
userID: 1,
@@ -66,7 +66,7 @@ t.test("#UpdateUsersGamePlaytypeStats", (t) => {
// insert some mock scores
const ratings = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
await db["personal-bests"].insert(
await MONGODB_KILL["personal-bests"].insert(
ratings.map((e) =>
deepmerge(TestingIIDXSPScorePB, {
chartID: crypto.randomBytes(20).toString("hex"),
@@ -81,7 +81,7 @@ t.test("#UpdateUsersGamePlaytypeStats", (t) => {
t.strictSame(res, [], "Should return an empty object");
const gs = await db["game-stats"].findOne();
const gs = await MONGODB_KILL["game-stats"].findOne();
t.hasStrict(
gs,
@@ -99,9 +99,9 @@ t.test("#UpdateUsersGamePlaytypeStats", (t) => {
});
t.test("Should return class deltas", async (t) => {
await db["game-stats"].remove({});
await MONGODB_KILL["game-stats"].remove({});
await db["game-stats"].insert({
await MONGODB_KILL["game-stats"].insert({
game: "iidx",
playtype: "SP",
userID: 1,
@@ -132,7 +132,7 @@ t.test("#UpdateUsersGamePlaytypeStats", (t) => {
"Should return the class delta",
);
const gs = await db["game-stats"].findOne();
const gs = await MONGODB_KILL["game-stats"].findOne();
t.hasStrict(
gs,
@@ -152,9 +152,9 @@ t.test("#UpdateUsersGamePlaytypeStats", (t) => {
});
t.test("Should return updated class deltas", async (t) => {
await db["game-stats"].remove({});
await MONGODB_KILL["game-stats"].remove({});
await db["game-stats"].insert({
await MONGODB_KILL["game-stats"].insert({
game: "iidx",
playtype: "SP",
userID: 1,
@@ -186,7 +186,7 @@ t.test("#UpdateUsersGamePlaytypeStats", (t) => {
"Should return the updated class delta",
);
const gs = await db["game-stats"].findOne();
const gs = await MONGODB_KILL["game-stats"].findOne();
t.hasStrict(
gs,
@@ -2,7 +2,7 @@ import type { KtLogger } from "#lib/log/log.js";
import type { ClassDelta, GameGroup, integer, Playtype, UserGameStats } from "tachi-common";
import { CreateGameSettings } from "#lib/game-settings/create-game-settings";
import db from "#services/mongo/db";
import MONGODB_KILL from "#services/mongo/db";
import type { ClassProvider } from "../calculated-data/types";
@@ -22,7 +22,7 @@ export async function UpdateUsersGamePlaytypeStats(
// Attempt to find a users game stats if one already exists. If one doesn't exist,
// this is this players first import for this game!
const userGameStats = await db["game-stats"].findOne({
const userGameStats = await MONGODB_KILL["game-stats"].findOne({
game,
playtype,
userID,
@@ -49,7 +49,7 @@ export async function UpdateUsersGamePlaytypeStats(
updateClasses[`classes.${delta.set}`] = delta.new;
}
await db["game-stats"].update(
await MONGODB_KILL["game-stats"].update(
{
game,
playtype,
@@ -63,7 +63,7 @@ export async function UpdateUsersGamePlaytypeStats(
},
);
} else {
const hasAnyScores = await db.scores.findOne({
const hasAnyScores = await MONGODB_KILL.scores.findOne({
game,
playtype,
userID,
@@ -90,7 +90,7 @@ export async function UpdateUsersGamePlaytypeStats(
};
log.info(`Created new gamestats for ${game} (${playtype})`);
await db["game-stats"].insert(newStats);
await MONGODB_KILL["game-stats"].insert(newStats);
await CreateGameSettings(userID, game, playtype);
}
@@ -20,31 +20,31 @@ import type { ConverterFunction } from "../../common/types";
import type { MytChunithmScore } from "./types";
const DIFFICULTIES = {
[ChunithmLevel.CHUNITHM_LEVEL_UNSPECIFIED]: undefined,
[ChunithmLevel.CHUNITHM_LEVEL_BASIC]: "BASIC",
[ChunithmLevel.CHUNITHM_LEVEL_ADVANCED]: "ADVANCED",
[ChunithmLevel.CHUNITHM_LEVEL_EXPERT]: "EXPERT",
[ChunithmLevel.CHUNITHM_LEVEL_MASTER]: "MASTER",
[ChunithmLevel.CHUNITHM_LEVEL_ULTIMA]: "ULTIMA",
[ChunithmLevel.CHUNITHM_LEVEL_WORLDS_END]: "WORLD'S END",
[ChunithmLevel.UNSPECIFIED]: undefined,
[ChunithmLevel.BASIC]: "BASIC",
[ChunithmLevel.ADVANCED]: "ADVANCED",
[ChunithmLevel.EXPERT]: "EXPERT",
[ChunithmLevel.MASTER]: "MASTER",
[ChunithmLevel.ULTIMA]: "ULTIMA",
[ChunithmLevel.WORLDS_END]: "WORLD'S END",
};
const CLEAR_LAMPS = {
[ChunithmClearStatus.CHUNITHM_CLEAR_STATUS_UNSPECIFIED]: undefined,
[ChunithmClearStatus.CHUNITHM_CLEAR_STATUS_FAILED]: "FAILED",
[ChunithmClearStatus.CHUNITHM_CLEAR_STATUS_CLEAR]: "CLEAR",
[ChunithmClearStatus.CHUNITHM_CLEAR_STATUS_HARD]: "HARD",
[ChunithmClearStatus.CHUNITHM_CLEAR_STATUS_ABSOLUTE]: "BRAVE",
[ChunithmClearStatus.CHUNITHM_CLEAR_STATUS_ABSOLUTE_PLUS]: "ABSOLUTE",
[ChunithmClearStatus.CHUNITHM_CLEAR_STATUS_CATASTROPHY]: "CATASTROPHY",
[ChunithmClearStatus.UNSPECIFIED]: undefined,
[ChunithmClearStatus.FAILED]: "FAILED",
[ChunithmClearStatus.CLEAR]: "CLEAR",
[ChunithmClearStatus.HARD]: "HARD",
[ChunithmClearStatus.ABSOLUTE]: "BRAVE",
[ChunithmClearStatus.ABSOLUTE_PLUS]: "ABSOLUTE",
[ChunithmClearStatus.CATASTROPHY]: "CATASTROPHY",
} as const;
const NOTE_LAMPS = {
[ChunithmComboStatus.CHUNITHM_COMBO_STATUS_UNSPECIFIED]: undefined,
[ChunithmComboStatus.CHUNITHM_COMBO_STATUS_NONE]: "NONE",
[ChunithmComboStatus.CHUNITHM_COMBO_STATUS_FULL_COMBO]: "FULL COMBO",
[ChunithmComboStatus.CHUNITHM_COMBO_STATUS_ALL_JUSTICE]: "ALL JUSTICE",
[ChunithmComboStatus.CHUNITHM_COMBO_STATUS_ALL_JUSTICE_CRITICAL]: "ALL JUSTICE CRITICAL",
[ChunithmComboStatus.UNSPECIFIED]: undefined,
[ChunithmComboStatus.NONE]: "NONE",
[ChunithmComboStatus.FULL_COMBO]: "FULL COMBO",
[ChunithmComboStatus.ALL_JUSTICE]: "ALL JUSTICE",
[ChunithmComboStatus.ALL_JUSTICE_CRITICAL]: "ALL JUSTICE CRITICAL",
} as const;
const ConvertAPIMytChunithm: ConverterFunction<MytChunithmScore, EmptyObject> = async (
@@ -3,24 +3,38 @@ import type { EmptyObject } from "#utils/types";
import type { integer } from "tachi-common";
import ScoreImportFatalError from "#lib/score-import/framework/score-importing/score-import-error";
import { ChunithmUserClient } from "#proto/generated/chunithm/user_grpc_pb";
import { GetPlaylogRequest, type GetPlaylogStreamItem } from "#proto/generated/chunithm/user_pb";
import { credentials } from "@grpc/grpc-js";
import { ChunithmUser, GetPlaylogRequestSchema } from "#proto/generated/chunithm/user_pb";
import { create } from "@bufbuild/protobuf";
import { ConnectError, createClient } from "@connectrpc/connect";
import type { ParserFunctionReturns } from "../../common/types";
import type { MytChunithmScore } from "./types";
import {
FetchMytTitleAPIID,
GetMytHostname,
StreamRPCAsAsync,
} from "../../common/api-myt/traverse-api";
import { CreateMytTransport, FetchMytTitleAPIID } from "../../common/api-myt/traverse-api";
async function* getObjectsFromGrpcIterable(
iterable: AsyncIterable<GetPlaylogStreamItem>,
): AsyncIterable<MytChunithmScore> {
for await (const item of iterable) {
yield item.toObject();
async function* streamPlaylog(userID: integer, log: KtLogger): AsyncIterable<MytChunithmScore> {
const profileApiId = await FetchMytTitleAPIID(userID, "chunithm", log);
const client = createClient(ChunithmUser, CreateMytTransport());
const request = create(GetPlaylogRequestSchema, { profileApiId });
try {
for await (const item of client.getPlaylog(request)) {
yield item;
}
} catch (err) {
if (err instanceof ConnectError) {
log.error(
{ err, code: err.code },
`MYT gRPC error streaming Chunithm playlog for userID ${userID}`,
);
} else {
log.error(
{ err },
`Unexpected MYT error streaming Chunithm playlog for userID ${userID}`,
);
}
throw new ScoreImportFatalError(500, `Failed to get scores from MYT.`);
}
}
@@ -28,29 +42,8 @@ export default async function ParseMytChunithm(
userID: integer,
log: KtLogger,
): Promise<ParserFunctionReturns<MytChunithmScore, EmptyObject>> {
const profileApiId = await FetchMytTitleAPIID(userID, "chunithm", log);
const endpoint = GetMytHostname();
const client = new ChunithmUserClient(endpoint, credentials.createSsl());
const request = new GetPlaylogRequest();
request.setProfileApiId(profileApiId);
let iterable;
try {
const stream = StreamRPCAsAsync(client.getPlaylog.bind(client), request, log);
iterable = getObjectsFromGrpcIterable(stream);
} catch (err) {
log.error(
`Unexpected MYT error while streaming Chunithm playlog items for userID ${userID}: ${err}`,
);
throw new ScoreImportFatalError(500, `Failed to get scores from MYT.`);
}
return {
iterable,
iterable: streamPlaylog(userID, log),
context: {},
classProvider: null,
game: "chunithm",
@@ -1,3 +1,3 @@
import type { GetPlaylogStreamItem } from "#proto/generated/chunithm/user_pb";
export type MytChunithmScore = GetPlaylogStreamItem.AsObject;
export type MytChunithmScore = GetPlaylogStreamItem;
@@ -17,20 +17,20 @@ import type { ConverterFunction } from "../../common/types";
import type { MytMaimaiDxScore } from "./types";
const DIFFICULTIES = {
[MaimaiLevel.MAIMAI_LEVEL_UNSPECIFIED]: undefined,
[MaimaiLevel.MAIMAI_LEVEL_BASIC]: "Basic",
[MaimaiLevel.MAIMAI_LEVEL_ADVANCED]: "Advanced",
[MaimaiLevel.MAIMAI_LEVEL_EXPERT]: "Expert",
[MaimaiLevel.MAIMAI_LEVEL_MASTER]: "Master",
[MaimaiLevel.MAIMAI_LEVEL_REMASTER]: "Re:Master",
[MaimaiLevel.MAIMAI_LEVEL_UTAGE]: "Utage",
[MaimaiLevel.UNSPECIFIED]: undefined,
[MaimaiLevel.BASIC]: "Basic",
[MaimaiLevel.ADVANCED]: "Advanced",
[MaimaiLevel.EXPERT]: "Expert",
[MaimaiLevel.MASTER]: "Master",
[MaimaiLevel.REMASTER]: "Re:Master",
[MaimaiLevel.UTAGE]: "Utage",
};
function getLamp(
comboStatus: number,
isClear: boolean,
): ScoreData<"maimaidx:Single">["lamp"] | undefined {
if (comboStatus === MaimaiComboStatus.MAIMAI_COMBO_STATUS_UNSPECIFIED) {
if (comboStatus === MaimaiComboStatus.UNSPECIFIED) {
return undefined;
}
@@ -38,23 +38,23 @@ function getLamp(
return "FAILED";
}
if (comboStatus === MaimaiComboStatus.MAIMAI_COMBO_STATUS_NONE) {
if (comboStatus === MaimaiComboStatus.NONE) {
return "CLEAR";
}
if (comboStatus === MaimaiComboStatus.MAIMAI_COMBO_STATUS_FULL_COMBO) {
if (comboStatus === MaimaiComboStatus.FULL_COMBO) {
return "FULL COMBO";
}
if (comboStatus === MaimaiComboStatus.MAIMAI_COMBO_STATUS_FULL_COMBO_PLUS) {
if (comboStatus === MaimaiComboStatus.FULL_COMBO_PLUS) {
return "FULL COMBO+";
}
if (comboStatus === MaimaiComboStatus.MAIMAI_COMBO_STATUS_ALL_PERFECT) {
if (comboStatus === MaimaiComboStatus.ALL_PERFECT) {
return "ALL PERFECT";
}
if (comboStatus === MaimaiComboStatus.MAIMAI_COMBO_STATUS_ALL_PERFECT_PLUS) {
if (comboStatus === MaimaiComboStatus.ALL_PERFECT_PLUS) {
return "ALL PERFECT+";
}
@@ -3,24 +3,38 @@ import type { EmptyObject } from "#utils/types";
import type { integer } from "tachi-common";
import ScoreImportFatalError from "#lib/score-import/framework/score-importing/score-import-error";
import { MaimaiUserClient } from "#proto/generated/maimai/user_grpc_pb";
import { GetPlaylogRequest, type GetPlaylogStreamItem } from "#proto/generated/maimai/user_pb";
import { credentials } from "@grpc/grpc-js";
import { GetPlaylogRequestSchema, MaimaiUser } from "#proto/generated/maimai/user_pb";
import { create } from "@bufbuild/protobuf";
import { ConnectError, createClient } from "@connectrpc/connect";
import type { ParserFunctionReturns } from "../../common/types";
import type { MytMaimaiDxScore } from "./types";
import {
FetchMytTitleAPIID,
GetMytHostname,
StreamRPCAsAsync,
} from "../../common/api-myt/traverse-api";
import { CreateMytTransport, FetchMytTitleAPIID } from "../../common/api-myt/traverse-api";
async function* getObjectsFromGrpcIterable(
iterable: AsyncIterable<GetPlaylogStreamItem>,
): AsyncIterable<MytMaimaiDxScore> {
for await (const item of iterable) {
yield item.toObject();
async function* streamPlaylog(userID: integer, log: KtLogger): AsyncIterable<MytMaimaiDxScore> {
const profileApiId = await FetchMytTitleAPIID(userID, "maimaidx", log);
const client = createClient(MaimaiUser, CreateMytTransport());
const request = create(GetPlaylogRequestSchema, { profileApiId });
try {
for await (const item of client.getPlaylog(request)) {
yield item;
}
} catch (err) {
if (err instanceof ConnectError) {
log.error(
{ err, code: err.code },
`MYT gRPC error streaming maimai DX playlog for userID ${userID}`,
);
} else {
log.error(
{ err },
`Unexpected MYT error streaming maimai DX playlog for userID ${userID}`,
);
}
throw new ScoreImportFatalError(500, `Failed to get scores from MYT.`);
}
}
@@ -28,29 +42,8 @@ export default async function ParseMytMaimaiDx(
userID: integer,
log: KtLogger,
): Promise<ParserFunctionReturns<MytMaimaiDxScore, EmptyObject>> {
const profileApiId = await FetchMytTitleAPIID(userID, "maimaidx", log);
const endpoint = GetMytHostname();
const client = new MaimaiUserClient(endpoint, credentials.createSsl());
const request = new GetPlaylogRequest();
request.setProfileApiId(profileApiId);
let iterable;
try {
const stream = StreamRPCAsAsync(client.getPlaylog.bind(client), request, log);
iterable = getObjectsFromGrpcIterable(stream);
} catch (err) {
log.error(
`Unexpected MYT error while streaming maimai DX playlog items for userID ${userID}: ${err}`,
);
throw new ScoreImportFatalError(500, `Failed to get scores from MYT.`);
}
return {
iterable,
iterable: streamPlaylog(userID, log),
context: {},
classProvider: null,
game: "maimaidx",
@@ -1,3 +1,3 @@
import type { GetPlaylogStreamItem } from "#proto/generated/maimai/user_pb";
export type MytMaimaiDxScore = GetPlaylogStreamItem.AsObject;
export type MytMaimaiDxScore = GetPlaylogStreamItem;
@@ -20,12 +20,12 @@ import type { ConverterFunction } from "../../common/types";
import type { MytOngekiScore } from "./types";
const DIFFICULTIES = {
[OngekiLevel.ONGEKI_LEVEL_UNSPECIFIED]: undefined,
[OngekiLevel.ONGEKI_LEVEL_BASIC]: "BASIC",
[OngekiLevel.ONGEKI_LEVEL_ADVANCED]: "ADVANCED",
[OngekiLevel.ONGEKI_LEVEL_EXPERT]: "EXPERT",
[OngekiLevel.ONGEKI_LEVEL_MASTER]: "MASTER",
[OngekiLevel.ONGEKI_LEVEL_LUNATIC]: "LUNATIC",
[OngekiLevel.UNSPECIFIED]: undefined,
[OngekiLevel.BASIC]: "BASIC",
[OngekiLevel.ADVANCED]: "ADVANCED",
[OngekiLevel.EXPERT]: "EXPERT",
[OngekiLevel.MASTER]: "MASTER",
[OngekiLevel.LUNATIC]: "LUNATIC",
};
function getNoteLamp(
@@ -34,8 +34,8 @@ function getNoteLamp(
techScore: number,
): ScoreData<"ongeki:Single">["noteLamp"] | undefined {
if (
comboStatus === OngekiComboStatus.ONGEKI_COMBO_STATUS_UNSPECIFIED ||
clearStatus === OngekiClearStatus.ONGEKI_CLEAR_STATUS_UNSPECIFIED
comboStatus === OngekiComboStatus.UNSPECIFIED ||
clearStatus === OngekiClearStatus.UNSPECIFIED
) {
return undefined;
}
@@ -44,22 +44,22 @@ function getNoteLamp(
return "ALL BREAK+";
}
if (comboStatus === OngekiComboStatus.ONGEKI_COMBO_STATUS_ALL_BREAK) {
if (comboStatus === OngekiComboStatus.ALL_BREAK) {
return "ALL BREAK";
}
if (comboStatus === OngekiComboStatus.ONGEKI_COMBO_STATUS_FULL_COMBO) {
if (comboStatus === OngekiComboStatus.FULL_COMBO) {
return "FULL COMBO";
}
if (
clearStatus === OngekiClearStatus.ONGEKI_CLEAR_STATUS_OVER_DAMAGE ||
clearStatus === OngekiClearStatus.ONGEKI_CLEAR_STATUS_CLEARED
clearStatus === OngekiClearStatus.OVER_DAMAGE ||
clearStatus === OngekiClearStatus.CLEARED
) {
return "CLEAR";
}
if (clearStatus === OngekiClearStatus.ONGEKI_CLEAR_STATUS_FAILED) {
if (clearStatus === OngekiClearStatus.FAILED) {
return "LOSS";
}
@@ -3,24 +3,38 @@ import type { EmptyObject } from "#utils/types";
import type { integer } from "tachi-common";
import ScoreImportFatalError from "#lib/score-import/framework/score-importing/score-import-error";
import { OngekiUserClient } from "#proto/generated/ongeki/user_grpc_pb";
import { GetPlaylogRequest, type GetPlaylogStreamItem } from "#proto/generated/ongeki/user_pb";
import { credentials } from "@grpc/grpc-js";
import { GetPlaylogRequestSchema, OngekiUser } from "#proto/generated/ongeki/user_pb";
import { create } from "@bufbuild/protobuf";
import { ConnectError, createClient } from "@connectrpc/connect";
import type { ParserFunctionReturns } from "../../common/types";
import type { MytOngekiScore } from "./types";
import {
FetchMytTitleAPIID,
GetMytHostname,
StreamRPCAsAsync,
} from "../../common/api-myt/traverse-api";
import { CreateMytTransport, FetchMytTitleAPIID } from "../../common/api-myt/traverse-api";
async function* getObjectsFromGrpcIterable(
iterable: AsyncIterable<GetPlaylogStreamItem>,
): AsyncIterable<MytOngekiScore> {
for await (const item of iterable) {
yield item.toObject();
async function* streamPlaylog(userID: integer, log: KtLogger): AsyncIterable<MytOngekiScore> {
const profileApiId = await FetchMytTitleAPIID(userID, "ongeki", log);
const client = createClient(OngekiUser, CreateMytTransport());
const request = create(GetPlaylogRequestSchema, { profileApiId });
try {
for await (const item of client.getPlaylog(request)) {
yield item;
}
} catch (err) {
if (err instanceof ConnectError) {
log.error(
{ err, code: err.code },
`MYT gRPC error streaming Ongeki playlog for userID ${userID}`,
);
} else {
log.error(
{ err },
`Unexpected MYT error streaming Ongeki playlog for userID ${userID}`,
);
}
throw new ScoreImportFatalError(500, `Failed to get scores from MYT.`);
}
}
@@ -28,29 +42,8 @@ export default async function ParseMytOngeki(
userID: integer,
log: KtLogger,
): Promise<ParserFunctionReturns<MytOngekiScore, EmptyObject>> {
const profileApiId = await FetchMytTitleAPIID(userID, "ongeki", log);
const endpoint = GetMytHostname();
const client = new OngekiUserClient(endpoint, credentials.createSsl());
const request = new GetPlaylogRequest();
request.setProfileApiId(profileApiId);
let iterable;
try {
const stream = StreamRPCAsAsync(client.getPlaylog.bind(client), request, log);
iterable = getObjectsFromGrpcIterable(stream);
} catch (err) {
log.error(
`Unexpected MYT error while streaming Ongeki playlog items for userID ${userID}: ${err}`,
);
throw new ScoreImportFatalError(500, `Failed to get scores from MYT.`);
}
return {
iterable,
iterable: streamPlaylog(userID, log),
context: {},
classProvider: null,
game: "ongeki",
@@ -1,3 +1,3 @@
import type { GetPlaylogStreamItem } from "#proto/generated/ongeki/user_pb";
export type MytOngekiScore = GetPlaylogStreamItem.AsObject;
export type MytOngekiScore = GetPlaylogStreamItem;
@@ -1,50 +1,36 @@
import type { ClassProvider } from "#lib/score-import/framework/calculated-data/types";
import type { WaccaUserClient } from "#proto/generated/wacca/user_grpc_pb";
import ScoreImportFatalError from "#lib/score-import/framework/score-importing/score-import-error";
import { WaccaVersion } from "#proto/generated/wacca/common_pb";
import { DataRequest, type DataResponse } from "#proto/generated/wacca/user_pb";
import { DataRequestSchema, WaccaUser } from "#proto/generated/wacca/user_pb";
import { create } from "@bufbuild/protobuf";
import { createClient, type Transport } from "@connectrpc/connect";
import { WaccaStageUps } from "tachi-common/config/game-support/wacca";
import { UnaryRPCAsAsync } from "../../common/api-myt/traverse-api";
export default async function CreateMytWACCAClassHandler(
titleApiId: string,
userClient: WaccaUserClient,
transport: Transport,
): Promise<ClassProvider> {
const req = new DataRequest();
req.setApiId(titleApiId);
const dataRes: DataResponse = await UnaryRPCAsAsync(userClient.getData.bind(userClient), req);
const data = dataRes;
const client = createClient(WaccaUser, transport);
const req = create(DataRequestSchema, { apiId: titleApiId });
const data = await client.getData(req);
return (_gptString, _userID, _ratings, _logger) => {
// Currently (May 2025) Reverse and Plus are supported on Myt.
// We look for both Reverse and PLUS version data, PLUS being prioritized if exists.
// If / when custom dans are added, this will need to change.
const versionDataMap = data.getVersionDataMap();
let versionData: DataResponse.VersionData | undefined;
const versionData =
data.versionData[WaccaVersion.PLUS] ?? data.versionData[WaccaVersion.REVERSE];
if (versionDataMap.has(WaccaVersion.WACCA_VERSION_PLUS)) {
versionData = versionDataMap.get(WaccaVersion.WACCA_VERSION_PLUS);
} else if (versionDataMap.has(WaccaVersion.WACCA_VERSION_REVERSE)) {
versionData = versionDataMap.get(WaccaVersion.WACCA_VERSION_REVERSE);
}
// rank:
// 0 -> none
// 1 -> stage I
// 2 -> stage II
// ...
// 14 -> stage XIV
if (versionData === undefined || versionData.getRank() === 0) {
// rank: 0 = none, 1 = stage I, 2 = stage II, ... 14 = stage XIV
if (versionData === undefined || versionData.rank === 0) {
return {};
}
const stageEnum = WaccaStageUps[versionData.getRank() - 1];
const stageEnum = WaccaStageUps[versionData.rank - 1];
if (stageEnum === undefined) {
throw new ScoreImportFatalError(400, `Unknown stage up value ${versionData.getRank()}`);
throw new ScoreImportFatalError(400, `Unknown stage up value ${versionData.rank}`);
}
return {
@@ -9,30 +9,23 @@ import {
SongOrChartNotFoundFailure,
} from "#lib/score-import/framework/common/converter-failures";
import { ParseDateFromString } from "#lib/score-import/framework/common/score-utils";
import {
type WaccaClearStatus,
WaccaMusicDifficulty,
type WaccaMusicDifficultyMap,
} from "#proto/generated/wacca/common_pb";
import { type WaccaClearStatus, WaccaMusicDifficulty } from "#proto/generated/wacca/common_pb";
import { FindChartOnInGameID } from "#utils/queries/charts";
import { FindSongOnID } from "#utils/queries/songs";
import type { ConverterFunction } from "../../common/types";
import type { MytWaccaScore } from "./types";
const DIFFICULTIES: Record<
WaccaMusicDifficultyMap[keyof WaccaMusicDifficultyMap],
Difficulties["wacca:Single"] | undefined
> = {
[WaccaMusicDifficulty.WACCA_MUSIC_DIFFICULTY_UNSPECIFIED]: undefined,
[WaccaMusicDifficulty.WACCA_MUSIC_DIFFICULTY_NORMAL]: "NORMAL",
[WaccaMusicDifficulty.WACCA_MUSIC_DIFFICULTY_HARD]: "HARD",
[WaccaMusicDifficulty.WACCA_MUSIC_DIFFICULTY_EXPERT]: "EXPERT",
[WaccaMusicDifficulty.WACCA_MUSIC_DIFFICULTY_INFERNO]: "INFERNO",
const DIFFICULTIES: Partial<Record<WaccaMusicDifficulty, Difficulties["wacca:Single"]>> = {
[WaccaMusicDifficulty.UNSPECIFIED]: undefined,
[WaccaMusicDifficulty.NORMAL]: "NORMAL",
[WaccaMusicDifficulty.HARD]: "HARD",
[WaccaMusicDifficulty.EXPERT]: "EXPERT",
[WaccaMusicDifficulty.INFERNO]: "INFERNO",
};
function convertClearStatus(
status: WaccaClearStatus.AsObject | undefined,
status: WaccaClearStatus | undefined,
): GetEnumValue<"wacca:Single", "lamp"> {
if (status === undefined) {
throw new InvalidScoreFailure(`Can't process a score without clearStatus`);
@@ -3,27 +3,37 @@ import type { EmptyObject } from "#utils/types";
import type { integer } from "tachi-common";
import ScoreImportFatalError from "#lib/score-import/framework/score-importing/score-import-error";
import { WaccaUserClient } from "#proto/generated/wacca/user_grpc_pb";
import { PlaylogRequest, type PlaylogStreamItem } from "#proto/generated/wacca/user_pb";
import { credentials } from "@grpc/grpc-js";
import { PlaylogRequestSchema, WaccaUser } from "#proto/generated/wacca/user_pb";
import { create } from "@bufbuild/protobuf";
import { ConnectError, createClient } from "@connectrpc/connect";
import type { ParserFunctionReturns } from "../../common/types";
import type { MytWaccaScore } from "./types";
import {
FetchMytTitleAPIID,
GetMytHostname,
StreamRPCAsAsync,
} from "../../common/api-myt/traverse-api";
import { CreateMytTransport, FetchMytTitleAPIID } from "../../common/api-myt/traverse-api";
import CreateMytWACCAClassHandler from "./class-handler";
async function* getObjectsFromGrpcIterable(
iterable: AsyncIterable<PlaylogStreamItem>,
): AsyncIterable<MytWaccaScore> {
for await (const item of iterable) {
// Re: non-null assertion - in GRPC, all fields are always technically optional.
// Realistically, it's ok to crash if this is undefined - it should never be.
yield item.getInfo()!.toObject();
async function* streamPlaylog(apiId: string, log: KtLogger): AsyncIterable<MytWaccaScore> {
const client = createClient(WaccaUser, CreateMytTransport());
const request = create(PlaylogRequestSchema, { apiId });
try {
for await (const item of client.getPlaylog(request)) {
if (!item.info) {
log.warn(`Received WACCA playlog stream item with no info — skipping.`);
continue;
}
yield item.info;
}
} catch (err) {
if (err instanceof ConnectError) {
log.error({ err, code: err.code }, `MYT gRPC error streaming WACCA playlog`);
} else {
log.error({ err }, `Unexpected MYT error streaming WACCA playlog`);
}
throw new ScoreImportFatalError(500, `Failed to get scores from MYT.`);
}
}
@@ -32,56 +42,18 @@ export default async function ParseMytWACCA(
log: KtLogger,
): Promise<ParserFunctionReturns<MytWaccaScore, EmptyObject>> {
const titleApiId = await FetchMytTitleAPIID(userID, "wacca", log);
const endpoint = GetMytHostname();
let client;
try {
client = new WaccaUserClient(endpoint, credentials.createSsl());
} catch (err) {
// Note: I don't think this actually does anything on the network, so
// it shouldn't really fail. Still, wrap just in case.
log.error(
{
userID,
err,
},
`Unexpected MYT during WaccaUserClient creation for ${userID}: ${err}`,
);
throw new ScoreImportFatalError(500, `Failed to connect to MYT.`);
}
const req = new PlaylogRequest();
req.setApiId(titleApiId);
let iterable;
try {
const stream = StreamRPCAsAsync(client.getPlaylog.bind(client), req, log);
iterable = getObjectsFromGrpcIterable(stream);
} catch (err) {
log.error(
`Unexpected MYT error while streaming WACCA playlog items for userID ${userID}: ${err}`,
);
throw new ScoreImportFatalError(500, `Failed to get scores from MYT.`);
}
let classProvider;
try {
classProvider = await CreateMytWACCAClassHandler(titleApiId, client);
classProvider = await CreateMytWACCAClassHandler(titleApiId, CreateMytTransport());
} catch (err) {
log.error(`Unexpected MYT error while fetching player data for userID ${userID}: ${err}`);
throw new ScoreImportFatalError(500, `Failed to fetch player data from MYT.`);
}
return {
iterable,
iterable: streamPlaylog(titleApiId, log),
context: {},
classProvider,
game: "wacca",
@@ -1,3 +1,3 @@
import type { PlaylogInfo } from "#proto/generated/wacca/user_pb";
export type MytWaccaScore = PlaylogInfo.AsObject;
export type MytWaccaScore = PlaylogInfo;

Some files were not shown because too many files have changed in this diff Show More