diff --git a/src/lib/components/DevButtons.svelte b/src/lib/components/DevButtons.svelte index 15210c1..8770feb 100644 --- a/src/lib/components/DevButtons.svelte +++ b/src/lib/components/DevButtons.svelte @@ -2,6 +2,30 @@ import { browser } from "$app/environment"; import Button from "$lib/components/Button.svelte"; + let { anonymousId }: { anonymousId: string | null } = $props(); + + let seeding = $state(false); + + async function seedHistory() { + if (!browser || !anonymousId || seeding) return; + seeding = true; + try { + const response = await fetch("/api/dev/seed-history", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ anonymousId }) + }); + const result = await response.json(); + alert( + `Seeded! Inserted: ${result.inserted?.join(", ")}. Skipped (already exist): ${result.skipped?.join(", ") || "none"}` + ); + } catch { + alert("Failed to seed history"); + } finally { + seeding = false; + } + } + function clearLocalStorage() { if (!browser) return; // Clear all bibdle-related localStorage items @@ -86,4 +110,13 @@ > Clear LocalStorage + + diff --git a/src/lib/components/GuessesTable.svelte b/src/lib/components/GuessesTable.svelte index a5103e1..b1d36a3 100644 --- a/src/lib/components/GuessesTable.svelte +++ b/src/lib/components/GuessesTable.svelte @@ -71,8 +71,7 @@

Guess what book of the bible you think the verse is from. You will - get clues to tell you if your guess is close or not. Green means the - category is correct; red means wrong. + get clues to help you after each guess.

{:else} diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index abb2039..142e097 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -413,18 +413,31 @@
Daily Verse Date: {dailyVerse.date}
- + {/if} {#if user && session} -
- Signed in as {[user.firstName, user.lastName].filter(Boolean).join(" ")}{user.email ? ` (${user.email})` : ""}{user.appleId ? " using Apple" : ""} -
+
+ Signed in as {[user.firstName, user.lastName] + .filter(Boolean) + .join(" ")}{user.email + ? ` (${user.email})` + : ""}{user.appleId ? " using Apple" : ""} | + + + >Sign out
{/if} diff --git a/src/routes/api/dev/seed-history/+server.ts b/src/routes/api/dev/seed-history/+server.ts new file mode 100644 index 0000000..3ef1a14 --- /dev/null +++ b/src/routes/api/dev/seed-history/+server.ts @@ -0,0 +1,66 @@ +import type { RequestHandler } from './$types'; +import { db } from '$lib/server/db'; +import { dailyCompletions } from '$lib/server/db/schema'; +import { json } from '@sveltejs/kit'; +import crypto from 'node:crypto'; + +const DEV_HOSTS = ['localhost:5173', 'test.bibdle.com']; + +// A spread of book IDs to use as fake guesses +const SAMPLE_BOOK_IDS = [ + 'GEN', 'EXO', 'PSA', 'PRO', 'ISA', 'JER', 'MAT', 'MRK', 'LUK', 'JHN', + 'ROM', 'GAL', 'EPH', 'PHP', 'REV', 'ACT', 'HEB', 'JAS', '1CO', '2CO', +]; + +export const POST: RequestHandler = async ({ request }) => { + const host = request.headers.get('host') ?? ''; + if (!DEV_HOSTS.includes(host)) { + return json({ error: 'Not allowed in production' }, { status: 403 }); + } + + try { + const { anonymousId } = await request.json(); + + if (!anonymousId || typeof anonymousId !== 'string') { + return json({ error: 'anonymousId required' }, { status: 400 }); + } + + const today = new Date(); + const inserted: string[] = []; + const skipped: string[] = []; + + for (let i = 1; i <= 10; i++) { + const d = new Date(today); + d.setDate(d.getDate() - i); + const date = d.toLocaleDateString('en-CA'); // YYYY-MM-DD + + const guessCount = Math.floor(Math.random() * 6) + 1; // 1-6 guesses + // Pick `guessCount` random books (last one is the "correct" answer) + const shuffled = [...SAMPLE_BOOK_IDS].sort(() => Math.random() - 0.5); + const guesses = shuffled.slice(0, guessCount); + + try { + await db.insert(dailyCompletions).values({ + id: crypto.randomUUID(), + anonymousId, + date, + guessCount, + guesses: JSON.stringify(guesses), + completedAt: new Date(d.getTime() + 12 * 60 * 60 * 1000), // noon on that day + }); + inserted.push(date); + } catch (err: any) { + if (err?.code === 'SQLITE_CONSTRAINT_UNIQUE' || err?.message?.includes('UNIQUE')) { + skipped.push(date); + } else { + throw err; + } + } + } + + return json({ success: true, inserted, skipped }); + } catch (err) { + console.error('Error seeding history:', err); + return json({ error: 'Failed to seed history' }, { status: 500 }); + } +}; diff --git a/src/routes/auth/apple/callback/+server.ts b/src/routes/auth/apple/callback/+server.ts index 78f1b08..a7c7f94 100644 --- a/src/routes/auth/apple/callback/+server.ts +++ b/src/routes/auth/apple/callback/+server.ts @@ -25,6 +25,12 @@ export const POST: RequestHandler = async ({ request, cookies }) => { } cookies.delete('apple_oauth_state', { path: '/' }); + const anonId = stored.anonymousId; + if (!anonId) { + console.error('[Apple auth] Missing anonymousId in state cookie'); + throw error(400, 'Missing anonymous ID — please return to the game and try again'); + } + // Exchange authorization code for tokens const tokens = await exchangeAppleCode(code, `${publicEnv.PUBLIC_SITE_URL}/auth/apple/callback`); const claims = decodeAppleIdToken(tokens.id_token); @@ -51,7 +57,8 @@ export const POST: RequestHandler = async ({ request, cookies }) => { if (existingAppleUser) { userId = existingAppleUser.id; - await auth.migrateAnonymousStats(stored.anonymousId, userId); + console.log(`[Apple auth] Returning Apple user: userId=${userId}, anonId=${anonId}`); + await auth.migrateAnonymousStats(anonId, userId); } else if (claims.email) { // 2. Check if email matches an existing email/password user const existingEmailUser = await auth.getUserByEmail(claims.email); @@ -59,10 +66,12 @@ export const POST: RequestHandler = async ({ request, cookies }) => { // Link Apple account to existing user await db.update(userTable).set({ appleId }).where(eq(userTable.id, existingEmailUser.id)); userId = existingEmailUser.id; - await auth.migrateAnonymousStats(stored.anonymousId, userId); + console.log(`[Apple auth] Linked Apple to existing email user: userId=${userId}, anonId=${anonId}`); + await auth.migrateAnonymousStats(anonId, userId); } else { // 3. Brand new user — use anonymousId as user ID to preserve local stats - userId = stored.anonymousId || crypto.randomUUID(); + userId = anonId; + console.log(`[Apple auth] New user (has email): userId=${userId}`); try { await db.insert(userTable).values({ id: userId, @@ -79,6 +88,8 @@ export const POST: RequestHandler = async ({ request, cookies }) => { const retryUser = await auth.getUserByAppleId(appleId); if (retryUser) { userId = retryUser.id; + console.log(`[Apple auth] Race condition (has email): resolved to userId=${userId}, anonId=${anonId}`); + await auth.migrateAnonymousStats(anonId, userId); } else { throw error(500, 'Failed to create user'); } @@ -89,7 +100,8 @@ export const POST: RequestHandler = async ({ request, cookies }) => { } } else { // No email from Apple — create account with appleId only - userId = stored.anonymousId || crypto.randomUUID(); + userId = anonId; + console.log(`[Apple auth] New user (no email): userId=${userId}`); try { await db.insert(userTable).values({ id: userId, @@ -105,6 +117,8 @@ export const POST: RequestHandler = async ({ request, cookies }) => { const retryUser = await auth.getUserByAppleId(appleId); if (retryUser) { userId = retryUser.id; + console.log(`[Apple auth] Race condition (no email): resolved to userId=${userId}, anonId=${anonId}`); + await auth.migrateAnonymousStats(anonId, userId); } else { throw error(500, 'Failed to create user'); } diff --git a/src/routes/auth/apple/test/+page.svelte b/src/routes/auth/apple/test/+page.svelte index e3f6d31..cdd02fd 100644 --- a/src/routes/auth/apple/test/+page.svelte +++ b/src/routes/auth/apple/test/+page.svelte @@ -1,25 +1,36 @@
{#if user}
-

Signed in as {user.email ?? 'no email'}

+

+ Signed in as {user.email ?? "no email"} +

-
{:else}