mirror of
https://github.com/jcreek/LivingDexTracker.git
synced 2026-07-12 18:43:45 +00:00
123 lines
3.8 KiB
TypeScript
123 lines
3.8 KiB
TypeScript
import type { Pokedex } from '$lib/models/Pokedex';
|
|
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
|
|
*/
|
|
export async function calculateExpectedEntries(
|
|
supabase: SupabaseClient,
|
|
pokedex: Pokedex
|
|
): Promise<number[]> {
|
|
let query = supabase.from('pokedex_entries').select('id');
|
|
|
|
// Apply form filter: if isFormDex is false, only include base forms
|
|
// Base forms have form IS NULL, except Unown which has no base form (use 'A')
|
|
if (!pokedex.isFormDex) {
|
|
query = query.or('form.is.null,and(pokemon.eq.Unown,form.eq.A)');
|
|
}
|
|
|
|
// Apply region filter: if gameScope is specified, filter by region
|
|
if (pokedex.gameScope) {
|
|
// Determine region from gameScope using region_game_mappings
|
|
const { data: regionData, error: regionError } = await supabase
|
|
.from('region_game_mappings')
|
|
.select('region')
|
|
.eq('game', pokedex.gameScope)
|
|
.maybeSingle();
|
|
|
|
if (regionError) {
|
|
console.error('Error looking up region for gameScope:', pokedex.gameScope, regionError);
|
|
throw new Error(
|
|
`Failed to lookup region for gameScope "${pokedex.gameScope}": ${regionError.message}`
|
|
);
|
|
}
|
|
|
|
if (regionData?.region) {
|
|
query = query.eq('regionToCatchIn', regionData.region);
|
|
}
|
|
}
|
|
|
|
// Apply game filter: if gameScope is specified, filter by gamesToCatchIn array
|
|
if (pokedex.gameScope) {
|
|
query = query.contains('gamesToCatchIn', [pokedex.gameScope]);
|
|
}
|
|
|
|
const { data, error } = await query;
|
|
|
|
if (error) {
|
|
console.error('Error calculating expected entries:', error);
|
|
throw new Error(`Failed to calculate expected entries: ${error.message}`);
|
|
}
|
|
|
|
return data?.map((entry) => entry.id) || [];
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
*/
|
|
export async function populatePokedexMappings(
|
|
supabase: SupabaseClient,
|
|
pokedexId: string,
|
|
pokedex: Pokedex
|
|
): Promise<void> {
|
|
const expectedEntryIds = await calculateExpectedEntries(supabase, pokedex);
|
|
|
|
if (expectedEntryIds.length === 0) {
|
|
console.warn(`No expected entries calculated for pokedex ${pokedexId}`);
|
|
return;
|
|
}
|
|
|
|
const mappings = expectedEntryIds.map((pokedexEntryId) => ({
|
|
pokedexId,
|
|
pokedexEntryId
|
|
}));
|
|
|
|
// 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}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Recalculate pokedex_entries_mapping table for a pokedex (used on update)
|
|
* Uses a single atomic RPC call to prevent orphaned pokedexes
|
|
* @throws Error if the Supabase RPC call fails
|
|
*/
|
|
export async function recalculatePokedexMappings(
|
|
supabase: SupabaseClient,
|
|
pokedexId: string,
|
|
pokedex: Pokedex
|
|
): Promise<void> {
|
|
const expectedEntryIds = await calculateExpectedEntries(supabase, pokedex);
|
|
|
|
if (expectedEntryIds.length === 0) {
|
|
console.warn(`No expected entries calculated for pokedex ${pokedexId}`);
|
|
return;
|
|
}
|
|
|
|
// 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 recalculating pokedex mappings:', error);
|
|
throw new Error(`Failed to recalculate pokedex mappings: ${error.message}`);
|
|
}
|
|
}
|