data(*): Add final data changes

This commit is contained in:
Josh Creek
2026-01-17 23:30:40 +00:00
parent 479b3ae7a0
commit 46351c9af0
29 changed files with 12187 additions and 122 deletions
+135
View File
@@ -0,0 +1,135 @@
import type { SupabaseClient } from '@supabase/supabase-js';
import type { Pokedex } from '$lib/models/Pokedex';
type GameDexRow = {
id: string;
displayName: string;
sortOrder: number;
isDlc: boolean;
parentDexId: string | null;
};
type GameSummary = {
displayName: string;
releaseYear: number;
};
type GameDexRowWithGame = GameDexRow & {
games: GameSummary | GameSummary[] | null;
};
async function getGameIdForScope(
supabase: SupabaseClient,
gameScope: string
): Promise<string | null> {
const { data, error } = await supabase
.from('games')
.select('id')
.eq('displayName', gameScope)
.maybeSingle();
if (error || !data) return null;
return data.id;
}
export async function resolveDexScopes(
supabase: SupabaseClient,
pokedex: Pokedex
): Promise<string[]> {
if (!pokedex.gameScope) return [];
if (pokedex.dexScopes && pokedex.dexScopes.length > 0) {
return pokedex.dexScopes;
}
const gameId = await getGameIdForScope(supabase, pokedex.gameScope);
if (!gameId) return [];
const { data, error } = await supabase
.from('game_dexes')
.select('id')
.eq('gameId', gameId)
.order('sortOrder', { ascending: true })
.order('displayName', { ascending: true });
if (error || !data) return [];
return data.map((row) => row.id);
}
export async function setPokedexDexScopes(
supabase: SupabaseClient,
pokedexId: string,
dexIds: string[]
): Promise<void> {
const uniqueDexIds = Array.from(new Set(dexIds));
const { error: deleteError } = await supabase
.from('pokedex_dex_scopes')
.delete()
.eq('pokedexId', pokedexId);
if (deleteError) {
throw new Error(`Failed to clear dex scopes: ${deleteError.message}`);
}
if (uniqueDexIds.length === 0) return;
const rows = uniqueDexIds.map((dexId) => ({
pokedexId,
dexId
}));
const { error: insertError } = await supabase.from('pokedex_dex_scopes').insert(rows);
if (insertError) {
throw new Error(`Failed to save dex scopes: ${insertError.message}`);
}
}
export async function listGameDexes(
supabase: SupabaseClient
): Promise<{
gameDexes: Record<string, GameDexRow[]>;
gameOrder: string[];
games: { displayName: string; releaseYear: number }[];
}> {
const { data, error } = await supabase
.from('game_dexes')
.select('id, displayName, sortOrder, isDlc, parentDexId, games!inner(displayName, releaseYear)')
.order('sortOrder', { ascending: true })
.order('displayName', { ascending: true });
if (error || !data) return { gameDexes: {}, gameOrder: [], games: [] };
const grouped: Record<string, GameDexRow[]> = {};
const releaseYears: Record<string, number> = {};
const rows = data as GameDexRowWithGame[];
for (const row of rows) {
const game = Array.isArray(row.games) ? row.games[0] : row.games;
if (!game) continue;
const gameName = game.displayName;
if (!grouped[gameName]) grouped[gameName] = [];
if (releaseYears[gameName] == null) {
releaseYears[gameName] = game.releaseYear;
}
grouped[gameName].push({
id: row.id,
displayName: row.displayName,
sortOrder: row.sortOrder,
isDlc: row.isDlc,
parentDexId: row.parentDexId
});
}
const games = Object.keys(grouped)
.map((name) => ({
displayName: name,
releaseYear: releaseYears[name] ?? 0
}))
.sort((a, b) => {
if (a.releaseYear !== b.releaseYear) return b.releaseYear - a.releaseYear;
return a.displayName.localeCompare(b.displayName);
});
const gameOrder = games.map((game) => game.displayName);
return { gameDexes: grouped, gameOrder, games };
}
+44 -1
View File
@@ -1,5 +1,6 @@
import type { Pokedex } from '$lib/models/Pokedex';
import type { SupabaseClient } from '@supabase/supabase-js';
import { resolveDexScopes } from '$lib/services/PokedexDexScopeService';
/**
* Calculate expected pokedex entries based on pokedex configuration
@@ -10,12 +11,54 @@ export async function calculateExpectedEntries(
supabase: SupabaseClient,
pokedex: Pokedex
): Promise<number[]> {
const dexScopes = await resolveDexScopes(supabase, pokedex);
if (dexScopes.length > 0) {
const { data: dexEntries, error: dexError } = await supabase
.from('game_pokedex_entries')
.select('pokemonId')
.in('dexId', dexScopes);
if (dexError) {
console.error('Error calculating dex-scoped entries:', dexError);
throw new Error(`Failed to calculate dex-scoped entries: ${dexError.message}`);
}
const uniqueIds = Array.from(new Set(dexEntries?.map((row) => row.pokemonId) || []));
if (uniqueIds.length === 0) return [];
if (!pokedex.isFormDex) {
const { data: entries, error: entryError } = await supabase
.from('pokedex_entries')
.select('id, pokemon, form')
.in('id', uniqueIds);
if (entryError) {
console.error('Error filtering base forms for dex scopes:', entryError);
throw new Error(`Failed to filter base forms: ${entryError.message}`);
}
return (
entries
?.filter(
(entry) =>
entry.form === null ||
entry.form === 'male' ||
(entry.pokemon === 'Unown' && entry.form === 'A')
)
.map((entry) => entry.id) || []
);
}
return uniqueIds;
}
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)');
query = query.or('form.is.null,form.eq.male,and(pokemon.eq.Unown,form.eq.A)');
}
// Apply region filter: if gameScope is specified, filter by region