feat(bot): added achievement caching
Some checks failed
Commitlint / Run commitlint scanning (push) Has been cancelled

This commit is contained in:
Ahmad 2025-06-24 19:33:11 -04:00
parent 0cf8da1941
commit b990b8ea06
No known key found for this signature in database
GPG key ID: 8FD8A93530D182BF
3 changed files with 105 additions and 102 deletions

View file

@ -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);

View file

@ -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<schema.userAchievementsTableTypes[]> {
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<boolean> {
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<schema.achievementDefinitionsTableTypes | undefined> {
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<boolean> {
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<boolean> {
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);

View file

@ -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);
}
}