diff --git a/src/routes/api/pokedexes/+server.ts b/src/routes/api/pokedexes/+server.ts index bba10c4..9851d0c 100644 --- a/src/routes/api/pokedexes/+server.ts +++ b/src/routes/api/pokedexes/+server.ts @@ -24,6 +24,8 @@ export const GET = async (event: RequestEvent) => { // POST: Create new pokedex export const POST = async (event: RequestEvent) => { let requestedName: string | undefined; + let createdPokedexId: string | undefined; + let createdPokedexName: string | undefined; try { const userId = await requireAuth(event); const data: Partial = await event.request.json(); @@ -32,9 +34,31 @@ export const POST = async (event: RequestEvent) => { const repo = new PokedexRepository(event.locals.supabase, userId); const pokedex = await repo.create(data); + createdPokedexId = pokedex._id; + createdPokedexName = pokedex.name; // Populate pokedex_entries_mapping table with expected entries - await populatePokedexMappings(event.locals.supabase, pokedex._id, pokedex); + try { + await populatePokedexMappings(event.locals.supabase, pokedex._id, pokedex); + } catch (mappingError) { + // Rollback: delete the partially-initialised pokedex to prevent dangling records + console.error( + `Failed to populate pokedex mappings for pokedex id="${createdPokedexId}" name="${createdPokedexName}":`, + mappingError + ); + try { + await repo.delete(pokedex._id); + console.log( + `Successfully rolled back pokedex id="${createdPokedexId}" name="${createdPokedexName}"` + ); + } catch (deleteError) { + console.error( + `Failed to rollback pokedex id="${createdPokedexId}" name="${createdPokedexName}":`, + deleteError + ); + } + return json({ error: 'Failed to initialise Pokédex. Please try again.' }, { status: 500 }); + } return json(pokedex); } catch (err) { diff --git a/src/routes/api/pokedexes/[id]/+server.ts b/src/routes/api/pokedexes/[id]/+server.ts index 1295442..1e30041 100644 --- a/src/routes/api/pokedexes/[id]/+server.ts +++ b/src/routes/api/pokedexes/[id]/+server.ts @@ -73,9 +73,10 @@ export const PUT = async (event: RequestEvent) => { // Recalculate pokedex_entries_mapping if configuration changed // Only recalculate if isFormDex or gameScope actually changed (these are the only fields that affect mapping) + // Compare the persisted result (pokedex) to existingPokedex to catch changes that repo.update may normalize or default const configChanged = - (data.isFormDex !== undefined && existingPokedex.isFormDex !== data.isFormDex) || - (data.gameScope !== undefined && existingPokedex.gameScope !== data.gameScope); + pokedex.isFormDex !== existingPokedex.isFormDex || + pokedex.gameScope !== existingPokedex.gameScope; if (configChanged) { await recalculatePokedexMappings(event.locals.supabase, id, pokedex); diff --git a/supabase/migrations/20260110180000_create_recalculate_pokedex_mappings_function.sql b/supabase/migrations/20260110180000_create_recalculate_pokedex_mappings_function.sql index d6a9b68..d728040 100644 --- a/supabase/migrations/20260110180000_create_recalculate_pokedex_mappings_function.sql +++ b/supabase/migrations/20260110180000_create_recalculate_pokedex_mappings_function.sql @@ -28,7 +28,7 @@ BEGIN RAISE EXCEPTION 'pokedex not found'; END IF; - IF v_user_id != auth.uid() THEN + IF auth.uid() IS NULL OR v_user_id IS DISTINCT FROM auth.uid() THEN RAISE EXCEPTION 'not authorized: you do not own this pokedex'; END IF; @@ -38,6 +38,7 @@ BEGIN WHERE "pokedexId" = p_pokedex_id; INSERT INTO pokedex_entries_mapping ("pokedexId", "pokedexEntryId") - SELECT p_pokedex_id, unnest(p_entry_ids); + SELECT p_pokedex_id, unnest(p_entry_ids) + ON CONFLICT ("pokedexId", "pokedexEntryId") DO NOTHING; END; $$;