feat: Introduce TimerKey enum and refactor timer key usage across services with new tests.

This commit is contained in:
syntaxbullet
2026-02-13 13:11:16 +01:00
parent 570cdc69c1
commit 2d35a5eabb
5 changed files with 57 additions and 7 deletions

View File

@@ -50,7 +50,7 @@ export const userTimers = pgTable('user_timers', {
userId: bigint('user_id', { mode: 'bigint' })
.references(() => users.id, { onDelete: 'cascade' }).notNull(),
type: varchar('type', { length: 50 }).notNull(), // 'COOLDOWN', 'EFFECT', 'ACCESS'
key: varchar('key', { length: 100 }).notNull(), // 'daily', 'chn_12345', 'xp_boost'
key: varchar('key', { length: 100 }).notNull(), // TimerKey, 'chn_12345', 'xp_boost'
expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(),
metadata: jsonb('metadata').default({}),
}, (table) => [

View File

@@ -0,0 +1,44 @@
import { describe, it, expect } from "bun:test";
import { TimerKey, TimerType, TransactionType } from "./constants";
describe("TimerKey", () => {
it("should have CHAT_XP value", () => {
expect(TimerKey.CHAT_XP).toBe("chat_xp" as TimerKey);
});
it("should have DAILY value", () => {
expect(TimerKey.DAILY).toBe("daily" as TimerKey);
});
it("should have WEEKLY value", () => {
expect(TimerKey.WEEKLY).toBe("weekly" as TimerKey);
});
});
describe("TimerType", () => {
it("should have COOLDOWN value", () => {
expect(TimerType.COOLDOWN).toBe("COOLDOWN" as TimerType);
});
it("should have EFFECT value", () => {
expect(TimerType.EFFECT).toBe("EFFECT" as TimerType);
});
it("should have ACCESS value", () => {
expect(TimerType.ACCESS).toBe("ACCESS" as TimerType);
});
});
describe("TransactionType", () => {
it("should have DAILY_REWARD value", () => {
expect(TransactionType.DAILY_REWARD).toBe("DAILY_REWARD" as TransactionType);
});
it("should have TRANSFER_IN value", () => {
expect(TransactionType.TRANSFER_IN).toBe("TRANSFER_IN" as TransactionType);
});
it("should have TRANSFER_OUT value", () => {
expect(TransactionType.TRANSFER_OUT).toBe("TRANSFER_OUT" as TransactionType);
});
});

View File

@@ -10,6 +10,12 @@ export enum TimerType {
TRIVIA_COOLDOWN = 'TRIVIA_COOLDOWN',
}
export enum TimerKey {
CHAT_XP = 'chat_xp',
DAILY = 'daily',
WEEKLY = 'weekly',
}
export enum EffectType {
ADD_XP = 'ADD_XP',
ADD_BALANCE = 'ADD_BALANCE',

View File

@@ -4,7 +4,7 @@ import { config } from "@shared/lib/config";
import { withTransaction } from "@/lib/db";
import type { Transaction } from "@shared/lib/types";
import { UserError } from "@shared/lib/errors";
import { TimerType, TransactionType } from "@shared/lib/constants";
import { TimerKey, TimerType, TransactionType } from "@shared/lib/constants";
export const economyService = {
transfer: async (fromUserId: string, toUserId: string, amount: bigint, tx?: Transaction) => {
@@ -82,7 +82,7 @@ export const economyService = {
where: and(
eq(userTimers.userId, BigInt(userId)),
eq(userTimers.type, TimerType.COOLDOWN),
eq(userTimers.key, 'daily')
eq(userTimers.key, TimerKey.DAILY)
),
});
@@ -141,7 +141,7 @@ export const economyService = {
.values({
userId: BigInt(userId),
type: TimerType.COOLDOWN,
key: 'daily',
key: TimerKey.DAILY,
expiresAt: nextReadyAt,
})
.onConflictDoUpdate({

View File

@@ -3,7 +3,7 @@ import { eq, sql, and } from "drizzle-orm";
import { withTransaction } from "@/lib/db";
import { config } from "@shared/lib/config";
import type { Transaction } from "@shared/lib/types";
import { TimerType } from "@shared/lib/constants";
import { TimerKey, TimerType } from "@shared/lib/constants";
export const levelingService = {
// Calculate total XP required to REACH a specific level (Cumulative)
@@ -84,7 +84,7 @@ export const levelingService = {
where: and(
eq(userTimers.userId, BigInt(id)),
eq(userTimers.type, TimerType.COOLDOWN),
eq(userTimers.key, 'chat_xp')
eq(userTimers.key, TimerKey.CHAT_XP)
),
});
@@ -120,7 +120,7 @@ export const levelingService = {
.values({
userId: BigInt(id),
type: TimerType.COOLDOWN,
key: 'chat_xp',
key: TimerKey.CHAT_XP,
expiresAt: nextReadyAt,
})
.onConflictDoUpdate({