feat(backend): guest-facing Wahl overview endpoint + dev seed

GET /api/wahl/guest/overview (guest JWT) returns the open Wahlen for the
guest's KC, each with its workshops and the guest's own current priorities
(null if not yet submitted) — everything the client needs to render the
Wahl form without any LT-only endpoint. Verified end to end against a local
Postgres (guest login -> overview -> submit -> re-fetch).

prisma/seed-dev.js: minimal dev fixture (one KC "DEV123" + Gemeinde + open
Wahl with three workshops).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-10 08:45:11 +02:00
co-authored by Claude Sonnet 5
parent 4471d3a716
commit 912461751a
3 changed files with 78 additions and 0 deletions
+9
View File
@@ -69,6 +69,15 @@ export class WahlController {
return this.wahl.createForceZuteilung(wahlId, dto.teilnehmerId, dto.workshopId);
}
/// Everything a guest needs to fill in the Wahl: open Wahlen for their KC,
/// each with its workshops and the guest's own current priorities (if any).
@Get('guest/overview')
@UseGuards(AuthGuard('guest'))
guestOverview(@Req() req: GuestAuthenticatedRequest) {
const guest = req.user!;
return this.wahl.guestOverview(guest.kcId, guest.guestId);
}
/// Guests submit their own workshop preferences (guest JWT, not Authentik).
@Post(':wahlId/teilnehmer')
@UseGuards(AuthGuard('guest'))
+34
View File
@@ -22,6 +22,40 @@ export class WahlService {
return this.prisma.wahl.findMany({ where: { kcId } });
}
/// Guest-facing view: open Wahlen for the guest's KC, each with its
/// workshops and the guest's own current priorities (null if not submitted).
async guestOverview(kcId: string, guestAccountId: string) {
const [kc, wahlen] = await Promise.all([
this.prisma.kc.findUnique({ where: { id: kcId }, select: { id: true, name: true } }),
this.prisma.wahl.findMany({
where: { kcId, isOpen: true },
orderBy: { createdAt: 'asc' },
include: {
workshops: {
select: { id: true, name: true, kapazitaet: true },
orderBy: { name: 'asc' },
},
teilnehmer: {
where: { guestAccountId },
select: { prioritaeten: true },
},
},
}),
]);
return {
kc,
wahlen: wahlen.map((w) => ({
id: w.id,
name: w.name,
datumsSchluessel: w.datumsSchluessel,
teil: w.teil,
workshops: w.workshops,
meinePrioritaeten: (w.teilnehmer[0]?.prioritaeten as string[] | undefined) ?? null,
})),
};
}
async createWorkshop(
wahlId: string,
name: string,