diff --git a/.gitignore b/.gitignore index e2a50cb343d3..f3a3a0ff2f30 100644 --- a/.gitignore +++ b/.gitignore @@ -133,3 +133,4 @@ frontend/static/webfonts-preview frontend/.env.sentry-build-plugin .claude/worktrees 1024MiB +.pnpm-store diff --git a/backend/__tests__/api/controllers/admin.spec.ts b/backend/__tests__/api/controllers/admin.spec.ts index 55c28c2c9a5c..50cabda30bf3 100644 --- a/backend/__tests__/api/controllers/admin.spec.ts +++ b/backend/__tests__/api/controllers/admin.spec.ts @@ -8,6 +8,8 @@ import * as ReportDal from "../../../src/dal/report"; import * as LogsDal from "../../../src/dal/logs"; import GeorgeQueue from "../../../src/queues/george-queue"; import * as AuthUtil from "../../../src/utils/auth"; +import * as DailyLeaderboards from "../../../src/utils/daily-leaderboards"; +import * as WeeklyXpLeaderboard from "../../../src/services/weekly-xp-leaderboard"; import { enableRateLimitExpects } from "../../__testData__/rate-limit"; import Test from "supertest/lib/test"; @@ -66,12 +68,26 @@ describe("AdminController", () => { const userBannedMock = vi.spyOn(UserDal, "setBanned"); const georgeBannedMock = vi.spyOn(GeorgeQueue, "userBanned"); const getUserMock = vi.spyOn(UserDal, "getPartialUser"); + const purgeUserFromDailyLeaderboardsMock = vi.spyOn( + DailyLeaderboards, + "purgeUserFromDailyLeaderboards", + ); + const purgeUserFromXpLeaderboardsMock = vi.spyOn( + WeeklyXpLeaderboard, + "purgeUserFromXpLeaderboards", + ); beforeEach(() => { - [userBannedMock, georgeBannedMock, getUserMock].forEach((it) => - it.mockClear(), - ); + [ + userBannedMock, + georgeBannedMock, + getUserMock, + purgeUserFromDailyLeaderboardsMock, + purgeUserFromXpLeaderboardsMock, + ].forEach((it) => it.mockClear()); userBannedMock.mockResolvedValue(); + purgeUserFromDailyLeaderboardsMock.mockResolvedValue(); + purgeUserFromXpLeaderboardsMock.mockResolvedValue(); }); it("should ban user with discordId", async () => { @@ -101,6 +117,14 @@ describe("AdminController", () => { ]); expect(userBannedMock).toHaveBeenCalledWith(victimUid, true); expect(georgeBannedMock).toHaveBeenCalledWith("discordId", true); + expect(purgeUserFromDailyLeaderboardsMock).toHaveBeenCalledWith( + victimUid, + (await configuration).dailyLeaderboards, + ); + expect(purgeUserFromXpLeaderboardsMock).toHaveBeenCalledWith( + victimUid, + (await configuration).leaderboards.weeklyXp, + ); }); it("should unban user without discordId", async () => { //GIVEN @@ -128,6 +152,8 @@ describe("AdminController", () => { ]); expect(userBannedMock).toHaveBeenCalledWith(victimUid, false); expect(georgeBannedMock).not.toHaveBeenCalled(); + expect(purgeUserFromDailyLeaderboardsMock).not.toHaveBeenCalled(); + expect(purgeUserFromXpLeaderboardsMock).not.toHaveBeenCalled(); }); it("should fail without mandatory properties", async () => { //GIVEN diff --git a/backend/src/api/controllers/admin.ts b/backend/src/api/controllers/admin.ts index 85068dc5beb4..2e766ce00da4 100644 --- a/backend/src/api/controllers/admin.ts +++ b/backend/src/api/controllers/admin.ts @@ -16,6 +16,8 @@ import MonkeyError, { getErrorMessage } from "../../utils/error"; import { Configuration } from "@monkeytype/schemas/configuration"; import { addImportantLog } from "../../dal/logs"; import { MonkeyRequest } from "../types"; +import { purgeUserFromDailyLeaderboards } from "../../utils/daily-leaderboards"; +import { purgeUserFromXpLeaderboards } from "../../services/weekly-xp-leaderboard"; export async function test(_req: MonkeyRequest): Promise { return new MonkeyResponse("OK", null); @@ -33,13 +35,28 @@ export async function toggleBan( const discordId = user.discordId; const discordIdIsValid = discordId !== undefined && discordId !== ""; - await UserDAL.setBanned(uid, !user.banned); - if (discordIdIsValid) await GeorgeQueue.userBanned(discordId, !user.banned); + const banning = !user.banned; + + await UserDAL.setBanned(uid, banning); + if (discordIdIsValid) await GeorgeQueue.userBanned(discordId, banning); + + if (banning) { + await Promise.all([ + purgeUserFromDailyLeaderboards( + uid, + req.ctx.configuration.dailyLeaderboards, + ), + purgeUserFromXpLeaderboards( + uid, + req.ctx.configuration.leaderboards.weeklyXp, + ), + ]); + } - void addImportantLog("user_ban_toggled", { banned: !user.banned }, uid); + void addImportantLog("user_ban_toggled", { banned: banning }, uid); return new MonkeyResponse(`Ban toggled`, { - banned: !user.banned, + banned: banning, }); } diff --git a/backend/src/api/controllers/result.ts b/backend/src/api/controllers/result.ts index d56ad0c0c54d..1353f4e3a1a1 100644 --- a/backend/src/api/controllers/result.ts +++ b/backend/src/api/controllers/result.ts @@ -23,7 +23,10 @@ import { incrementDailyLeaderboard, } from "../../utils/prometheus"; import GeorgeQueue from "../../queues/george-queue"; -import { getDailyLeaderboard } from "../../utils/daily-leaderboards"; +import { + getDailyLeaderboard, + purgeUserFromDailyLeaderboards, +} from "../../utils/daily-leaderboards"; import * as UserDAL from "../../dal/user"; import { buildMonkeyMail } from "../../utils/monkey-mail"; import * as WeeklyXpLeaderboard from "../../services/weekly-xp-leaderboard"; @@ -386,11 +389,21 @@ export async function addResult( subject: "Banned", body: "Your account has been automatically banned for triggering the anticheat system. If you believe this is a mistake, please contact support.", }); - await UserDAL.addToInbox( - uid, - [mail], - req.ctx.configuration.users.inbox, - ); + await Promise.all([ + UserDAL.addToInbox( + uid, + [mail], + req.ctx.configuration.users.inbox, + ), + purgeUserFromDailyLeaderboards( + uid, + req.ctx.configuration.dailyLeaderboards, + ), + WeeklyXpLeaderboard.purgeUserFromXpLeaderboards( + uid, + req.ctx.configuration.leaderboards.weeklyXp, + ), + ]); user.banned = true; } }