import { db } from '$lib/server/db'; import { dailyCompletions, type DailyCompletion } from '$lib/server/db/schema'; import { eq, desc } from 'drizzle-orm'; import type { PageServerLoad } from './$types'; export const load: PageServerLoad = async ({ url, locals }) => { // Check if user is authenticated if (!locals.user) { return { stats: null, error: null, user: null, session: null, requiresAuth: true }; } const userId = locals.user.id; if (!userId) { return { stats: null, error: 'No user ID provided', user: locals.user, session: locals.session }; } try { // Get all completions for this user const completions = await db .select() .from(dailyCompletions) .where(eq(dailyCompletions.anonymousId, userId)) .orderBy(desc(dailyCompletions.date)); if (completions.length === 0) { return { stats: { totalSolves: 0, avgGuesses: 0, gradeDistribution: { 'S++': 0, 'S+': 0, 'A+': 0, 'A': 0, 'B+': 0, 'B': 0, 'C+': 0, 'C': 0 }, currentStreak: 0, bestStreak: 0, recentCompletions: [] }, user: locals.user, session: locals.session }; } // Calculate basic stats const totalSolves = completions.length; const totalGuesses = completions.reduce((sum: number, c: DailyCompletion) => sum + c.guessCount, 0); const avgGuesses = Math.round((totalGuesses / totalSolves) * 100) / 100; // Calculate grade distribution const gradeDistribution = { 'S++': 0, // This will be calculated differently since we don't store chapter correctness 'S+': completions.filter((c: DailyCompletion) => c.guessCount === 1).length, 'A+': completions.filter((c: DailyCompletion) => c.guessCount === 2).length, 'A': completions.filter((c: DailyCompletion) => c.guessCount === 3).length, 'B+': completions.filter((c: DailyCompletion) => c.guessCount >= 4 && c.guessCount <= 6).length, 'B': completions.filter((c: DailyCompletion) => c.guessCount >= 7 && c.guessCount <= 10).length, 'C+': completions.filter((c: DailyCompletion) => c.guessCount >= 11 && c.guessCount <= 15).length, 'C': completions.filter((c: DailyCompletion) => c.guessCount > 15).length }; // Calculate streaks const sortedDates = completions .map((c: DailyCompletion) => c.date) .sort(); let currentStreak = 0; let bestStreak = 0; let tempStreak = 1; if (sortedDates.length > 0) { // Check if current streak is active (includes today or yesterday) const today = new Date().toISOString().split('T')[0]; const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString().split('T')[0]; const lastPlayedDate = sortedDates[sortedDates.length - 1]; if (lastPlayedDate === today || lastPlayedDate === yesterday) { currentStreak = 1; // Count backwards from the most recent date for (let i = sortedDates.length - 2; i >= 0; i--) { const currentDate = new Date(sortedDates[i + 1]); const prevDate = new Date(sortedDates[i]); const daysDiff = Math.floor((currentDate.getTime() - prevDate.getTime()) / (1000 * 60 * 60 * 24)); if (daysDiff === 1) { currentStreak++; } else { break; } } } // Calculate best streak bestStreak = 1; for (let i = 1; i < sortedDates.length; i++) { const currentDate = new Date(sortedDates[i]); const prevDate = new Date(sortedDates[i - 1]); const daysDiff = Math.floor((currentDate.getTime() - prevDate.getTime()) / (1000 * 60 * 60 * 24)); if (daysDiff === 1) { tempStreak++; } else { bestStreak = Math.max(bestStreak, tempStreak); tempStreak = 1; } } bestStreak = Math.max(bestStreak, tempStreak); } // Get recent completions (last 7 days) const recentCompletions = completions .slice(0, 7) .map((c: DailyCompletion) => ({ date: c.date, guessCount: c.guessCount, grade: getGradeFromGuesses(c.guessCount) })); return { stats: { totalSolves, avgGuesses, gradeDistribution, currentStreak, bestStreak, recentCompletions }, user: locals.user, session: locals.session }; } catch (error) { console.error('Error fetching user stats:', error); return { stats: null, error: 'Failed to fetch stats', user: locals.user, session: locals.session }; } }; function getGradeFromGuesses(guessCount: number): string { if (guessCount === 1) return "S+"; if (guessCount === 2) return "A+"; if (guessCount === 3) return "A"; if (guessCount >= 4 && guessCount <= 6) return "B+"; if (guessCount >= 7 && guessCount <= 10) return "B"; if (guessCount >= 11 && guessCount <= 15) return "C+"; return "C"; }