From 3121b9aa36e42ec90e0d9883548bf1248fd1c82d Mon Sep 17 00:00:00 2001 From: Sabine Maennel <5292683+sabinem@users.noreply.github.com> Date: Fri, 23 Oct 2026 13:07:40 +0200 Subject: [PATCH] refactor(manage): read hackathon owners off the list HackathonService.List now carries owners for every hackathon the caller may read (#364), so the admin Hackathons page no longer fetches each hackathon in full to read them: one call instead of N+1. A row can no longer lose its owners on its own, so "Couldn't load owners" goes too. --- .../(app)/manage/hackathons/+page.server.ts | 54 ++++--------------- .../(app)/manage/hackathons/+page.svelte | 21 +++----- 2 files changed, 16 insertions(+), 59 deletions(-) diff --git a/components/frontend/src/routes/(app)/manage/hackathons/+page.server.ts b/components/frontend/src/routes/(app)/manage/hackathons/+page.server.ts index e17fe672..dcd5a27e 100644 --- a/components/frontend/src/routes/(app)/manage/hackathons/+page.server.ts +++ b/components/frontend/src/routes/(app)/manage/hackathons/+page.server.ts @@ -35,17 +35,10 @@ export const load: PageServerLoad = async (event) => { throw e }) - // TODO(backend: list-owners): `List` leaves `owners` empty — only `Get` fills - // it — so each row costs a `Get` of its own, the whole hackathon tree fetched - // to read one edge of it. Until then the page makes one call per hackathon, - // in parallel. Once `List` carries owners for a caller who may read them, - // delete `ownersOf` and read `h.owners` straight off the list entry. - const owners = await Promise.all(hackathons.map((h) => ownersOf(event, h.id))) - - // Only what the page draws. A `Get` carries every project, member and page - // of the hackathon; none of it belongs in the page payload. + // `List` fills `owners` for every hackathon the caller may read, which for an + // admin is all of them — so the owners arrive with the list, in one call. return { - hackathons: hackathons.map((h, i) => ({ + hackathons: hackathons.map((h) => ({ id: h.id, name: h.name, logo: h.logo, @@ -54,41 +47,12 @@ export const load: PageServerLoad = async (event) => { endsAt: h.endsAt, visibility: h.visibility, participantCount: h.participantCount, - owners: owners[i], + // Only what a badge shows, not the whole user record. + owners: h.owners.map((o) => ({ + id: o.id, + displayName: o.displayName, + username: o.username, + })), })), } } - -type Owner = { id: string; displayName: string; username: string } - -/** - * The hackathon's owners, or null when they could not be read. - * - * Null rather than a failed page: one hackathon deleted between `List` and its - * `Get`, or one refusal, should cost that row its owners and nothing else. The - * page says so on the row rather than showing an empty list, which would read - * as a hackathon nobody runs. - */ -async function ownersOf( - event: Parameters[0], - hackathonId: string, -): Promise { - const { hackathon } = requireGrpc(event.locals.grpc) - try { - const { hackathon: h } = await hackathon.get({ hackathonId }) - if (!h) return null - - return h.owners.map((o) => ({ - id: o.id, - displayName: o.displayName, - username: o.username, - })) - } catch (e) { - event.locals.logger.warn( - { err: e, hackathonId }, - "manage/hackathons: could not read owners", - ) - - return null - } -} diff --git a/components/frontend/src/routes/(app)/manage/hackathons/+page.svelte b/components/frontend/src/routes/(app)/manage/hackathons/+page.svelte index 27ad2b60..dd63cb74 100644 --- a/components/frontend/src/routes/(app)/manage/hackathons/+page.svelte +++ b/components/frontend/src/routes/(app)/manage/hackathons/+page.svelte @@ -14,7 +14,7 @@ let { data }: { data: PageData } = $props(); type Row = PageData['hackathons'][number]; - type Owner = NonNullable[number]; + type Owner = Row['owners'][number]; let search = $state(''); @@ -57,7 +57,7 @@ const filtered = $derived( sorted.filter((h) => { const group = whenGroup(h.status); - const owners = (h.owners ?? []).flatMap((o) => [o.displayName, o.username]); + const owners = h.owners.flatMap((o) => [o.displayName, o.username]); return matchesAny( [ h.name, @@ -131,18 +131,11 @@ {/snippet} {#snippet owners(h: Row)} - {#if h.owners === null} - - - Couldn't load owners - - {:else} -
- {#each h.owners as o (o.id)} - {ownerName(o)} - {/each} -
- {/if} +
+ {#each h.owners as o (o.id)} + {ownerName(o)} + {/each} +
{/snippet}