habits.andr33v.ru/server/api/habits/[id]/complete.post.ts

117 lines
3.7 KiB
TypeScript

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<CompletionResponse> => {
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,
};
});