Skip to content

TogoActive v3 — Architecture Overview

TogoActive is a virtual fundraising / activity-challenge platform ("powered by togoparts.com"). v3 is the new generation of the platform: a React admin console, a Laravel admin API, a Laravel white-label (WL) public API, and a Next.js white-label public site — all operating on the same live production database as the still-running legacy application, and integrating deeply with the Togoparts (TGP) platform for user accounts, Strava activity data, and leaderboard ranking.

This document is the map. Each subsystem has its own deep-dive doc — see README.md for the full index.

Related docs: 02-admin-frontend.md · 03-admin-backend-api.md · 04-wl-api.md · 05-wl-frontend.md · 06-database-schema.md · 07-legacy-app-and-tgp-integration.md · 08-event-setup-guide.md · 09-developer-guide.md


1. The five systems

#SystemStackLocation (dev box)GitHub repoRole
1Admin FrontendReact 18 + Vite 5 SPA/var/www/togoactive-development/admin-frontendtga-v3-admin-webThe v3 admin console. Everything an event manager configures happens here. Served as a static dist/ build.
2Admin BackendLaravel 10 API/var/www/togoactive-development/admin-backendtga-v3-admin-apiREST API (/api/v1) behind the admin SPA. Sanctum auth, RBAC, per-event roles, feature settings, all event CRUD. Writes to the live shared DB.
3WL APILaravel API/var/www/togoactive-development/wl-event/frontend-api-wl-developmenttga-v3-wl-apiPublic-facing API (/api/v1) for the white-label event sites: event resolution, registration, payments (Stripe), donations, profiles, leaderboards, emails, crons.
4WL FrontendNext.js 14 (App Router) + TypeScript/var/www/togoactive-development/wl-event/frontend-wl-developmenttga-v3-wl-webThe public white-label event site. One deployment serves many events — the event is resolved from the request's Host domain. Renders admin-built pages from blocks JSON.
5Legacy app ("old admin")Laravel/var/www/togoactive— (production)The previous-generation production app. Still live. Still owns Strava activity ingestion, leaderboard sync, ranking, and achievement awarding (all events); achievement emails only for events < 49. Shares the same database.

Plus the external Togoparts platform (togoparts.com): the original cycling-community site whose database (mysql_tgp) holds the real user accounts, Strava challenge activity data, denormalized leaderboards, and the rank stored procedures every system calls.


2. Big picture

The single most important architectural fact: all Laravel apps (admin API, WL API, legacy) point at the same live production database. There is no "dev database" separation by default.

  • A configuration row edited from the new admin is instantly visible to the WL API, the WL site, and the legacy app.
  • Code, on the other hand, is deployed per-server — pushing to GitHub does not change anything until the code is pulled/deployed on each machine.
  • This asymmetry explains most "works here, broken there" bugs (see §6 Deployment).

3. How an event exists across the systems

An "event" (e.g. event 49, TOGO SG61) is:

  1. A row in events with a mode (default_mode / sessionalmode / donation_mode) plus satellite rows: events_dates, event_images, registration_setup, social_seo, events_meta (key/value, incl. TGP_CHALLENGE_ID), event_domains, and JSON blobs in configuration (appearance, email branding, donation config, fundraiser config, default messages, payment gateways).
  2. A set of feature flagsFeatureRegistry in the admin backend defines ~130 features in 8 groups; the event's mode provides defaults, and event_feature_settings stores per-event overrides. Flags hide/show admin sidebar sections and gate WL behavior (e.g. registration.merchandise enables the merch step).
  3. A set of builder pagesevent_pages rows whose blocks JSON is rendered 1:1 by the WL site's BlockRenderer (~80 block types). System page types (user_detail, team_detail, donation, my_profile, …) power dynamic routes like /user/{id}.
  4. A registration form schema — the Form Builder writes the event_registration configuration blob (fields, merchandise copy, success page; the upgrade tab writes the event_upgrade key).
  5. Commerce datarewards (SKUs), coupons, multi_quantity_discount, Stripe credentials from the shared payment_gateway_config library.
  6. A mapping to a TGP challengeTgpChallenge::cid() resolves the TGA event to a Togoparts challenge id (the configuration/events_meta TGP_CHALLENGE_ID rows; the admin backend and legacy app also keep a static map for old events 8–37), which is where leaderboards and ranking live.
  7. One or more public domainsevent_domains rows; the WL site resolves every request's Host header through the WL API's resolve-event endpoint.

4. The main data flows

