mirror of
https://github.com/pupperpowell/bibdle.git
synced 2026-04-05 17:33:31 -04:00
Possible fix for sign in with apple migrations failing
This commit is contained in:
@@ -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
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="secondary"
|
||||
onclick={seedHistory}
|
||||
disabled={seeding}
|
||||
class="w-full py-4 md:py-2"
|
||||
>
|
||||
{seeding ? "Seeding..." : "Seed 10 Days of History"}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -71,8 +71,7 @@
|
||||
</h2>
|
||||
<p class="text-gray-700 leading-relaxed italic">
|
||||
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.
|
||||
</p>
|
||||
</Container>
|
||||
{:else}
|
||||
|
||||
@@ -413,18 +413,31 @@
|
||||
</div>
|
||||
<div>Daily Verse Date: {dailyVerse.date}</div>
|
||||
</div>
|
||||
<DevButtons />
|
||||
<DevButtons anonymousId={persistence.anonymousId} />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if user && session}
|
||||
<div class="mt-6 pt-4 border-t border-gray-200 text-center text-xs text-gray-400">
|
||||
Signed in as {[user.firstName, user.lastName].filter(Boolean).join(" ")}{user.email ? ` (${user.email})` : ""}{user.appleId ? " using Apple" : ""}
|
||||
<form method="POST" action="/auth/logout" use:enhance class="inline">
|
||||
<div
|
||||
class="mt-6 pt-4 border-t border-gray-200 text-center text-xs text-gray-400"
|
||||
>
|
||||
Signed in as {[user.firstName, user.lastName]
|
||||
.filter(Boolean)
|
||||
.join(" ")}{user.email
|
||||
? ` (${user.email})`
|
||||
: ""}{user.appleId ? " using Apple" : ""} |
|
||||
|
||||
<form
|
||||
method="POST"
|
||||
action="/auth/logout"
|
||||
use:enhance
|
||||
class="inline"
|
||||
>
|
||||
<button
|
||||
type="submit"
|
||||
class="ml-2 underline hover:text-gray-600 transition-colors cursor-pointer"
|
||||
>Sign out</button>
|
||||
>Sign out</button
|
||||
>
|
||||
</form>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
66
src/routes/api/dev/seed-history/+server.ts
Normal file
66
src/routes/api/dev/seed-history/+server.ts
Normal file
@@ -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 });
|
||||
}
|
||||
};
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
@@ -1,25 +1,36 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/state';
|
||||
import AuthModal from '$lib/components/AuthModal.svelte';
|
||||
import { page } from "$app/state";
|
||||
import { browser } from "$app/environment";
|
||||
import AuthModal from "$lib/components/AuthModal.svelte";
|
||||
|
||||
let isOpen = $state(true);
|
||||
const user = $derived(page.data.user);
|
||||
const anonymousId = crypto.randomUUID();
|
||||
let anonymousId = $state("");
|
||||
|
||||
$effect(() => {
|
||||
if (browser) {
|
||||
anonymousId = localStorage.getItem("bibdle-anonymous-id") ?? "";
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="min-h-screen bg-gray-900 flex items-center justify-center p-4">
|
||||
{#if user}
|
||||
<div class="text-white text-center space-y-4">
|
||||
<p class="text-lg">Signed in as <strong>{user.email ?? 'no email'}</strong></p>
|
||||
<p class="text-lg">
|
||||
Signed in as <strong>{user.email ?? "no email"}</strong>
|
||||
</p>
|
||||
<form method="POST" action="/auth/logout">
|
||||
<button class="px-4 py-2 bg-red-600 rounded-md hover:bg-red-700 transition-colors">
|
||||
<button
|
||||
class="px-4 py-2 bg-red-600 rounded-md hover:bg-red-700 transition-colors"
|
||||
>
|
||||
Sign Out
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
{:else}
|
||||
<button
|
||||
onclick={() => isOpen = true}
|
||||
onclick={() => (isOpen = true)}
|
||||
class="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
Open Auth Modal
|
||||
|
||||
Reference in New Issue
Block a user