School Golf — Multi-Tenant Rebuild Plan

Use your browser's print dialog (Ctrl/Cmd + P) to save as PDF or print.

School Golf

Multi-Tenant Rebuild with Teams — Product Requirements Document

Intent & Goal

Transform the single-school Aves Golf Tracker into 'School Golf,' a multi-tenant platform where many schools independently manage their golf teams, run live scoring, view leaderboards, and track players year-round including off-season non-school competition. Data is school-owned (not player-portable), new schools join via an approved-onboarding flow, and schools pay a subscription while players and parents use the platform for free. Within each school, admins can create multiple teams (Varsity, JV, Boys, Girls, Middle School, 7th Grade, etc.) so multi-team schools can organize rosters, events, rankings, and dashboards by team — while any coach in the school can administer any team (no team-level access scoping in v1). The rebuild adds a School entity as the tenancy root, a Team entity per school, scopes all existing entities to a school (and events/rosters to a team), introduces per-school branding and theming, adds a season/off-season model, and adds subscription billing for schools. The app name is 'School Golf' and the custom domain schoolgolf.com will be connected.

Audience & Roles

  • Platform Owner — approves new schools and manages the platform; not scoped to a school.
  • School Admin — the coach/AD who sets up and runs their school's teams.
  • Coach — manages players, events, practices, scoring for ANY team in their school (a boys coach can score the girls team's match if the girls coach is absent).
  • Parent — views/logs their child's rounds including off-season; no admin tools.
  • Walking Scorer — enters scores via event code, unchanged.
  • School Admin, Coach, and Parent are all bound to a school_id; Platform Owner is not. No team-level access control in v1 — all coaches in a school have equal administrative power across all that school's teams.

Core Flows (must work end-to-end)

  • 1. Platform owner approves a new school: a coach requests access via a public request form (school name, their email, role); the request lands in a Platform Owner review queue; on approval a School record is created and the requesting coach becomes that school's first School Admin with login access.
  • 2. School admin runs the setup wizard: after approval the admin lands on a multi-step setup wizard — (a) name and create their FIRST team via a blank text field (no preset name, so it works for Varsity, JV, Middle School, 7th Grade, etc.), (b) upload a school logo, (c) set a primary theme color, (d) invite coaches. Additional teams are created later from a Teams admin page. Single-team schools name one team and move on; multi-team schools add more at any time.
  • 3. School admin manages teams and rosters: admin creates additional teams (e.g. Varsity, JV, Girls) from a Teams page, assigns each player a primary team for roster organization, and can roster a player into any team's event on a given day (a kid may play JV one match and get called up to Varsity the next) — so team is really an attribute of the Event, and the player's primary team is just a default.
  • 4. School admin manages a season by team: admin creates events and practices scoped to their school AND a specific team, selects players, generates the 4-digit access code, and runs live scoring — all flows filtered by school_id and team_id so no cross-school or cross-team data leaks on lists.
  • 5. Walking scorer scores a live round: scorer enters the event's access code at /walking-scorer; the code resolves to a school- and team-scoped event; scoring proceeds as today, with data written against the school's players and that team's rounds.
  • 6. Parent or coach logs an off-season round: a round is created against a school player without an event (or tagged as 'non-school' / off-season); the round is unaffiliated with any team so it shows under 'All Teams' but does not pollute a specific team's in-season rankings; included in the player's dashboard and coach insights.
  • 7. Coach filters by team across the app: the Events list, Schedule, Rankings, and Dashboard each get a team filter (sitting beside the season switcher) so a coach sees the JV schedule separately from Varsity, ranks only that team's players based on their rounds in that team's events, and narrows the dashboard player selector and stats to that team; 'All Teams' is always available as the default view.
  • 8. Coach reviews player insights across the full year: the coach opens a player's dashboard filtered to 'All Time' or 'Off-Season' and by team, and sees round history, scoring trends, and practice data spanning school season and off-season, so they can plan the next season.
  • 9. School subscription billing: the School Admin subscribes (monthly or annual) via Stripe checkout; subscription status gates access — if a school's subscription lapses, the admin sees a billing prompt and non-admin users see a read-only / 'contact your admin' state; the Platform Owner can see subscription status per school. Subscription is per school, not per team.
  • 10. Per-school theming renders throughout: the school's logo displays in the header and the school's primary color overrides the theme tokens at runtime, so each school's instance feels branded to them.

Technical Requirements

  • App branding: rename the app to 'School Golf'; connect the custom domain schoolgolf.com; remove all Aves-specific branding.
  • Multi-tenancy: add a new School entity (id, name, logo_url, primary_color, status, stripe_subscription_status, created_date) as the tenancy root.
  • Add a new Team entity (id, school_id, name, created_date) — a school can have many teams; on approval the setup wizard creates the first team via a user-supplied name (no preset default).
  • Add a school_id field to Player, Event, Round, Practice, HoleScore, RoutineCheck, Course, Coach, and Team.
  • Add an optional team_id to Event and Practice (an event belongs to one team) and an optional primary_team_id to Player (roster organization only; a player can still be rostered into any team's event).
  • Rounds inherit team context from their event_id; off-season rounds have no team_id and show under 'All Teams' only.
  • Thread school_id (and team_id where relevant) into every create and every list/filter call.
  • Update RLS on every entity so users can only read/write records where school_id matches their school; the Platform Owner role bypasses the school filter. No team-level RLS in v1 — any coach in a school can administer any team's events, players, and scoring.
  • User binding: add school_id to the User entity (or a SchoolMembership join) so each user belongs to one school; Platform Owner has no school_id.
  • Approved onboarding: a public request form writes a SchoolRequest entity the Platform Owner reviews; approval creates the School and sets the requester's school_id + role.
  • Event access codes must be unique within a school (not globally) to avoid collisions across schools.
  • Seasons: add an optional Season entity (school_id, name, start_date, end_date, type 'school'|'off_season') and a season_id (or season tag) on Round/Event/Practice; dashboard and rankings respect the season filter.
  • Off-season rounds: rounds with no event_id or tagged off-season are excluded from in-season rankings (exclude_from_rankings already exists) but included in the player dashboard and coach insights; they carry no team_id so they never pollute a specific team's rankings.
  • Team filtering: add a team_id filter param to getRankingsData and getLeaderboardData and the Dashboard fetches so lists/stats narrow by team; 'All Teams' omits the filter.
  • Theming: load the school's primary_color and logo_url at app boot and override --primary CSS token at runtime; render the logo in the header.
  • Billing: use Stripe (already installed) for per-school subscriptions; store subscription status on the School entity; gate access via the ProtectedRoute / AuthProvider layer. Subscription is per school, not per team.
  • Reuse existing backend functions (getLeaderboardData, getRankingsData, saveHoleScore, etc.) by adding school_id and team_id filtering inside them.
  • Preserve all existing live-scoring, routine-check, leaderboard, and dashboard functionality — they gain school_id + team_id dimensions, not a rewrite of their logic.

Design Preferences

  • Rename the app to 'School Golf' and remove all Aves-specific branding.
  • The header shows the active school's logo next to the app name, and the primary color adapts per school at runtime via CSS tokens.
  • The School Admin setup wizard is a clean, multi-step mobile-first flow: (1) Create your first team — blank name field, (2) upload logo, (3) pick primary color, (4) invite coaches.
  • The Teams admin page lists all teams with add/edit; each player has a primary team badge.
  • Season switcher and team filter sit together as compact segmented controls on the Dashboard, Rankings, Events, and Schedule pages — team filter defaults to 'All Teams'.
  • Off-season rounds are visually distinguished (subtle badge or muted styling) so coaches can tell at a glance what's in-season vs off-season.
  • The Platform Owner review queue is a simple admin-only page listing pending school requests with Approve/Reject.

App Store Publishing — Action Plan

  • 1. Enroll in the Apple Developer Program (developer.apple.com). It costs $99/year; a personal/individual account is fine to start, while an Organization account lists the school as the publisher (Apple may require a legal business name and a D-U-N-S number). Activation can take up to 2 business days, and the Keys tab can take up to 48 hours to appear after payment — begin this before anything else.
  • 2. While waiting, write a privacy policy (Apple requires a public URL) and prepare App Store listing assets: app name, short and full descriptions, category, keywords, and device screenshots (generate these from the app preview). Confirm the app name is not trademarked.
  • 3. Complete Apple's age-rating and data-disclosure questionnaire honestly, based on what the app collects (names, photos, emails, player data). Apple will also ask how users sign up — you have email/password plus Google — so be ready to describe the login flow.
  • 4. In the Base44 editor: Publish → Mobile app → Check Your App → Run App Scan against the App Store guidelines, and resolve every critical finding before submitting.
  • 5. Once the Apple account is active, create an App Store Connect API key (Users and Access → Integrations → +; role Admin or App Manager). Copy the Issuer ID and Key ID, download the .p8 file, and note your Team ID (Apple Developer account → Membership details).
  • 6. In Base44: Publish → Mobile app → Build Stores Files → Create App Store files. Enter the Issuer ID, Key ID, Team ID, and upload the .p8 key. Set the app logo, generate the files, and download the IPA bundle.
  • 7. Upload the IPA to App Store Connect (via the website or the Transporter app on a Mac). Complete the listing — screenshots, description, privacy policy URL, category, and age rating — then submit for review.
  • 8. Wait for Apple review (a few days, possible back-and-forth). Apps involving youth/school data may receive questions about accounts, data collection, and age rating; be ready to explain who uses the app and what data is stored.
  • Optional — Google Play: to also publish on Android, enroll separately in the Google Play Console (~$25 one-time) and build Android store files in the same Base44 Publish flow. Starting with iOS-only to learn the process, then adding Android, is a common path.