diff --git a/backend/__tests__/utils/validation.spec.ts b/backend/__tests__/utils/validation.spec.ts index ccef9bd5b..f649a0e2c 100644 --- a/backend/__tests__/utils/validation.spec.ts +++ b/backend/__tests__/utils/validation.spec.ts @@ -121,4 +121,55 @@ describe("Validation", () => { ); }); }); + it("isTestTooShort", () => { + const testCases = [ + { + result: { + mode: "time", + mode2: 10, + customText: undefined, + testDuration: 10, + bailedOut: false, + }, + expected: true, + }, + { + result: { + mode: "time", + mode2: 15, + customText: undefined, + testDuration: 15, + bailedOut: false, + }, + expected: false, + }, + { + result: { + mode: "time", + mode2: 0, + customText: undefined, + testDuration: 20, + bailedOut: false, + }, + expected: false, + }, + { + result: { + mode: "time", + mode2: 0, + customText: undefined, + testDuration: 2, + bailedOut: false, + }, + expected: true, + }, + ]; + + testCases.forEach((testCase) => { + //@ts-ignore + expect(Validation.isTestTooShort(testCase.result)).toBe( + testCase.expected + ); + }); + }); }); diff --git a/backend/redis-scripts/purge-results.lua b/backend/redis-scripts/purge-results.lua new file mode 100644 index 000000000..c4c15b82b --- /dev/null +++ b/backend/redis-scripts/purge-results.lua @@ -0,0 +1,23 @@ +local redis_call = redis.call +local string_match = string.match + +local user_id = ARGV[1] +local leaderboards_namespace = ARGV[2] + +local current_cursor = '0' +local match_pattern = leaderboards_namespace .. '*' + +repeat + local result = redis_call('SCAN', current_cursor, 'MATCH', match_pattern) + local next_cursor, matched_keys = result[1], result[2] + + for _, key in ipairs(matched_keys) do + if (string_match(key, 'results')) then + redis_call('HDEL', key, user_id) + elseif (string_match(key, 'scores')) then + redis_call('ZREM', key, user_id) + end + end + + current_cursor = next_cursor +until (current_cursor == '0') diff --git a/backend/src/api/controllers/user.ts b/backend/src/api/controllers/user.ts index 8819785a3..400cd213c 100644 --- a/backend/src/api/controllers/user.ts +++ b/backend/src/api/controllers/user.ts @@ -13,6 +13,7 @@ import { deleteAll as deleteAllResults } from "../../dal/result"; import { deleteConfig } from "../../dal/config"; import { verify } from "../../utils/captcha"; import * as LeaderboardsDAL from "../../dal/leaderboards"; +import { purgeUserFromDailyLeaderboards } from "../../utils/daily-leaderboards"; async function verifyCaptcha(captcha: string): Promise { if (!(await verify(captcha))) { @@ -76,6 +77,10 @@ export async function resetUser( deleteAllPresets(uid), deleteAllResults(uid), deleteConfig(uid), + purgeUserFromDailyLeaderboards( + uid, + req.ctx.configuration.dailyLeaderboards + ), ]); Logger.logToDb("user_reset", `${userInfo.email} ${userInfo.name}`, uid); @@ -105,6 +110,10 @@ export async function clearPb( const { uid } = req.ctx.decodedToken; await UserDAL.clearPb(uid); + await purgeUserFromDailyLeaderboards( + uid, + req.ctx.configuration.dailyLeaderboards + ); Logger.logToDb("user_cleared_pbs", "", uid); return new MonkeyResponse("User's PB cleared"); diff --git a/backend/src/utils/daily-leaderboards.ts b/backend/src/utils/daily-leaderboards.ts index dce8d201d..6ad9f4116 100644 --- a/backend/src/utils/daily-leaderboards.ts +++ b/backend/src/utils/daily-leaderboards.ts @@ -157,6 +157,19 @@ export class DailyLeaderboard { } } +export async function purgeUserFromDailyLeaderboards( + uid: string, + configuration: MonkeyTypes.Configuration["dailyLeaderboards"] +): Promise { + const connection = RedisClient.getConnection(); + if (!connection || !configuration.enabled) { + return; + } + + // @ts-ignore + await connection.purgeResults(0, uid, dailyLeaderboardNamespace); +} + let DAILY_LEADERBOARDS: LRUCache; export function initializeDailyLeaderboardsCache(