109 lines
3.0 KiB
TypeScript
109 lines
3.0 KiB
TypeScript
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<User> {
|
|
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,
|
|
};
|
|
}
|