Arena Stats
A person-centric, multi-sport athlete registry — self-registration, per-sport profiles, minor and guardian-consent gating, per-sport national-association approval, and a visibility-aware public directory.
The problem
A national multi-sport program needs one registry of athletes that many sports can share, without collapsing every sport's rules into a single rigid schema. An athlete is one person who may compete in several sports, each governed by its own association with its own approval authority. Model that naively and you get either duplicated people across sports or a tangle where changing one sport's rules risks breaking another's. On top of the structural problem sits a compliance one: many registrants are minors. Their data has to be gated by guardian consent and masked by default in any public view — not as an afterthought, but as a rule the system can't be talked out of.
Key challenges
The hardest constraint is keeping the person independent of the sports. If the athlete record holds foreign keys into sport, competition, and scoring tables, every sport-specific change threatens the core identity. Minor detection, consent gating, and visibility masking have to be derived and enforced consistently across the API and the UI. And approval authority has to be scoped precisely — a reviewer for one sport must not be able to approve or even see submissions for another.
What we built
AR Data built Arena as a pnpm + Turborepo monorepo enforcing a strict one-way dependency rule: the person (`Player`) holds no foreign keys into any sport, competition, or scoring table — sport tables reference the player, never the reverse. Identity stays stable while sports evolve independently around it. A pure, framework-free domain package holds the rules — minor detection from birthdate, guardian-consent gating, visibility, directory eligibility — validated with zod and shared identically by the Fastify/Prisma API and the Next.js web app. Minors default to restricted visibility with photo, birthdate, and region masked in the public directory; a sport profile is reviewed only by an association admin scoped to that exact sport, with cross-sport review rejected outright; and a player appears publicly only with at least one approved sport and non-private visibility. Every state transition writes to an activity log.
Our approach
- 1
Person-centric core, sports on the outside
A one-way dependency rule keeps the Player record free of foreign keys into sport tables. Sports reference the person; the person never references a sport. Identity stays durable as sports are added or changed.
- 2
Rules in a pure domain package
Minor detection, consent gating, visibility, and directory eligibility live in a framework-free package with zod schemas, shared by both API and web. The same rule can't drift between server and client because there's only one copy.
- 3
Minor and guardian-consent gating by default
isMinor is derived from birthdate. A minor can't submit a sport profile without recorded guardian consent, and defaults to restricted visibility — photo, birthdate, and region masked in the public directory.
- 4
Scoped per-sport approval
A sport profile is reviewed only by an association admin scoped to that sport (or a program-wide admin). A reviewer attempting to act on another sport's submission is rejected. Approval authority is precise, not blanket.
Key architectural decisions
One-way dependency: person never references sport
Making identity independent of any sport is what lets the platform be genuinely multi-sport. Sport tables reference the player, never the reverse, so one sport's schema changes can't destabilize the core.
Domain logic in a pure, shared package
Consent, minor status, and visibility are too important to re-implement per surface. A single framework-free package validated with zod guarantees the API and UI enforce identical rules.
Restricted-by-default for minors
For under-18 registrants, safe defaults beat opt-in privacy. Guardian consent to submit and masked public fields make the compliant path the automatic one.
Directory eligibility as an explicit rule
A player appears publicly only with at least one approved sport and non-private visibility. Encoding eligibility as a rule keeps unapproved or private profiles out of the public directory by construction.
Results
- Person-centric registry — one athlete, many sports, no duplication
- Strict one-way dependency isolating identity from sport-specific data
- Minor detection with mandatory guardian-consent gating
- Restricted-by-default visibility masking for minors in public views
- Per-sport approval scoped to the governing association
- Visibility-aware public directory with explicit eligibility rules
- Full activity log on every create, update, consent, submit, and approval
Impact
Arena Stats gives a multi-sport program a single, durable athlete registry that respects each sport's own approval authority and treats minor-data protection as a structural guarantee rather than a policy note. The architecture — a stable person at the center, sports and their rules orbiting independently — is the pattern that lets the same platform serve a new sport tomorrow without touching the identity that every sport depends on. It reflects how AR Data ships: get the domain model right, encode the rules once, and deliver a production system end to end rather than a prototype that buckles at the first new requirement.
Tech stack
Want a case study like this?
30 minutes. We scope the real problem and figure out what to build.
Book a call

