import { userQuests } from "@/db/schema"; import { eq, and } from "drizzle-orm"; import { UserError } from "@/lib/errors"; import { DrizzleClient } from "@/lib/DrizzleClient"; import { economyService } from "@/modules/economy/economy.service"; import { levelingService } from "@/modules/leveling/leveling.service"; import { withTransaction } from "@/lib/db"; import type { Transaction } from "@/lib/types"; import { TransactionType } from "@/lib/constants"; export const questService = { assignQuest: async (userId: string, questId: number, tx?: Transaction) => { return await withTransaction(async (txFn) => { return await txFn.insert(userQuests) .values({ userId: BigInt(userId), questId: questId, progress: 0, }) .onConflictDoNothing() // Ignore if already assigned .returning(); }, tx); }, updateProgress: async (userId: string, questId: number, progress: number, tx?: Transaction) => { return await withTransaction(async (txFn) => { return await txFn.update(userQuests) .set({ progress: progress }) .where(and( eq(userQuests.userId, BigInt(userId)), eq(userQuests.questId, questId) )) .returning(); }, tx); }, completeQuest: async (userId: string, questId: number, tx?: Transaction) => { return await withTransaction(async (txFn) => { const userQuest = await txFn.query.userQuests.findFirst({ where: and( eq(userQuests.userId, BigInt(userId)), eq(userQuests.questId, questId) ), with: { quest: true, } }); if (!userQuest) throw new UserError("Quest not assigned"); if (userQuest.completedAt) throw new UserError("Quest already completed"); // Mark completed await txFn.update(userQuests) .set({ completedAt: new Date() }) .where(and( eq(userQuests.userId, BigInt(userId)), eq(userQuests.questId, questId) )); // Distribute Rewards const rewards = userQuest.quest.rewards as { xp?: number, balance?: number }; const results = { xp: 0n, balance: 0n }; if (rewards?.balance) { const bal = BigInt(rewards.balance); await economyService.modifyUserBalance(userId, bal, TransactionType.QUEST_REWARD, `Reward for quest ${questId}`, null, txFn); results.balance = bal; } if (rewards?.xp) { const xp = BigInt(rewards.xp); await levelingService.addXp(userId, xp, txFn); results.xp = xp; } return { success: true, rewards: results }; }, tx); }, getUserQuests: async (userId: string) => { return await DrizzleClient.query.userQuests.findMany({ where: eq(userQuests.userId, BigInt(userId)), with: { quest: true, } }); } };