import { db } from '$lib/server/db'; import { games, pokemonGameEntries, userEntries, userGames, catchEmAllSettings } from '$lib/server/db/schema'; import { and, eq, inArray } from 'drizzle-orm'; import type { Actions, PageServerLoad } from './$types'; import { availabilityValues } from '$lib/availability'; import { fail } from '@sveltejs/kit'; export type WireEntry = [id: number, gameId: number, availability: number, note?: string]; export const load: PageServerLoad = async ({ locals }) => { const pokemonRows = await db.query.pokemon.findMany({ with: { entry: true }, orderBy: { number: 'asc' } }); const pokemon = pokemonRows.map(({ entry, ...rest }) => ({ ...rest, entries: entry.map((e): WireEntry => { const code = availabilityValues.indexOf(e.availability); return e.note === null ? [e.id, e.gameId, code] : [e.id, e.gameId, code, e.note]; }) })); const gameRows = await db.query.games.findMany({ orderBy: { generation: 'asc', id: 'asc' } }); let caughtIds: number[] = []; let selectedGameIds: number[] = []; let gamesConfigured = false; if (locals.user) { const [settings] = await db .select() .from(catchEmAllSettings) .where(eq(catchEmAllSettings.userId, locals.user.id)); gamesConfigured = !!settings; const caughtRows = await db .select({ id: userEntries.pokemonGameEntryId }) .from(userEntries) .where(eq(userEntries.userId, locals.user.id)); caughtIds = caughtRows.map((r) => r.id); const gameRows = await db .select({ id: userGames.gameId }) .from(userGames) .where(eq(userGames.userId, locals.user.id)); selectedGameIds = gameRows.map((r) => r.id); } return { pokemon, games: gameRows, caughtIds, selectedGameIds, gamesConfigured: gamesConfigured || selectedGameIds.length > 0 }; }; export const actions = { togglePokemonCaught: async ({ request, locals }) => { if (!locals.user) return fail(401, { message: 'Log in to track your progress' }); const data = await request.formData(); const entryId = Number(data.get('entryId')); if (!Number.isSafeInteger(entryId) || entryId <= 0 || entryId > 2147483647) return fail(400, { message: 'Invalid entry' }); // only catchable entries can be toggled — the client disables these // checkboxes but we can't trust that const [entry] = await db .select({ availability: pokemonGameEntries.availability }) .from(pokemonGameEntries) .where(eq(pokemonGameEntries.id, entryId)); if (!entry) return fail(404, { message: 'Unknown entry' }); if (entry.availability !== 'catchable') { return fail(400, { message: 'This entry does not have confirmed encounter data' }); } if (data.has('caught') && data.get('caught') !== 'on') return fail(400, { message: 'Invalid caught state' }); const caught = data.get('caught') === 'on'; if (caught) { await db .insert(userEntries) .values({ pokemonGameEntryId: entryId, userId: locals.user.id }) .onConflictDoNothing(); } else { await db .delete(userEntries) .where( and(eq(userEntries.pokemonGameEntryId, entryId), eq(userEntries.userId, locals.user.id)) ); } return { saved: true }; }, setGames: async ({ request, locals }) => { if (!locals.user) return fail(401, { message: 'Log in to set rules' }); const data = await request.formData(); const submittedIds = data.getAll('gameIds').map(Number); if ( submittedIds.length > 200 || submittedIds.some((id) => !Number.isSafeInteger(id) || id <= 0) ) { return fail(400, { message: 'Invalid game selection' }); } const gameIds = [...new Set(submittedIds)]; if (gameIds.length > 0) { const validGames = await db .select({ id: games.id }) .from(games) .where(inArray(games.id, gameIds)); if (validGames.length !== gameIds.length) return fail(400, { message: 'Unknown game' }); } const userId = locals.user.id; await db.transaction(async (tx) => { await tx .insert(catchEmAllSettings) .values({ userId }) .onConflictDoUpdate({ target: catchEmAllSettings.userId, set: { userId } }); await tx.delete(userGames).where(eq(userGames.userId, userId)); if (gameIds.length > 0) { await tx.insert(userGames).values(gameIds.map((gameId) => ({ userId, gameId }))); } }); return { saved: true }; } } satisfies Actions;