Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions backend/src/api/controllers/result.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ import { MonkeyRequest } from "../types";
import { getFunbox, checkCompatibility } from "@monkeytype/funbox";
import { tryCatch } from "@monkeytype/util/trycatch";
import { getCachedConfiguration } from "../../init/configuration";
import { allTimeLeaderboardCache } from "../../utils/all-time-leaderboard-cache";

try {
if (!anticheatImplemented()) throw new Error("undefined");
Expand Down Expand Up @@ -534,6 +535,9 @@ export async function addResult(
},
dailyLeaderboardsConfig,
);

allTimeLeaderboardCache.clear();

if (
dailyLeaderboardRank >= 1 &&
dailyLeaderboardRank <= 10 &&
Expand Down
16 changes: 15 additions & 1 deletion backend/src/api/controllers/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import MonkeyError, {
isFirebaseError,
} from "../../utils/error";
import { MonkeyResponse } from "../../utils/monkey-response";
import { getCached, setCached, invalidateUserCache } from "../../utils/cache";
import * as DiscordUtils from "../../utils/discord";
import {
buildAgentLog,
Expand Down Expand Up @@ -914,6 +915,10 @@ export async function getProfile(
): Promise<GetProfileResponse> {
const { uidOrName } = req.params;

const cacheKey = `user:profile:${uidOrName}`;
const cached = await getCached<GetProfileResponse>(cacheKey);
if (cached !== null) return cached;

const user = req.query.isUid
? await UserDAL.getUser(uidOrName, "get user profile")
: await UserDAL.getUserByName(uidOrName, "get user profile");
Expand Down Expand Up @@ -987,7 +992,11 @@ export async function getProfile(
};

if (banned) {
return new MonkeyResponse("Profile retrived: banned user", baseProfile);
await setCached(
cacheKey,
new MonkeyResponse("Profile retrieved: banned user", baseProfile),
);
return new MonkeyResponse("Profile retrieved: banned user", baseProfile);
}

const allTimeLbs = await getAllTimeLbs(user.uid);
Expand All @@ -1005,6 +1014,10 @@ export async function getProfile(
} else {
delete profileData.testActivity;
}
await setCached(
cacheKey,
new MonkeyResponse("Profile retrieved", profileData),
);
return new MonkeyResponse("Profile retrieved", profileData);
}

Expand Down Expand Up @@ -1050,6 +1063,7 @@ export async function updateProfile(
};

await UserDAL.updateProfile(uid, profileDetailsUpdates, user.inventory);
await invalidateUserCache(uid);

return new MonkeyResponse("Profile updated", profileDetailsUpdates);
}
Expand Down
Empty file removed backend/src/credentials/.gitkeep
Empty file.
12 changes: 4 additions & 8 deletions backend/src/dal/leaderboards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,8 @@ export async function get(

return leaderboard;
} catch (e) {
// oxlint-disable-next-line no-unsafe-member-access
if (e.error === 175) {
if ((e as unknown as { error: number }).error === 175) {
//QueryPlanKilled, collection was removed during the query
return false;
}
throw e;
}
Expand Down Expand Up @@ -162,10 +160,8 @@ export async function getRank(
return results[0] ?? null;
}
} catch (e) {
// oxlint-disable-next-line no-unsafe-member-access
if (e.error === 175) {
if ((e as unknown as { error: number }).error === 175) {
//QueryPlanKilled, collection was removed during the query
return false;
}
throw e;
}
Expand Down Expand Up @@ -393,8 +389,8 @@ async function createIndex(
Logger.warning(`Index ${key} not matching, dropping and recreating...`);

const existingIndex = (await getUsersCollection().listIndexes().toArray())
// oxlint-disable-next-line no-unsafe-member-access
.map((it) => it.name as string)

.map((it: unknown) => (it as { name: string }).name)
.find((it) => it.startsWith(key));

if (existingIndex !== undefined && existingIndex !== null) {
Expand Down
34 changes: 34 additions & 0 deletions backend/src/utils/all-time-leaderboard-cache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
type AllTimeCacheKey = {
mode: string;
language: string;
mode2: string;
};

type CacheEntry = {
data: unknown[];
count: number;
};

class AllTimeLeaderboardCache {
private cache = new Map<string, CacheEntry>();

private getKey({ mode, language, mode2 }: AllTimeCacheKey): string {
return `alltime-lb:${mode}:${language}:${mode2}`;
}

get(key: AllTimeCacheKey): CacheEntry | null {
const cacheKey = this.getKey(key);
const entry = this.cache.get(cacheKey);
return entry ?? null;
}

set(key: AllTimeCacheKey, data: unknown[], count: number): void {
this.cache.set(this.getKey(key), { data, count });
}

clear(): void {
this.cache.clear();
}
}

export const allTimeLeaderboardCache = new AllTimeLeaderboardCache();
42 changes: 42 additions & 0 deletions backend/src/utils/cache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { getConnection } from "../init/redis";

const CACHE_PREFIX = "cache:";
const TTL = 300; // == 5 minutes

export async function getCached<T>(key: string): Promise<T | null> {
const redis = getConnection();
if (!redis) return null;

try {
const data = await redis.get(`${CACHE_PREFIX}${key}`);
if (data === null || data === undefined || data === "") return null;
return JSON.parse(data) as T;
} catch {
return null;
}
}

export async function setCached<T>(key: string, data: T): Promise<void> {
const redis = getConnection();
if (!redis) return;

try {
await redis.setex(`${CACHE_PREFIX}${key}`, TTL, JSON.stringify(data));
} catch (err) {
console.error("Cache set failed:", err);
}
}

export async function invalidateUserCache(userId: string): Promise<void> {
const redis = getConnection();
if (!redis) return;

try {
const keys = await redis.keys(`${CACHE_PREFIX}user:profile:${userId}*`);
if (keys.length > 0) {
await redis.del(keys);
}
} catch (err) {
console.error("Cache invalidation failed:", err);
}
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
"oxlint": "1.40.0",
"oxlint-tsgolint": "0.11.1",
"prettier": "3.7.1",
"turbo": "2.5.6",
"turbo": "2.7.5",
"vitest": "4.0.15"
},
"lint-staged": {
Expand Down
Loading