feat: client monorepo (Flutter app) + web fallback redesign #1

Merged
linus merged 37 commits from feat/backend-phases-0-6 into main 2026-09-12 11:27:27 +00:00
2 changed files with 44 additions and 0 deletions
Showing only changes of commit 0b588fa4b7 - Show all commits
+8
View File
@@ -78,6 +78,14 @@ export class WahlController {
return this.wahl.guestOverview(guest.kcId, guest.guestId); return this.wahl.guestOverview(guest.kcId, guest.guestId);
} }
/// The guest's own assignment result per Wahl they took part in.
@Get('guest/results')
@UseGuards(AuthGuard('guest'))
guestResults(@Req() req: GuestAuthenticatedRequest) {
const guest = req.user!;
return this.wahl.guestResults(guest.kcId, guest.guestId);
}
/// Guests submit their own workshop preferences (guest JWT, not Authentik). /// Guests submit their own workshop preferences (guest JWT, not Authentik).
@Post(':wahlId/teilnehmer') @Post(':wahlId/teilnehmer')
@UseGuards(AuthGuard('guest')) @UseGuards(AuthGuard('guest'))
+36
View File
@@ -56,6 +56,42 @@ export class WahlService {
}; };
} }
/// Guest-facing result view: for every Wahl in the guest's KC where they
/// took part, their assignment (workshop name + wish rank), or a pending
/// marker if the algorithm has not run for them yet.
async guestResults(kcId: string, guestAccountId: string) {
const teilnahmen = await this.prisma.teilnehmer.findMany({
where: { guestAccountId, wahl: { kcId } },
orderBy: { wahl: { createdAt: 'asc' } },
select: {
wahl: { select: { id: true, name: true, datumsSchluessel: true, teil: true } },
zuteilung: { select: { workshopId: true, wunschRang: true, isForced: true } },
},
});
const workshopIds = teilnahmen
.map((t) => t.zuteilung?.workshopId)
.filter((id): id is string => !!id);
const workshops = workshopIds.length
? await this.prisma.workshop.findMany({
where: { id: { in: workshopIds } },
select: { id: true, name: true },
})
: [];
const nameById = new Map(workshops.map((w) => [w.id, w.name]));
return teilnahmen.map((t) => {
const z = t.zuteilung;
return {
wahl: t.wahl,
status: !z ? 'PENDING' : z.workshopId ? 'ASSIGNED' : 'UNASSIGNED',
workshopName: z?.workshopId ? (nameById.get(z.workshopId) ?? null) : null,
wunschRang: z?.wunschRang ?? null,
isForced: z?.isForced ?? false,
};
});
}
async createWorkshop( async createWorkshop(
wahlId: string, wahlId: string,
name: string, name: string,