mirror of
https://github.com/jcreek/LivingDexTracker.git
synced 2026-07-13 02:53:45 +00:00
fix(#64): Address PR comments
This commit is contained in:
@@ -8,6 +8,7 @@ import type { SupabaseClient } from '@supabase/supabase-js';
|
||||
/**
|
||||
* Calculate expected pokedex entries based on pokedex configuration
|
||||
* Applies the same filtering logic as CombinedDataRepository
|
||||
* @throws Error if the Supabase query fails
|
||||
*/
|
||||
async function calculateExpectedEntries(
|
||||
supabase: SupabaseClient,
|
||||
@@ -28,7 +29,7 @@ async function calculateExpectedEntries(
|
||||
.from('region_game_mappings')
|
||||
.select('region')
|
||||
.eq('game', pokedex.gameScope)
|
||||
.single();
|
||||
.maybeSingle();
|
||||
|
||||
if (regionData?.region) {
|
||||
query = query.eq('regionToCatchIn', regionData.region);
|
||||
@@ -44,7 +45,7 @@ async function calculateExpectedEntries(
|
||||
|
||||
if (error) {
|
||||
console.error('Error calculating expected entries:', error);
|
||||
return [];
|
||||
throw new Error(`Failed to calculate expected entries: ${error.message}`);
|
||||
}
|
||||
|
||||
return data?.map((entry) => entry.id) || [];
|
||||
@@ -52,6 +53,8 @@ async function calculateExpectedEntries(
|
||||
|
||||
/**
|
||||
* Populate pokedex_entries_mapping table for a pokedex
|
||||
* Uses chunked upsert to avoid UNIQUE constraint violations and request size limits
|
||||
* @throws Error if the Supabase upsert fails
|
||||
*/
|
||||
async function populatePokedexMappings(
|
||||
supabase: SupabaseClient,
|
||||
@@ -70,11 +73,19 @@ async function populatePokedexMappings(
|
||||
pokedexEntryId
|
||||
}));
|
||||
|
||||
const { error } = await supabase.from('pokedex_entries_mapping').insert(mappings);
|
||||
// Process in chunks to avoid request size limits
|
||||
const CHUNK_SIZE = 500;
|
||||
for (let i = 0; i < mappings.length; i += CHUNK_SIZE) {
|
||||
const chunk = mappings.slice(i, i + CHUNK_SIZE);
|
||||
const { error } = await supabase.from('pokedex_entries_mapping').upsert(chunk, {
|
||||
onConflict: 'pokedexId,pokedexEntryId',
|
||||
ignoreDuplicates: true
|
||||
});
|
||||
|
||||
if (error) {
|
||||
console.error('Error populating pokedex mappings:', error);
|
||||
throw new Error(`Failed to populate pokedex mappings: ${error.message}`);
|
||||
if (error) {
|
||||
console.error('Error populating pokedex mappings:', error);
|
||||
throw new Error(`Failed to populate pokedex mappings: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import type { SupabaseClient } from '@supabase/supabase-js';
|
||||
/**
|
||||
* Calculate expected pokedex entries based on pokedex configuration
|
||||
* Applies the same filtering logic as CombinedDataRepository
|
||||
* @throws Error if the Supabase query fails
|
||||
*/
|
||||
async function calculateExpectedEntries(
|
||||
supabase: SupabaseClient,
|
||||
@@ -28,7 +29,7 @@ async function calculateExpectedEntries(
|
||||
.from('region_game_mappings')
|
||||
.select('region')
|
||||
.eq('game', pokedex.gameScope)
|
||||
.single();
|
||||
.maybeSingle();
|
||||
|
||||
if (regionData?.region) {
|
||||
query = query.eq('regionToCatchIn', regionData.region);
|
||||
@@ -44,16 +45,17 @@ async function calculateExpectedEntries(
|
||||
|
||||
if (error) {
|
||||
console.error('Error calculating expected entries:', error);
|
||||
return [];
|
||||
throw new Error(`Failed to calculate expected entries: ${error.message}`);
|
||||
}
|
||||
|
||||
return data?.map((entry) => entry.id) || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate pokedex_entries_mapping table for a pokedex
|
||||
* Recalculate pokedex_entries_mapping table for a pokedex (used on update)
|
||||
* Uses a single atomic RPC call to prevent orphaned pokedexes
|
||||
*/
|
||||
async function populatePokedexMappings(
|
||||
async function recalculatePokedexMappings(
|
||||
supabase: SupabaseClient,
|
||||
pokedexId: string,
|
||||
pokedex: Pokedex
|
||||
@@ -65,42 +67,18 @@ async function populatePokedexMappings(
|
||||
return;
|
||||
}
|
||||
|
||||
const mappings = expectedEntryIds.map((pokedexEntryId) => ({
|
||||
pokedexId,
|
||||
pokedexEntryId
|
||||
}));
|
||||
|
||||
const { error } = await supabase.from('pokedex_entries_mapping').insert(mappings);
|
||||
// Call the atomic RPC function to delete old mappings and insert new ones
|
||||
const { error } = await supabase.rpc('recalculate_pokedex_mappings', {
|
||||
p_pokedex_id: pokedexId,
|
||||
p_entry_ids: expectedEntryIds
|
||||
});
|
||||
|
||||
if (error) {
|
||||
console.error('Error populating pokedex mappings:', error);
|
||||
throw new Error(`Failed to populate pokedex mappings: ${error.message}`);
|
||||
console.error('Error recalculating pokedex mappings:', error);
|
||||
throw new Error(`Failed to recalculate pokedex mappings: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recalculate pokedex_entries_mapping table for a pokedex (used on update)
|
||||
*/
|
||||
async function recalculatePokedexMappings(
|
||||
supabase: SupabaseClient,
|
||||
pokedexId: string,
|
||||
pokedex: Pokedex
|
||||
): Promise<void> {
|
||||
// Delete old mappings
|
||||
const { error: deleteError } = await supabase
|
||||
.from('pokedex_entries_mapping')
|
||||
.delete()
|
||||
.eq('pokedexId', pokedexId);
|
||||
|
||||
if (deleteError) {
|
||||
console.error('Error deleting old pokedex mappings:', deleteError);
|
||||
throw new Error(`Failed to delete old pokedex mappings: ${deleteError.message}`);
|
||||
}
|
||||
|
||||
// Populate new mappings
|
||||
await populatePokedexMappings(supabase, pokedexId, pokedex);
|
||||
}
|
||||
|
||||
// GET: Get single pokedex by ID
|
||||
export const GET = async (event: RequestEvent) => {
|
||||
try {
|
||||
@@ -154,6 +132,13 @@ export const PUT = async (event: RequestEvent) => {
|
||||
}
|
||||
|
||||
const repo = new PokedexRepository(event.locals.supabase, userId);
|
||||
|
||||
// Fetch the existing pokedex before update to compare values
|
||||
const existingPokedex = await repo.findById(id);
|
||||
if (!existingPokedex) {
|
||||
return json({ error: 'Pokedex not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
const pokedex = await repo.update(id, data);
|
||||
|
||||
if (!pokedex) {
|
||||
@@ -161,16 +146,16 @@ export const PUT = async (event: RequestEvent) => {
|
||||
}
|
||||
|
||||
// Recalculate pokedex_entries_mapping if configuration changed
|
||||
// Only recalculate if type flags or gameScope changed
|
||||
// Only recalculate if type flags or gameScope actually changed
|
||||
const configChanged =
|
||||
data.isFormDex !== undefined ||
|
||||
data.gameScope !== undefined ||
|
||||
(data.isLivingDex !== undefined && pokedex.isLivingDex !== data.isLivingDex) ||
|
||||
(data.isShinyDex !== undefined && pokedex.isShinyDex !== data.isShinyDex) ||
|
||||
(data.isOriginDex !== undefined && pokedex.isOriginDex !== data.isOriginDex);
|
||||
(data.isFormDex !== undefined && existingPokedex.isFormDex !== data.isFormDex) ||
|
||||
(data.gameScope !== undefined && existingPokedex.gameScope !== data.gameScope) ||
|
||||
(data.isLivingDex !== undefined && existingPokedex.isLivingDex !== data.isLivingDex) ||
|
||||
(data.isShinyDex !== undefined && existingPokedex.isShinyDex !== data.isShinyDex) ||
|
||||
(data.isOriginDex !== undefined && existingPokedex.isOriginDex !== data.isOriginDex);
|
||||
|
||||
if (configChanged) {
|
||||
await recalculatePokedexMappings(event.locals.supabase, id, { ...pokedex, ...data });
|
||||
await recalculatePokedexMappings(event.locals.supabase, id, pokedex);
|
||||
}
|
||||
|
||||
return json(pokedex);
|
||||
|
||||
Reference in New Issue
Block a user