From b990b8ea06e2d6fd7baaf92e67215412b0703c21 Mon Sep 17 00:00:00 2001 From: Ahmad <103906421+ahmadk953@users.noreply.github.com> Date: Tue, 24 Jun 2025 19:33:11 -0400 Subject: [PATCH 1/3] feat(bot): added achievement caching --- src/commands/fun/achievement.ts | 8 +- src/db/functions/achievementFunctions.ts | 189 +++++++++++------------ src/util/achievementManager.ts | 10 +- 3 files changed, 105 insertions(+), 102 deletions(-) diff --git a/src/commands/fun/achievement.ts b/src/commands/fun/achievement.ts index 5fea0f2..f5b8c79 100644 --- a/src/commands/fun/achievement.ts +++ b/src/commands/fun/achievement.ts @@ -14,10 +14,10 @@ import { import { getAllAchievements, getUserAchievements, - awardAchievement, createAchievement, deleteAchievement, removeUserAchievement, + updateAchievementProgress, } from '@/db/db.js'; import { announceAchievement } from '@/util/achievementManager.js'; import { createPaginationButtons } from '@/util/helpers.js'; @@ -309,7 +309,11 @@ async function handleAwardAchievement( return; } - const success = await awardAchievement(user.id, achievementId); + const success = await updateAchievementProgress( + user.id, + achievementId, + 100, + ); if (success) { await announceAchievement(interaction.guild!, user.id, achievement); diff --git a/src/db/functions/achievementFunctions.ts b/src/db/functions/achievementFunctions.ts index 7d3ee6a..38c6551 100644 --- a/src/db/functions/achievementFunctions.ts +++ b/src/db/functions/achievementFunctions.ts @@ -1,6 +1,12 @@ import { and, eq } from 'drizzle-orm'; -import { db, ensureDbInitialized, handleDbError } from '../db.js'; +import { + db, + ensureDbInitialized, + handleDbError, + invalidateCache, + withCache, +} from '../db.js'; import * as schema from '../schema.js'; /** @@ -12,16 +18,22 @@ export async function getAllAchievements(): Promise< > { try { await ensureDbInitialized(); - if (!db) { console.error('Database not initialized, cannot get achievements'); return []; } - return await db - .select() - .from(schema.achievementDefinitionsTable) - .orderBy(schema.achievementDefinitionsTable.threshold); + const achievementDefinitions = await withCache( + 'achievementDefinitions', + async () => { + return await db + .select() + .from(schema.achievementDefinitionsTable) + .orderBy(schema.achievementDefinitionsTable.threshold); + }, + ); + + return achievementDefinitions; } catch (error) { return handleDbError('Failed to get all achievements', error as Error); } @@ -37,84 +49,33 @@ export async function getUserAchievements( ): Promise { try { await ensureDbInitialized(); - if (!db) { console.error('Database not initialized, cannot get user achievements'); return []; } - return await db - .select({ - id: schema.userAchievementsTable.id, - discordId: schema.userAchievementsTable.discordId, - achievementId: schema.userAchievementsTable.achievementId, - earnedAt: schema.userAchievementsTable.earnedAt, - progress: schema.userAchievementsTable.progress, - }) - .from(schema.userAchievementsTable) - .where(eq(schema.userAchievementsTable.discordId, userId)); + const cachedUserAchievements = await withCache( + `userAchievements:${userId}:allAchievements`, + async () => { + return await db + .select({ + id: schema.userAchievementsTable.id, + discordId: schema.userAchievementsTable.discordId, + achievementId: schema.userAchievementsTable.achievementId, + earnedAt: schema.userAchievementsTable.earnedAt, + progress: schema.userAchievementsTable.progress, + }) + .from(schema.userAchievementsTable) + .where(eq(schema.userAchievementsTable.discordId, userId)); + }, + ); + + return cachedUserAchievements; } catch (error) { return handleDbError('Failed to get user achievements', error as Error); } } -/** - * Award an achievement to a user - * @param userId - Discord ID of the user - * @param achievementId - ID of the achievement - * @returns Boolean indicating success - */ -export async function awardAchievement( - userId: string, - achievementId: number, -): Promise { - try { - await ensureDbInitialized(); - - if (!db) { - console.error('Database not initialized, cannot award achievement'); - return false; - } - - const existing = await db - .select() - .from(schema.userAchievementsTable) - .where( - and( - eq(schema.userAchievementsTable.discordId, userId), - eq(schema.userAchievementsTable.achievementId, achievementId), - ), - ) - .then((rows) => rows[0]); - - if (existing) { - if (existing.earnedAt) { - return false; - } - - await db - .update(schema.userAchievementsTable) - .set({ - earnedAt: new Date(), - progress: 100, - }) - .where(eq(schema.userAchievementsTable.id, existing.id)); - } else { - await db.insert(schema.userAchievementsTable).values({ - discordId: userId, - achievementId: achievementId, - earnedAt: new Date(), - progress: 100, - }); - } - - return true; - } catch (error) { - handleDbError('Failed to award achievement', error as Error); - return false; - } -} - /** * Update achievement progress for a user * @param userId - Discord ID of the user @@ -136,28 +97,53 @@ export async function updateAchievementProgress( return false; } - const existing = await db - .select() - .from(schema.userAchievementsTable) - .where( - and( - eq(schema.userAchievementsTable.discordId, userId), - eq(schema.userAchievementsTable.achievementId, achievementId), - ), - ) - .then((rows) => rows[0]); + const existing = await withCache( + `userAchievements:${userId}:${achievementId}`, + async () => { + return await db + .select() + .from(schema.userAchievementsTable) + .where( + and( + eq(schema.userAchievementsTable.discordId, userId), + eq(schema.userAchievementsTable.achievementId, achievementId), + ), + ) + .then((rows) => rows[0]); + }, + ); + + await invalidateCache(`userAchievements:${userId}:allAchievements`); + await invalidateCache(`userAchievements:${userId}:${achievementId}`); if (existing) { - await db - .update(schema.userAchievementsTable) - .set({ progress }) - .where(eq(schema.userAchievementsTable.id, existing.id)); + await withCache( + `userAchievements:${userId}:${achievementId}`, + async () => { + return await db + .update(schema.userAchievementsTable) + .set({ progress, earnedAt: progress === 100 ? new Date() : null }) + .where(eq(schema.userAchievementsTable.id, existing.id)) + .returning(); + }, + ); + await getUserAchievements(userId); } else { - await db.insert(schema.userAchievementsTable).values({ - discordId: userId, - achievementId, - progress, - }); + await withCache( + `userAchievements:${userId}:${achievementId}`, + async () => { + return await db + .insert(schema.userAchievementsTable) + .values({ + discordId: userId, + achievementId, + progress, + earnedAt: progress === 100 ? new Date() : null, + }) + .returning(); + }, + ); + await getUserAchievements(userId); } return true; @@ -184,12 +170,13 @@ export async function createAchievement(achievementData: { }): Promise { try { await ensureDbInitialized(); - if (!db) { console.error('Database not initialized, cannot create achievement'); return undefined; } + await invalidateCache('achievementDefinitions'); + const [achievement] = await db .insert(schema.achievementDefinitionsTable) .values({ @@ -204,6 +191,8 @@ export async function createAchievement(achievementData: { }) .returning(); + await getAllAchievements(); + return achievement; } catch (error) { return handleDbError('Failed to create achievement', error as Error); @@ -220,12 +209,13 @@ export async function deleteAchievement( ): Promise { try { await ensureDbInitialized(); - if (!db) { console.error('Database not initialized, cannot delete achievement'); return false; } + await invalidateCache('achievementDefinitions'); + await db .delete(schema.userAchievementsTable) .where(eq(schema.userAchievementsTable.achievementId, achievementId)); @@ -234,6 +224,8 @@ export async function deleteAchievement( .delete(schema.achievementDefinitionsTable) .where(eq(schema.achievementDefinitionsTable.id, achievementId)); + await getAllAchievements(); + return true; } catch (error) { handleDbError('Failed to delete achievement', error as Error); @@ -253,12 +245,14 @@ export async function removeUserAchievement( ): Promise { try { await ensureDbInitialized(); - if (!db) { console.error('Database not initialized, cannot remove user achievement'); return false; } + await invalidateCache(`userAchievements:${discordId}:allAchievements`); + await invalidateCache(`userAchievements:${discordId}:${achievementId}`); + await db .delete(schema.userAchievementsTable) .where( @@ -267,6 +261,9 @@ export async function removeUserAchievement( eq(schema.userAchievementsTable.achievementId, achievementId), ), ); + + await getUserAchievements(discordId); + return true; } catch (error) { handleDbError('Failed to remove user achievement', error as Error); diff --git a/src/util/achievementManager.ts b/src/util/achievementManager.ts index aaf3a05..cbc7746 100644 --- a/src/util/achievementManager.ts +++ b/src/util/achievementManager.ts @@ -2,7 +2,6 @@ import { Message, EmbedBuilder, TextChannel, Guild } from 'discord.js'; import { addXpToUser, - awardAchievement, getAllAchievements, getUserAchievements, getUserLevel, @@ -34,11 +33,14 @@ async function handleProgress( (a) => a.achievementId === achievement.id && a.earnedAt !== null, ); - await updateAchievementProgress(userId, achievement.id, progress); + const updated = await updateAchievementProgress( + userId, + achievement.id, + progress, + ); if (progress === 100 && !existing && !skipAward) { - const awarded = await awardAchievement(userId, achievement.id); - if (awarded && guild) { + if (updated && guild) { await announceAchievement(guild, userId, achievement); } } From 20f50536b62ffd806ff2c84a2cc9e95bcb333f07 Mon Sep 17 00:00:00 2001 From: Ahmad <103906421+ahmadk953@users.noreply.github.com> Date: Tue, 24 Jun 2025 20:22:51 -0400 Subject: [PATCH 2/3] chore(bot): update caching for achievements --- src/db/functions/achievementFunctions.ts | 83 +++++++++--------------- 1 file changed, 31 insertions(+), 52 deletions(-) diff --git a/src/db/functions/achievementFunctions.ts b/src/db/functions/achievementFunctions.ts index 38c6551..f71df89 100644 --- a/src/db/functions/achievementFunctions.ts +++ b/src/db/functions/achievementFunctions.ts @@ -55,7 +55,7 @@ export async function getUserAchievements( } const cachedUserAchievements = await withCache( - `userAchievements:${userId}:allAchievements`, + `userAchievements:${userId}`, async () => { return await db .select({ @@ -97,55 +97,38 @@ export async function updateAchievementProgress( return false; } - const existing = await withCache( - `userAchievements:${userId}:${achievementId}`, - async () => { - return await db - .select() - .from(schema.userAchievementsTable) - .where( - and( - eq(schema.userAchievementsTable.discordId, userId), - eq(schema.userAchievementsTable.achievementId, achievementId), - ), - ) - .then((rows) => rows[0]); - }, - ); - - await invalidateCache(`userAchievements:${userId}:allAchievements`); - await invalidateCache(`userAchievements:${userId}:${achievementId}`); + const existing = await db + .select() + .from(schema.userAchievementsTable) + .where( + and( + eq(schema.userAchievementsTable.discordId, userId), + eq(schema.userAchievementsTable.achievementId, achievementId), + ), + ) + .then((rows) => rows[0]); if (existing) { - await withCache( - `userAchievements:${userId}:${achievementId}`, - async () => { - return await db - .update(schema.userAchievementsTable) - .set({ progress, earnedAt: progress === 100 ? new Date() : null }) - .where(eq(schema.userAchievementsTable.id, existing.id)) - .returning(); - }, - ); - await getUserAchievements(userId); + await db + .update(schema.userAchievementsTable) + .set({ progress, earnedAt: progress === 100 ? new Date() : null }) + .where(eq(schema.userAchievementsTable.id, existing.id)) + .returning(); } else { - await withCache( - `userAchievements:${userId}:${achievementId}`, - async () => { - return await db - .insert(schema.userAchievementsTable) - .values({ - discordId: userId, - achievementId, - progress, - earnedAt: progress === 100 ? new Date() : null, - }) - .returning(); - }, - ); - await getUserAchievements(userId); + await db + .insert(schema.userAchievementsTable) + .values({ + discordId: userId, + achievementId, + progress, + earnedAt: progress === 100 ? new Date() : null, + }) + .returning(); } + await invalidateCache(`userAchievements:${userId}`); + await getUserAchievements(userId); + return true; } catch (error) { handleDbError('Failed to update achievement progress', error as Error); @@ -175,8 +158,6 @@ export async function createAchievement(achievementData: { return undefined; } - await invalidateCache('achievementDefinitions'); - const [achievement] = await db .insert(schema.achievementDefinitionsTable) .values({ @@ -191,6 +172,7 @@ export async function createAchievement(achievementData: { }) .returning(); + await invalidateCache('achievementDefinitions'); await getAllAchievements(); return achievement; @@ -214,8 +196,6 @@ export async function deleteAchievement( return false; } - await invalidateCache('achievementDefinitions'); - await db .delete(schema.userAchievementsTable) .where(eq(schema.userAchievementsTable.achievementId, achievementId)); @@ -224,6 +204,7 @@ export async function deleteAchievement( .delete(schema.achievementDefinitionsTable) .where(eq(schema.achievementDefinitionsTable.id, achievementId)); + await invalidateCache('achievementDefinitions'); await getAllAchievements(); return true; @@ -250,9 +231,6 @@ export async function removeUserAchievement( return false; } - await invalidateCache(`userAchievements:${discordId}:allAchievements`); - await invalidateCache(`userAchievements:${discordId}:${achievementId}`); - await db .delete(schema.userAchievementsTable) .where( @@ -262,6 +240,7 @@ export async function removeUserAchievement( ), ); + await invalidateCache(`userAchievements:${discordId}`); await getUserAchievements(discordId); return true; From 4b7c6dda0752707256711b4ab67fb2b0718aaffb Mon Sep 17 00:00:00 2001 From: Ahmad <103906421+ahmadk953@users.noreply.github.com> Date: Tue, 24 Jun 2025 20:31:45 -0400 Subject: [PATCH 3/3] chore(bot): update caching logic --- src/db/functions/achievementFunctions.ts | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/src/db/functions/achievementFunctions.ts b/src/db/functions/achievementFunctions.ts index f71df89..7e3440c 100644 --- a/src/db/functions/achievementFunctions.ts +++ b/src/db/functions/achievementFunctions.ts @@ -112,22 +112,17 @@ export async function updateAchievementProgress( await db .update(schema.userAchievementsTable) .set({ progress, earnedAt: progress === 100 ? new Date() : null }) - .where(eq(schema.userAchievementsTable.id, existing.id)) - .returning(); + .where(eq(schema.userAchievementsTable.id, existing.id)); } else { - await db - .insert(schema.userAchievementsTable) - .values({ - discordId: userId, - achievementId, - progress, - earnedAt: progress === 100 ? new Date() : null, - }) - .returning(); + await db.insert(schema.userAchievementsTable).values({ + discordId: userId, + achievementId, + progress, + earnedAt: progress === 100 ? new Date() : null, + }); } await invalidateCache(`userAchievements:${userId}`); - await getUserAchievements(userId); return true; } catch (error) { @@ -173,7 +168,6 @@ export async function createAchievement(achievementData: { .returning(); await invalidateCache('achievementDefinitions'); - await getAllAchievements(); return achievement; } catch (error) { @@ -205,7 +199,6 @@ export async function deleteAchievement( .where(eq(schema.achievementDefinitionsTable.id, achievementId)); await invalidateCache('achievementDefinitions'); - await getAllAchievements(); return true; } catch (error) { @@ -241,7 +234,6 @@ export async function removeUserAchievement( ); await invalidateCache(`userAchievements:${discordId}`); - await getUserAchievements(discordId); return true; } catch (error) {