feat(#56): Address PR comments

This commit is contained in:
Josh Creek
2026-01-10 12:37:43 +00:00
parent b1b6e63b6f
commit b30be24fd5
6 changed files with 187 additions and 13 deletions
@@ -88,12 +88,29 @@ export const POST = async (event: RequestEvent) => {
try {
const userId = await requireAuth(event);
const { id: pokedexId } = event.params;
const records: Partial<CatchRecord>[] = await event.request.json();
const body: unknown = await event.request.json();
if (!pokedexId) {
return json({ error: 'Pokedex ID is required' }, { status: 400 });
}
if (!Array.isArray(body)) {
return json({ error: 'Request body must be an array of catch records' }, { status: 400 });
}
const invalidIndex = body.findIndex((r) => {
if (!r || typeof r !== 'object') return true;
const entryId = (r as Partial<CatchRecord>).pokedexEntryId;
return entryId === undefined || entryId === null || entryId === '';
});
if (invalidIndex !== -1) {
return json(
{ error: `Record at index ${invalidIndex} is missing required field "pokedexEntryId"` },
{ status: 400 }
);
}
const records = body as Array<Partial<CatchRecord> & Pick<CatchRecord, 'pokedexEntryId'>>;
const { session } = await event.locals.safeGetSession();
if (session) {
await event.locals.supabase.auth.setSession(session);
+2 -1
View File
@@ -40,7 +40,8 @@
async function readApiErrorMessage(response: Response): Promise<string> {
try {
const data = await response.json();
// Use a clone so we can still fall back to reading the original body as text.
const data = await response.clone().json();
if (data && typeof data === 'object' && 'error' in data && typeof data.error === 'string') {
return data.error;
}
+15 -3
View File
@@ -41,6 +41,7 @@
let catchWriteQueue: ReturnType<typeof createCatchRecordWriteQueue> | null = null;
let catchWriteQueueKey: string | null = null;
let catchWriteQueueUnsubscribe: (() => void) | null = null;
let catchWriteStatus: CatchRecordWriteQueueStatus = {
pending: 0,
inFlight: 0,
@@ -57,6 +58,10 @@
localUser = value;
});
onDestroy(unsubscribe);
onDestroy(() => {
catchWriteQueueUnsubscribe?.();
catchWriteQueueUnsubscribe = null;
});
function openPokemonModal(pokemon: CombinedData) {
selectedPokemon = pokemon;
@@ -75,15 +80,19 @@
const desiredKey = `${localUser.id}:${pokedexId}`;
if (catchWriteQueue && catchWriteQueueKey === desiredKey) return;
// Unsubscribe from the previous queue's status store before replacing the queue.
catchWriteQueueUnsubscribe?.();
catchWriteQueueUnsubscribe = null;
catchWriteQueue = createCatchRecordWriteQueue({
endpointUrl: `/api/pokedexes/${pokedexId}/catch-records`,
fetchFn: fetch,
batchSize: 200,
concurrency: 2
concurrency: 1
});
catchWriteQueueKey = desiredKey;
catchWriteQueue.getStatus.subscribe((s) => {
catchWriteQueueUnsubscribe = catchWriteQueue.getStatus.subscribe((s) => {
catchWriteStatus = s;
});
}
@@ -332,9 +341,11 @@
if (document.visibilityState === 'hidden') flushKeepalive();
};
const onOnline = () => void catchWriteQueue?.flushNow();
window.addEventListener('pagehide', flushKeepalive);
document.addEventListener('visibilitychange', onVisibilityChange);
window.addEventListener('online', () => void catchWriteQueue?.flushNow());
window.addEventListener('online', onOnline);
// Periodic reconciliation to guard against any missed state (e.g. aborted tab-close flush).
const reconcileInterval = window.setInterval(() => {
@@ -347,6 +358,7 @@
return () => {
window.removeEventListener('pagehide', flushKeepalive);
document.removeEventListener('visibilitychange', onVisibilityChange);
window.removeEventListener('online', onOnline);
window.clearInterval(reconcileInterval);
};
});