Case study · DLRG Melle · 2026
Tournament software for a 24-hour charity swim
A staff app and public lookup kiosk for a volunteer lifesaving club. It registers 300–500 swimmers, records every distance they swim over 24 continuous hours, and produces the official rankings and award lists under the 2027 competition rules.
- 525 commits in 9 weeks
- 57 user stories shipped
- Next.js 16 · React 19 · Prisma 7

The constraints
What I didn't get to choose
24 hours, once a year
The competition runs continuously for a full day. There is no maintenance window and no second attempt.
Volunteers at 3 a.m.
Helpers record distances at the pool edge in night shifts, on their own phones, after a few minutes of instruction.
300–500 participants, results under a second
Several thousand bookings a night, and every ranking view has to answer in under one second while the event is live.
Personal data of minors
Birth dates, children's names and publication objections. GDPR is a feature requirement here, not a footer link.
Official rules I don't control
Age groups, medal thresholds and awards follow the 2027 competition rules. They changed while the app was being built.
Engineering
Four decisions, and what each one prevents
1.The booking ledger is append-only.
A booking row is never updated or deleted. A correction is a new row that points at the booking it replaces, and a unique index allows each booking to be corrected only once, so every history is a single chain.
Prevents: Arguments at the award ceremony about who changed a distance, and totals that silently count a corrected card twice. The trail the club asked for comes free, without a separate audit table.

Recording a count card. 130 m is not a multiple of the 50 m pool, so the app asks before it saves. It's a warning, not a block, because the helper is holding the paper card. 2.“Exactly one active event” is enforced by the database.
A partial unique index on the event table, UNIQUE (status) WHERE status = 'active', instead of a check in application code.
Prevents: Two admins on two laptops starting different events in the same second. The second write fails at the database, whatever the code above it gets wrong.
3.Start numbers are assigned atomically.
Several helpers register walk-ins at the same time. Each registration increments a per-event counter and creates the participant in one transaction. The row lock on the counter serialises concurrent sign-ups, and a unique (event, start number) constraint backs it up.
Prevents: The classic read-max-then-insert race, where two swimmers leave the desk with the same number printed on their card.
4.Privacy is one shared filter, not per-screen logic.
A participant who objects to publication is redacted in the ranking queries themselves. The live rankings, the public lookup kiosk and every PDF list read from those queries, and the unauthenticated kiosk returns only the fields it displays.
Prevents: Next year's new export quietly printing an objector's name because nobody remembered to add the check.

The public kiosk: swimmers type their start number and see their total, day/night split and current medal. It shows a first name only, and none at all for objectors.

Try it yourself
The demo is the real app with fictional swimmers. The logins are printed on its sign-in page, you can click everything, and it resets every night. The source code is available on request.
Available for freelance projects and full-time frontend roles.