import { PrismaClient, User } from '@prisma/client'; import { getPreviousGameDay } from '~/server/utils/gameDay'; /** * Calculates the user's daily visit streak based on a client-provided "Game Day". * It checks for consecutive daily visits and updates the user's streak count. * This function is idempotent and creates a visit record for the provided day. * * @param prisma The Prisma client instance. * @param userId The ID of the user. * @param gameDay The client's current day in "YYYY-MM-DD" format. * @returns The updated User object with the new streak count. */ export async function calculateDailyStreak(db: PrismaClient | Prisma.TransactionClient, userId: number, gameDay: string): Promise { const yesterdayGameDay = getPreviousGameDay(gameDay); // 1. Find the user and their most recent visit const [user, lastVisit] = await Promise.all([ db.user.findUnique({ where: { id: userId } }), db.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) { if (lastVisit.date === gameDay) { // Already visited today, streak doesn't change. newStreak = user.dailyStreak; } else if (lastVisit.date === yesterdayGameDay) { // 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. Upsert today's visit record. await db.dailyVisit.upsert({ where: { userId_date: { userId, date: gameDay } }, update: {}, create: { userId, date: gameDay }, }); // 4. Update the user's streak. const updatedUser = await db.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. A streak of 0 is treated as 1x. * * @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 || 1; // Treat a null/0 streak as 1x const multiplier = Math.max(1, Math.min(effectiveStreak, 3)); return { coins: reward.coins * multiplier, exp: reward.exp * multiplier, }; }