import { db } from '../../db/client.js'; import { users } from '../../db/schema.js'; import { eq, sql } from 'drizzle-orm'; export interface StreakInfo { days: number; lastDate: string | null; frozen: boolean; } /** * Normalize a date value (Date or string from mysql2) to 'YYYY-MM-DD' string. */ function toDateString(value: Date | string | null): string | null { if (!value) return null; if (typeof value === 'string') return value.slice(0, 10); return value.toISOString().slice(0, 10); } /** * Get the user's current streak info. * All date comparisons use UTC date strings (YYYY-MM-DD). */ export async function calculateStreak(userId: string): Promise { const [user] = await db .select({ streakDays: users.streakDays, streakLastDate: users.streakLastDate, }) .from(users) .where(eq(users.id, userId)) .limit(1); if (!user || !user.streakLastDate) { return { days: 0, lastDate: null, frozen: false }; } const today = todayUtc(); const yesterday = yesterdayUtc(); const lastDate = toDateString(user.streakLastDate); if (lastDate === today) { return { days: user.streakDays ?? 0, lastDate, frozen: false }; } if (lastDate === yesterday) { return { days: user.streakDays ?? 0, lastDate, frozen: false }; } // Streak is broken return { days: 0, lastDate, frozen: false }; } export async function updateStreakForCompletedChallenge(userId: string): Promise { const today = todayUtc(); const [user] = await db .select({ streakDays: users.streakDays, streakLastDate: users.streakLastDate, }) .from(users) .where(eq(users.id, userId)) .limit(1); if (!user) { return { days: 0, lastDate: null, frozen: false }; } const lastDate = toDateString(user.streakLastDate); // Already updated streak today if (lastDate === today) { return { days: user.streakDays ?? 0, lastDate: today, frozen: false }; } const yesterday = yesterdayUtc(); const isConsecutive = lastDate === yesterday; const newDays = isConsecutive ? (user.streakDays ?? 0) + 1 : 1; await db .update(users) .set({ streakDays: newDays, streakLastDate: sql`CAST(${today} AS DATE)` }) .where(eq(users.id, userId)); return { days: newDays, lastDate: today, frozen: false }; } /** * Freeze the streak (set last date to today without incrementing). */ export async function freezeStreak(userId: string): Promise { const today = todayUtc(); await db .update(users) .set({ streakLastDate: sql`CAST(${today} AS DATE)` }) .where(eq(users.id, userId)); const [user] = await db .select({ streakDays: users.streakDays }) .from(users) .where(eq(users.id, userId)) .limit(1); return { days: user?.streakDays ?? 0, lastDate: today, frozen: true }; } function todayUtc(): string { return new Date().toISOString().slice(0, 10); } function yesterdayUtc(): string { const d = new Date(); d.setDate(d.getDate() - 1); return d.toISOString().slice(0, 10); }