EGAC Platform Overview

The EGAC platform is the digital backbone for East Grinstead Athletics Club. It manages the full journey from a parent’s first enquiry through to club membership, handling taster session bookings, the Junior Academy waitlist, automated email communications, and admin reporting — all hosted entirely on Cloudflare’s free tier.

info
The platform is built on Astro SSR + Cloudflare Pages, with Cloudflare D1 (SQLite) as its database and Cloudflare KV for configuration caching. Email delivery uses Resend.

The two athlete paths

Every enquiry is automatically routed into one of two tracks the moment it arrives, based on the athlete’s date of birth and UK Athletics age-group rules.

flowchart TD A([Parent submits enquiry]) --> B{Resolve age group\nfrom DOB + session date} B -->|booking_type = taster| C[Taster Track] B -->|booking_type = waitlist| D[Academy Track] B -->|No DOB / out of range| C

C —> C1[Send booking invite email\nwith available dates] C1 —> C2[Parent picks a date\nvia /book/:token] C2 —> C3[Booking confirmed\nConfirmation email sent] C3 —> C4[7-day reminder\nsent by cron] C4 —> C5[Admin records attendance] C5 —> C6[Membership invite sent]

D —> D1[Added to academy_waitlist\nWaitlist email sent] D1 —> D2[Season rollover cron\nmoves unplaced entries] D2 —> D3[Admin invites from list] D3 —> D4[Parent responds yes/no\nvia /academy/respond/:token]

Technology stack

LayerTechnology
RuntimeCloudflare Workers (via Astro SSR)
DatabaseCloudflare D1 (SQLite)
Key-value storeCloudflare KV
EmailResend API
FrontendAstro + Tailwind CSS
LanguageTypeScript (strict)
TestsVitest

Repository structure

src/
├── api/                  ← Worker request handlers (all HTTP routes)
│   ├── enquiry.ts        ← POST /api/enquiry
│   ├── booking.ts        ← POST /api/booking
│   ├── academy.ts        ← POST /api/academy/respond
│   ├── router.ts         ← Single-entry routing switch
│   ├── admin/            ← Admin API handlers (auth-gated)
│   └── cron/             ← Scheduled job handlers
├── lib/
│   ├── business/         ← Pure functions: age, booking, routing
│   ├── repositories/     ← D1 query functions (all DB access)
│   ├── email/            ← Template rendering + Resend client
│   ├── db/               ← Schema types + D1 client
│   ├── auth/             ← Admin token validation
│   └── config.ts         ← KV-backed system configuration
├── pages/                ← Astro page components
│   ├── index.astro       ← Public landing page
│   ├── book/[token]      ← Taster booking UI
│   ├── join.astro        ← Membership form
│   ├── admin/            ← Admin dashboard pages
│   └── academy/respond/  ← Academy response UI
├── layouts/              ← Astro layout components
└── middleware/           ← Rate limiting + security headers
db/
└── migrations/           ← SQL migrations (applied via Wrangler)

Key design principles

Age groups are data, not code. All age-group configuration — session days, capacity, booking type — lives in the age_groups D1 table. No slot codes or age thresholds are hardcoded in application logic.

The enquiry is the single source of truth. Every significant action (invite sent, booking created, reminder sent, membership invited) is recorded as a typed event in enquiry.events (a JSON array column). This gives a complete audit trail per athlete.

All cron handlers are also HTTP handlers. Every scheduled job can be triggered via a POST request with a CRON_SECRET bearer token, making them straightforward to test, replay, or trigger manually in an emergency.

Email failures are non-fatal. The platform never lets a Resend API error crash a user-facing request. All email sends are wrapped and their results logged to the structured console, but the parent always gets a success response.