feat: Implement chat XP with cooldowns and display an XP progress bar on the student ID card.

This commit is contained in:
syntaxbullet
2025-12-09 12:04:03 +01:00
parent 90a1861416
commit 9250057574
3 changed files with 150 additions and 5 deletions

View File

@@ -1,10 +1,13 @@
import { users } from "@/db/schema";
import { eq, sql } from "drizzle-orm";
import { users, cooldowns } from "@/db/schema";
import { eq, sql, and } from "drizzle-orm";
import { DrizzleClient } from "@/lib/DrizzleClient";
// Simple configurable curve: Base * (Level ^ Exponent)
const XP_BASE = 1000;
const XP_EXPONENT = 1.5;
const XP_BASE = 100;
const XP_EXPONENT = 2.5;
const CHAT_XP_COOLDOWN_MS = 60000; // 1 minute
const MIN_CHAT_XP = 15;
const MAX_CHAT_XP = 25;
export const levelingService = {
// Calculate XP required for a specific level
@@ -12,6 +15,7 @@ export const levelingService = {
return Math.floor(XP_BASE * Math.pow(level, XP_EXPONENT));
},
// Pure XP addition - No cooldown checks
addXp: async (id: string, amount: bigint, tx?: any) => {
const execute = async (txFn: any) => {
// Get current state
@@ -45,7 +49,7 @@ export const levelingService = {
.returning();
return { user: updatedUser, levelUp, currentLevel };
}
};
if (tx) {
return await execute(tx);
@@ -55,4 +59,52 @@ export const levelingService = {
})
}
},
// Handle chat XP with cooldowns
processChatXp: async (id: string, tx?: any) => {
const execute = async (txFn: any) => {
// check if an xp cooldown is in place
const cooldown = await txFn.query.cooldowns.findFirst({
where: and(
eq(cooldowns.userId, BigInt(id)),
eq(cooldowns.actionKey, 'xp')
),
});
const now = new Date();
if (cooldown && cooldown.readyAt > now) {
return { awarded: false, reason: 'cooldown' };
}
// Calculate random XP
const amount = BigInt(Math.floor(Math.random() * (MAX_CHAT_XP - MIN_CHAT_XP + 1)) + MIN_CHAT_XP);
// Add XP
const result = await levelingService.addXp(id, amount, txFn);
// Update/Set Cooldown
const nextReadyAt = new Date(now.getTime() + CHAT_XP_COOLDOWN_MS);
await txFn.insert(cooldowns)
.values({
userId: BigInt(id),
actionKey: 'xp',
readyAt: nextReadyAt,
})
.onConflictDoUpdate({
target: [cooldowns.userId, cooldowns.actionKey],
set: { readyAt: nextReadyAt },
});
return { awarded: true, amount, ...result };
};
if (tx) {
return await execute(tx);
} else {
return await DrizzleClient.transaction(async (t) => {
return await execute(t);
})
}
}
};