import type { RequestHandler } from './$types'; import { db } from '$lib/server/db'; import { dailyCompletions } from '$lib/server/db/schema'; import { and, eq, asc } from 'drizzle-orm'; import { json } from '@sveltejs/kit'; import crypto from 'node:crypto'; export const POST: RequestHandler = async ({ request }) => { try { const { anonymousId, date, guessCount } = await request.json(); // Validation if (!anonymousId || !date || typeof guessCount !== 'number' || guessCount < 1) { return json({ error: 'Invalid data' }, { status: 400 }); } const completedAt = new Date(); try { // Insert with duplicate prevention await db.insert(dailyCompletions).values({ id: crypto.randomUUID(), anonymousId, date, guessCount, completedAt, }); } catch (err: any) { if (err?.code === 'SQLITE_CONSTRAINT_UNIQUE' || err?.message?.includes('UNIQUE')) { return json({ error: 'Already submitted' }, { status: 409 }); } throw err; } // Calculate statistics const allCompletions = await db .select() .from(dailyCompletions) .where(eq(dailyCompletions.date, date)) .orderBy(asc(dailyCompletions.completedAt)); const totalSolves = allCompletions.length; // Solve rank: position in time-ordered list const solveRank = allCompletions.findIndex(c => c.anonymousId === anonymousId) + 1; // Guess rank: count how many DISTINCT guess counts are better (grouped ranking) const uniqueBetterGuessCounts = new Set( allCompletions.filter(c => c.guessCount < guessCount).map(c => c.guessCount) ); const guessRank = uniqueBetterGuessCounts.size + 1; // Count ties: how many have the SAME guessCount (excluding self) const tiedCount = allCompletions.filter(c => c.guessCount === guessCount && c.anonymousId !== anonymousId).length; // Average guesses const totalGuesses = allCompletions.reduce((sum, c) => sum + c.guessCount, 0); const averageGuesses = Math.round((totalGuesses / totalSolves) * 10) / 10; // Percentile: what percentage of people you beat (100 - your rank percentage) const betterOrEqualCount = allCompletions.filter(c => c.guessCount <= guessCount).length; const percentile = Math.round((betterOrEqualCount / totalSolves) * 100); return json({ success: true, stats: { solveRank, guessRank, totalSolves, averageGuesses, tiedCount, percentile } }); } catch (err) { console.error('Error submitting completion:', err); return json({ error: 'Failed to submit completion' }, { status: 500 }); } }; export const GET: RequestHandler = async ({ url }) => { try { const anonymousId = url.searchParams.get('anonymousId'); const date = url.searchParams.get('date'); if (!anonymousId || !date) { return json({ error: 'Invalid data' }, { status: 400 }); } const userCompletions = await db .select() .from(dailyCompletions) .where(and( eq(dailyCompletions.anonymousId, anonymousId), eq(dailyCompletions.date, date) )) .limit(1); if (userCompletions.length === 0) { return json({ error: 'No completion found' }, { status: 404 }); } const userCompletion = userCompletions[0]; const guessCount = userCompletion.guessCount; // Calculate statistics const allCompletions = await db .select() .from(dailyCompletions) .where(eq(dailyCompletions.date, date)) .orderBy(asc(dailyCompletions.completedAt)); const totalSolves = allCompletions.length; // Solve rank: position in time-ordered list const solveRank = allCompletions.findIndex(c => c.anonymousId === anonymousId) + 1; // Guess rank: count how many DISTINCT guess counts are better (grouped ranking) const uniqueBetterGuessCounts = new Set( allCompletions.filter(c => c.guessCount < guessCount).map(c => c.guessCount) ); const guessRank = uniqueBetterGuessCounts.size + 1; // Count ties: how many have the SAME guessCount (excluding self) const tiedCount = allCompletions.filter(c => c.guessCount === guessCount && c.anonymousId !== anonymousId).length; // Average guesses const totalGuesses = allCompletions.reduce((sum, c) => sum + c.guessCount, 0); const averageGuesses = Math.round((totalGuesses / totalSolves) * 10) / 10; // Percentile: what percentage of people you beat (100 - your rank percentage) const betterOrEqualCount = allCompletions.filter(c => c.guessCount <= guessCount).length; const percentile = Math.round((betterOrEqualCount / totalSolves) * 100); return json({ success: true, stats: { solveRank, guessRank, totalSolves, averageGuesses, tiedCount, percentile } }); } catch (err) { console.error('Error fetching stats:', err); return json({ error: 'Failed to fetch stats' }, { status: 500 }); } };