See More Details

Unity Runn Club

An event-management platform for community races — publish an event, take registrations and payment, deliver a QR ticket, and check runners in on race day.

2026 – Present
Web (public site + admin panel)

Overview

Unity Runn Club is a full-stack platform for running community races end to end. An organizer publishes an event with its categories, prices and capacity; a runner registers, pays with Bakong KHQR, and receives a ticket with a QR code; on race day, staff scan that code at a check-in station. Everything in between — email, capacity accounting, payment settlement, the public marketing site — is part of the same system.

The backend is a Go modular monolith on Chi, split by domain (events, registrations, payments, tickets, check-in, notifications, telegram, site config, audit) over PostgreSQL and Redis. The frontend is a Next.js 16 app serving both the public site and the admin panel, and a separate Socket.IO gateway pushes live updates. All four run in Docker Compose, with a Dokploy stack for the staging deployment.

The project is deliberately documented against its own gaps: production Bakong credentials, automated backups, and external monitoring are all listed as open rather than implied to be done. Payments run against a mock provider until certified merchant credentials are in place.

Highlights

  • Full event lifecycle: categories, prices, capacity, schedules, FAQs and rules, with registration deadlines and transaction-safe capacity enforcement.
  • Bakong KHQR payment generation with server-side verification, per-category USD/KHR currency, checkout expiry, and a mock provider for local development.
  • QR ticket issuance and a race-day check-in station supporting camera scan, USB scanner, and manual registration-number lookup, with duplicate check-in blocked at both the service and database level.
  • Transactional email — registration, payment, cancellation, event update and reminder — delivered by a background worker off a Redis queue with a PostgreSQL recovery sweep and retry limits.
  • Admin panel for the runner roster, CSV export, audit log, role management, and a live public-site design editor with immutable version history.
  • A four-level role hierarchy (USER < STAFF < ADMIN < SUPER_ADMIN) enforced by the API rather than only in the UI.

Design Notes

01

Capacity is enforced in the transaction, not in application code

Race capacity is the one number that must never over-sell. Registration counters are updated inside the same PostgreSQL transaction that creates the registration, and a unique constraint enforces one active registration per user per event — so two simultaneous sign-ups for the last slot can't both succeed. A failed payment initialization compensates by releasing the reserved slot rather than leaving it stranded.

02

Payment settlement moved from browser polling to a leased background sweep

Waiting for a KHQR payment originally meant the checkout page polling the API until the state flipped, which ties settlement to a tab staying open. Reconciliation now runs as a background pass that leases rows in PostgreSQL, so a payment confirms whether or not the runner is still on the page — and two API replicas won't process the same payment twice.

03

A notification queue that survives a Redis restart

Emails are queued in Redis for latency, but Redis is not the source of truth: every message is also a row in PostgreSQL, and a recovery sweep re-queues anything a crash left behind. The worker publishes an expiring heartbeat, so the Super Admin system console can distinguish 'no mail to send' from 'the worker is dead' — a distinction that is invisible from queue depth alone.

04

Realtime is a cache, PostgreSQL is the truth

Published site-design changes are pushed to connected browsers over Socket.IO with Redis Pub/Sub. Because a reconnecting browser can miss messages that were published while it was away, reconnection reloads the authoritative version from PostgreSQL instead of trusting the last event it happened to receive.

05

Posters render at their real aspect ratio instead of being cropped

Event posters come in portrait, classic, square, landscape and story artboards, and cropping every one of them into a fixed card ratio wastes the artwork. Uploads are normalized to JPEG server-side and card and hero variants are generated on write; the public page then renders the selected artboard uncropped over an ambient edge-to-edge backdrop, with a fallback to the original image for events created before the variant pipeline existed.

06

Verified rather than assumed

CI runs Go unit and PostgreSQL-backed integration tests, go vet, gofmt, frontend lint and production build on every pull request. Playwright journeys cover event discovery, poster rendering, the runner dashboard, cookie preferences, admin login persistence and poster-artboard editing, in both a desktop Chromium and a mobile Pixel 7 viewport.

Brand

Unity Runn Club logo
Club identity, managed through the admin site-design editor