109 lines
3.2 KiB
TypeScript
109 lines
3.2 KiB
TypeScript
import { getUserIdFromSession } from '../../../utils/auth';
|
|
import { REWARDS } from '../../../utils/economy';
|
|
import prisma from '../../../utils/prisma';
|
|
import { applyStreakMultiplier } from '../../../utils/streak';
|
|
|
|
interface CompletionResponse {
|
|
message: string;
|
|
reward: {
|
|
coins: number;
|
|
exp: number;
|
|
};
|
|
updatedCoins: number;
|
|
updatedExp: number;
|
|
}
|
|
|
|
// Helper to get the start of the day in UTC
|
|
function getStartOfDay(date: Date): Date {
|
|
const d = new Date(date);
|
|
d.setUTCHours(0, 0, 0, 0);
|
|
return d;
|
|
}
|
|
|
|
export default defineEventHandler(async (event): Promise<CompletionResponse> => {
|
|
const userId = await getUserIdFromSession(event);
|
|
const habitId = parseInt(event.context.params?.id || '', 10);
|
|
|
|
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 today = new Date();
|
|
const jsDayOfWeek = today.getDay(); // 0 (Sunday) to 6 (Saturday)
|
|
|
|
// Convert JS day (Sun=0) to the application's convention (Mon=0, Sun=6)
|
|
const appDayOfWeek = (jsDayOfWeek === 0) ? 6 : jsDayOfWeek - 1;
|
|
|
|
if (!(habit.daysOfWeek as number[]).includes(appDayOfWeek)) {
|
|
throw createError({ statusCode: 400, statusMessage: 'Habit is not active today.' });
|
|
}
|
|
|
|
const startOfToday = getStartOfDay(today);
|
|
|
|
const existingCompletion = await prisma.habitCompletion.findFirst({
|
|
where: {
|
|
habitId: habitId,
|
|
date: startOfToday,
|
|
},
|
|
});
|
|
|
|
if (existingCompletion) {
|
|
throw createError({ statusCode: 409, statusMessage: 'Habit already completed today.' });
|
|
}
|
|
|
|
// Apply the streak multiplier to the base reward
|
|
const baseReward = REWARDS.HABITS.COMPLETION;
|
|
const finalReward = applyStreakMultiplier(baseReward, user.dailyStreak);
|
|
|
|
const village = await prisma.village.findUnique({ where: { userId } });
|
|
|
|
const [, updatedUser] = await prisma.$transaction([
|
|
prisma.habitCompletion.create({
|
|
data: {
|
|
habitId: habitId,
|
|
date: startOfToday,
|
|
},
|
|
}),
|
|
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}) ${user.dailyStreak === 2 ? 'удвоила' : 'утроила'} награду!` : ''}`,
|
|
coins: finalReward.coins,
|
|
exp: finalReward.exp,
|
|
}
|
|
})] : []),
|
|
]);
|
|
|
|
return {
|
|
message: 'Habit completed successfully!',
|
|
reward: finalReward,
|
|
updatedCoins: updatedUser.coins,
|
|
updatedExp: updatedUser.exp,
|
|
};
|
|
});
|