fix(#64): Address PR comments

This commit is contained in:
Josh Creek
2026-01-10 17:06:52 +00:00
parent 7c68d121ce
commit 68cc2a0712
7 changed files with 308 additions and 16 deletions
+26 -6
View File
@@ -1,8 +1,9 @@
<script lang="ts">
import { onDestroy } from 'svelte';
import { onDestroy, onMount } from 'svelte';
import { user } from '$lib/stores/user.js';
import { type User } from '@supabase/auth-js';
import SignUp from '$lib/components/SignUp.svelte';
import { goto } from '$app/navigation';
export let data;
let { supabase, stats } = data;
@@ -14,6 +15,28 @@
});
onDestroy(unsubscribe);
onMount(() => {
checkSessionAndRedirect();
});
async function checkSessionAndRedirect() {
try {
const {
data: { session }
} = await supabase.auth.getSession();
if (session) {
await goto('/my-pokedexes');
}
} catch (error) {
console.error('Error checking session:', error);
}
}
async function handleSignedUp() {
await goto('/welcome');
}
// Format large numbers (e.g., 1000 -> 1K, 1000000 -> 1M)
function formatNumber(num: number): string {
if (num >= 1000000) {
@@ -140,7 +163,7 @@
<div class="card shrink-0 w-full max-w-sm shadow-2xl bg-neutral">
<div class="card-body">
<h2 class="card-title text-2xl mb-4">Get Started Free</h2>
<SignUp {supabase} />
<SignUp {supabase} on:signedUp={handleSignedUp} />
</div>
</div>
</div>
@@ -153,17 +176,14 @@
<div class="stat">
<div class="stat-title">Pokémon caught</div>
<div class="stat-value text-primary">{pokemonCaught}</div>
<div class="stat-desc">Since January 2026</div>
</div>
<div class="stat">
<div class="stat-title">Users</div>
<div class="stat-value text-primary">{users}</div>
<div class="stat-desc">↗︎ 400 (22%)</div>
</div>
<div class="stat">
<div class="stat-title">Living Dexes Completed</div>
<div class="stat-value text-primary">{livingDexesCompleted}</div>
<div class="stat-desc">Trainers worldwide</div>
</div>
</div>
</div>
@@ -325,7 +345,7 @@
<div class="card shrink-0 w-full max-w-sm shadow-2xl bg-neutral">
<div class="card-body">
<h3 class="card-title text-xl mb-4">Sign Up Now</h3>
<SignUp {supabase} />
<SignUp {supabase} on:signedUp={handleSignedUp} />
</div>
</div>
</div>
+78
View File
@@ -3,6 +3,80 @@ import type { Pokedex } from '$lib/models/Pokedex';
import PokedexRepository from '$lib/repositories/PokedexRepository';
import { requireAuth } from '$lib/utils/auth';
import type { RequestEvent } from '@sveltejs/kit';
import type { SupabaseClient } from '@supabase/supabase-js';
/**
* Calculate expected pokedex entries based on pokedex configuration
* Applies the same filtering logic as CombinedDataRepository
*/
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 } = await supabase
.from('region_game_mappings')
.select('region')
.eq('game', pokedex.gameScope)
.single();
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);
return [];
}
return data?.map((entry) => entry.id) || [];
}
/**
* Populate pokedex_entries_mapping table for a pokedex
*/
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
}));
const { error } = await supabase.from('pokedex_entries_mapping').insert(mappings);
if (error) {
console.error('Error populating pokedex mappings:', error);
throw new Error(`Failed to populate pokedex mappings: ${error.message}`);
}
}
// GET: List all pokedexes for user
export const GET = async (event: RequestEvent) => {
@@ -31,6 +105,10 @@ export const POST = async (event: RequestEvent) => {
const repo = new PokedexRepository(event.locals.supabase, userId);
const pokedex = await repo.create(data);
// Populate pokedex_entries_mapping table with expected entries
await populatePokedexMappings(event.locals.supabase, pokedex._id, pokedex);
return json(pokedex);
} catch (err) {
console.error('Error in POST /api/pokedexes:', err);
+110
View File
@@ -3,6 +3,103 @@ import type { Pokedex } from '$lib/models/Pokedex';
import PokedexRepository from '$lib/repositories/PokedexRepository';
import { requireAuth } from '$lib/utils/auth';
import type { RequestEvent } from '@sveltejs/kit';
import type { SupabaseClient } from '@supabase/supabase-js';
/**
* Calculate expected pokedex entries based on pokedex configuration
* Applies the same filtering logic as CombinedDataRepository
*/
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 } = await supabase
.from('region_game_mappings')
.select('region')
.eq('game', pokedex.gameScope)
.single();
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);
return [];
}
return data?.map((entry) => entry.id) || [];
}
/**
* Populate pokedex_entries_mapping table for a pokedex
*/
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
}));
const { error } = await supabase.from('pokedex_entries_mapping').insert(mappings);
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)
*/
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) => {
@@ -63,6 +160,19 @@ export const PUT = async (event: RequestEvent) => {
return json({ error: 'Pokedex not found' }, { status: 404 });
}
// Recalculate pokedex_entries_mapping if configuration changed
// Only recalculate if type flags or gameScope 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);
if (configChanged) {
await recalculatePokedexMappings(event.locals.supabase, id, { ...pokedex, ...data });
}
return json(pokedex);
} catch (err) {
console.error(err);