import { getAuthenticatedUserId } from '../../../utils/auth'; import { REWARDS } from '../../../utils/economy'; import prisma from '../../../utils/prisma'; import { applyStreakMultiplier } from '../../../utils/streak'; import { getDayOfWeekFromGameDay } from '~/server/utils/gameDay'; interface CompletionResponse { message: string; reward: { coins: number; exp: number; }; updatedCoins: number; updatedExp: number; } export default defineEventHandler(async (event): Promise => { const userId = getAuthenticatedUserId(event); const habitId = parseInt(event.context.params?.id || '', 10); const body = await readBody(event); const gameDay: string = body.gameDay; // Expecting "YYYY-MM-DD" if (!gameDay || !/^\d{4}-\d{2}-\d{2}$/.test(gameDay)) { throw createError({ statusCode: 400, statusMessage: 'Invalid or missing gameDay property in request body. Expected YYYY-MM-DD.' }); } if (isNaN(habitId)) { throw createError({ statusCode: 400, statusMessage: 'Invalid habit ID.' }); } // Fetch user and habit in parallel const [user, habit] = await Promise.all([ prisma.user.findUnique({ where: { id: userId } }), prisma.habit.findFirst({ where: { id: habitId, userId } }) ]); if (!user) { throw createError({ statusCode: 401, statusMessage: 'User not found.' }); } if (!habit) { throw createError({ statusCode: 404, statusMessage: 'Habit not found.' }); } const appDayOfWeek = getDayOfWeekFromGameDay(gameDay); // For permanent users, ensure the habit is scheduled for today. // Anonymous users in the onboarding flow can complete it on any day. if (!user.isAnonymous) { if (!(habit.daysOfWeek as number[]).includes(appDayOfWeek)) { throw createError({ statusCode: 400, statusMessage: 'Habit is not active today.' }); } } const existingCompletion = await prisma.habitCompletion.findFirst({ where: { habitId: habitId, date: gameDay, }, }); if (existingCompletion) { throw createError({ statusCode: 409, statusMessage: 'Habit already completed today.' }); } // Determine the reward based on user type let finalReward: { coins: number, exp: number }; if (user.isAnonymous) { // Anonymous users in onboarding get a fixed reward from economy.ts finalReward = REWARDS.HABITS.ONBOARDING_COMPLETION; } else { // Permanent users get rewards based on streak // Streak defaults to 1 for multiplier if it's 0 or null const currentDailyStreak = user.dailyStreak && user.dailyStreak > 0 ? user.dailyStreak : 1; const baseReward = REWARDS.HABITS.COMPLETION; finalReward = applyStreakMultiplier(baseReward, currentDailyStreak); } const village = await prisma.village.findUnique({ where: { userId } }); const [, updatedUser] = await prisma.$transaction([ prisma.habitCompletion.create({ data: { habitId: habitId, date: gameDay, }, }), prisma.user.update({ where: { id: userId }, data: { coins: { increment: finalReward.coins, }, exp: { increment: finalReward.exp, }, }, }), ...(village ? [prisma.villageEvent.create({ data: { villageId: village.id, type: 'HABIT_COMPLETION', message: `Привычка "${habit.name}" выполнена, принеся вам ${finalReward.coins} монет и ${finalReward.exp} опыта.${user.dailyStreak > 1 ? ` Ваша серия визитов (x${user.dailyStreak}) увеличила награду!` : ''}`, coins: finalReward.coins, exp: finalReward.exp, } })] : []), ]); return { message: 'Habit completed successfully!', reward: finalReward, updatedCoins: updatedUser.coins, updatedExp: updatedUser.exp, }; });