Admin edit → public site. Admin SPA calls the admin API, which writes the shared DB and pings the WL site's revalidation endpoint (WlCache::flush). The WL site otherwise caches event config/pages for 60 seconds per event; a hard browser reload bypasses the cache. Admin preview iframes (Page Builder /builder-preview, Form Builder /registration-preview, Appearance preview) talk to the WL site directly over pb-*/fb-* postMessage — so admins see unsaved changes rendered by the real public renderer.

Visitor page view. Browser → WL site (Next.js) → middleware resolves Host → event id (WL API resolve-event) → SSR fetches event config + page blocks (60s TTL cache) → BlockRenderer renders the block tree → client hooks fetch live data (stats, leaderboards, viewer profile).

Registration. Step 1 immediately "partial-registers" the user (POST /auth/register creates the Togoparts account via TgpAccountProvisioner, the event_users row, and a $0 payments row — abandoners still count). Optional merchandise and donation steps produce Stripe Checkout sessions (StripeService, per-event credentials, itemised line items). The Stripe webhook + a verify-polling return page finalize the payment; success is a shareable token URL; emails are rendered by EmailTemplateRenderer with event branding; donation totals flow into the TGP leaderboard tables via DonationLeaderboardService.

Activity & ranking. Strava data is ingested by the legacy cron fleet (Stage 2) into the TGP DB, synced to leaderboards (Stage 3), and ranked by calling TGP stored procedures (UpdateRanks, UpdateDonationRanks, UpdateTeamRanks, …). The v3 WL API runs its own donations:recalc every 10 minutes for donation totals and exposes token-checked internal recalc endpoints. Achievement ownership split: the legacy AchievementMasterCron still awards achievements for all events with open windows (including ≥ 49), but achievement notification emails split at event 49 — legacy ChallengeNotification for events < 49, the v3 WL API's achievements:notify for events ≥ 49 (the challenge_achievement_winners.notified flag is the handshake; the legacy per-event SG61 cron is deliberately disabled).

5. Servers & environments

MachineWhat runs there
Dev/staging box — 128.199.72.46 (this machine)All four v3 working copies; admin SPA served from dist/ (v3.togoactive.com via nginx+php-fpm; DNS/certbot were pending); dev WL API (:8000) and WL site (:3000); admin API (:8001).
Production WL server — wl-api.togoparts.com / 178.128.113.107Production WL API + WL site (wl-web.service systemd unit, safe-build.sh zero-downtime deploys behind Apache).
DigitalOcean managed MySQLThe shared togoactive DB (+ togoactive-stage) and the TGP production DB. Shared by every app on both servers.

A cross-server dependency worth memorizing: the admin SPA's email-template preview/test-send and donation picker call the production WL API (VITE_WL_API_URL=https://wl-api.togoparts.com). If the prod WL API deploy is stale, those admin features break while everything else works — a preview-only bug signature.

6. Deployment model

  1. Code lives in four private GitHub repos (main branch): tga-v3-admin-api, tga-v3-admin-web, tga-v3-wl-api, tga-v3-wl-web.
  2. Developers commit + push per coherent change set; deployment to each server is a separate manual step (the team runs its own run.sh / deploy scripts — see 09-developer-guide.md).
  3. The admin frontend has no HMR in its served form — it must be rebuilt (npm run build) after every change for the static dist/ to update.
  4. The WL site deploys via safe-build.sh: build into .next-build, atomic swap, systemd restart, health checks, automatic rollback.
  5. Database changes (migrations run against the live DB — v3 migrations are applied there) and configuration edits are instantly global. Treat the DB as production at all times.

7. Security & access model (summary)

  • Admin API: Sanctum bearer tokens (24h default), custom admin guard on admin_users. Global RBAC (roles → permissions, super_admin bypass) plus per-event roles on admin_user_events.role (owner/admin/editor/viewer) enforced by CheckEventAccess — viewers are read-only server-side.
  • WL API: public endpoints throttled (60/min default, 300/min for read-heavy GETs, 3/min OTP); participant auth via Sanctum after a layered password verify against Togoparts users.passwd (crypt() handles both bcrypt and legacy hashes; strictly read-only — password columns are never rewritten); Google/Apple login via a JWT bridge to togoparts auth_v2.php.
  • Internal bridges: admin API → WL API calls (automation test-send, team/individual recalc) are token-checked with WL_INTERNAL_TOKEN; WL cache revalidation uses WL_REVALIDATE_SECRET; custom-domain provisioning uses WL_DEPLOY_TOKEN.
  • Payments: per-event Stripe credentials in the shared payment_gateway_config library; new events silently fall back to a shared TEST credential (togopart-test) — replacing it is a launch-checklist item.

Organiser guide and developer documentation for the TogoActive platform.