habits.andr33v.ru/server/utils/streak.ts

91 lines
2.7 KiB
TypeScript

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<User> {
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,
};
}