import { PrismaClient, User } from '@prisma/client'; /** * Creates a Date object for the start of a given day in UTC. */ function getStartOfDay(date: Date): Date { const startOfDay = new Date(date); startOfDay.setUTCHours(0, 0, 0, 0); return startOfDay; } /** * Calculates the user's daily visit streak. * It checks for consecutive daily visits and updates the user's streak count. * This function is idempotent and creates a visit record for the current day. * * @param prisma The Prisma client instance. * @param userId The ID of the user. * @returns The updated User object with the new streak count. */ export async function calculateDailyStreak(prisma: PrismaClient, userId: number): Promise { const today = getStartOfDay(new Date()); const yesterday = getStartOfDay(new Date()); yesterday.setUTCDate(yesterday.getUTCDate() - 1); // 1. Find the user and their most recent visit const [user, lastVisit] = await Promise.all([ prisma.user.findUnique({ where: { id: userId } }), prisma.dailyVisit.findFirst({ where: { userId }, orderBy: { date: 'desc' }, }), ]); if (!user) { throw new Error('User not found'); } let newStreak = user.dailyStreak; // 2. Determine the new streak count if (lastVisit) { const lastVisitDate = getStartOfDay(new Date(lastVisit.date)); if (lastVisitDate.getTime() === today.getTime()) { // Already visited today, streak doesn't change. newStreak = user.dailyStreak; } else if (lastVisitDate.getTime() === yesterday.getTime()) { // Visited yesterday, so increment the streak (capped at 3). newStreak = Math.min(user.dailyStreak + 1, 3); } else { // Missed a day, reset streak to 1. newStreak = 1; } } else { // No previous visits, so this is the first day of the streak. newStreak = 1; } if (newStreak === 0) { newStreak = 1; } // 3. Use upsert to create today's visit record and update the user's streak in a transaction const [, updatedUser] = await prisma.$transaction([ prisma.dailyVisit.upsert({ where: { userId_date: { userId, date: today } }, update: {}, create: { userId, date: today }, }), prisma.user.update({ where: { id: userId }, data: { dailyStreak: newStreak }, }), ]); return updatedUser; } interface Reward { coins: number; exp: number; } /** * Applies a streak-based multiplier to a given reward. * The multiplier is the streak count, capped at 3x. * * @param reward The base reward object { coins, exp }. * @param streak The user's current daily streak. * @returns The new reward object with the multiplier applied. */ export function applyStreakMultiplier(reward: Reward, streak: number | null | undefined): Reward { const effectiveStreak = streak || 0; const multiplier = Math.max(1, Math.min(effectiveStreak, 3)); if (multiplier === 0) { return { coins: reward.coins * 1, exp: reward.exp * 1, }; } return { coins: reward.coins * multiplier, exp: reward.exp * multiplier, }; }