diff --git a/server/src/server/router/api/v1/admin/router.ts b/server/src/server/router/api/v1/admin/router.ts index 54d12f143..5537a2872 100644 --- a/server/src/server/router/api/v1/admin/router.ts +++ b/server/src/server/router/api/v1/admin/router.ts @@ -12,6 +12,7 @@ import { Game, UserAuthLevels } from "tachi-common"; import db from "external/mongo/db"; import { DeleteScore } from "lib/delete-scores/delete-scores"; import { UpdateAllPBs } from "utils/calculations/recalc-scores"; +import DestroyUserGamePlaytypeData from "utils/reset-state/destroy-ugpt"; const logger = CreateLogCtx(__filename); @@ -260,6 +261,44 @@ router.post("/delete-score", prValidate({ scoreID: "string" }), async (req, res) }); }); +/** + * Destroys a users UGPT profile and forces a site recalc. + * + * @POST /api/v1/admin/destroy-ugpt + */ +router.post( + "/destroy-ugpt", + prValidate({ + userID: p.isInteger, + game: p.isIn(TachiConfig.GAMES), + playtype: "string", // lazy + }), + async (req, res) => { + const { userID, game, playtype } = req.body; + + const ugpt = await db["game-stats"].findOne({ + userID, + game, + playtype, + }); + + if (!ugpt) { + return res.status(404).json({ + success: false, + description: `No stats for ${userID} (${game} ${playtype}) exist.`, + }); + } + + await DestroyUserGamePlaytypeData(userID, game, playtype); + + return res.status(200).json({ + success: true, + description: `Completely destroyed UGPT for ${userID} (${game} ${playtype}).`, + body: {}, + }); + } +); + /** * Destroy a chart and all of its scores (and sessions). * diff --git a/server/src/utils/reset-state/destroy-ugpt.ts b/server/src/utils/reset-state/destroy-ugpt.ts new file mode 100644 index 000000000..f1dc8131d --- /dev/null +++ b/server/src/utils/reset-state/destroy-ugpt.ts @@ -0,0 +1,74 @@ +import db from "external/mongo/db"; +import { integer, Game, Playtypes } from "tachi-common"; + +/** + * Completely resets a UGPT profile. + * + * This function is dangerous! Should only be ran by admins. + */ +export default async function DestroyUserGamePlaytypeData( + userID: integer, + game: Game, + playtype: Playtypes[Game] +) { + await db["game-stats-snapshots"].remove({ + userID, + game, + playtype, + }); + + await db.scores.remove({ + userID, + game, + playtype, + }); + + await db["personal-bests"].remove({ + userID, + game, + playtype, + }); + + const sessionIDs = ( + await db.sessions.find( + { + userID, + game, + playtype, + }, + { + projection: { + sessionID: 1, + }, + } + ) + ).map((e) => e.sessionID); + + await db["session-view-cache"].remove({ + sessionID: { $in: sessionIDs }, + }); + + await db.sessions.remove({ + userID, + game, + playtype, + }); + + await db.imports.remove({ + userID, + game, + playtype, + }); + + await db["game-settings"].remove({ + userID, + game, + playtype, + }); + + await db["game-stats"].remove({ + userID, + game, + playtype, + }); +}