7.7 KiB
RadioStore — Implementation Plan
Multi-platform radio listing product: tvOS client (public browse + profile), Vapor REST backend (PostgreSQL), and React admin (station/user/policy management). This document is the authoritative roadmap for implementation order, boundaries, and technical choices.
1. Product summary
| Surface | Role |
|---|---|
| Apple TV app | Browse public catalog (grouped by geography → country → genre), play streams, Sign in with Apple, favorites, listen history; offline-capable catalog cache via SwiftData. |
| Backend | REST API only; auth for Apple-verified users and separate admin auth; merge anonymous → authenticated accounts; visibility rules (global hide, geo, age). |
| Admin (React) | CRUD stations, hide/show, geo & age restrictions; user list; operational views using React Bootstrap (react-bootstrap.netlify.app) for layout, forms, tables, modals. |
2. Domain model (conceptual)
2.1 Radio station
- title — display name
- image — URL or storage key resolved to URL
- stream URL — live broadcast link
- stream type — enum:
mp3,m3u8(extensible later) - taxonomy — associations to geo region, country, genre (many-to-many or normalized FKs as needed)
- visibility —
published/hidden(admin toggle; hidden excludes from public lists unless overridden by internal tools)
2.2 Catalog grouping (client + API contract)
Public API returns a structure suitable for tvOS navigation:
- Geo (e.g. continent or macro-region)
- Country (within geo)
- Genre (within country or cross-cutting — pick one canonical hierarchy and stick to it; recommended: Geo → Country → list of stations grouped by genre)
2.3 Users and identity
- Anonymous / hidden account — created on first app launch; stable server-side
device_user_idor JWT pairing; used until Sign in with Apple completes. - Authenticated account — Apple ID subject (
sub) after SIWA; server verifies identity token (Apple’s JWKS). - Merge — on first successful SIWA, attach anonymous profile data (listen history, favorites) to the Apple-backed user; then retire or alias the anonymous ID.
2.4 User data
- Favorites — ordered list of station IDs per user.
- Listen history — append-only or capped list: station, timestamp, optional duration; synced from client periodically.
2.5 Admin-only visibility rules
- Hide by geo — station invisible to clients resolving catalog for that geo (or country-level granularity if simpler).
- Hide by age — after SIWA, Apple may expose real user age only in limited ways; design assumption: use whatever age bucket or numeric age the app receives from Apple’s credential/API surface you adopt (document explicitly in API). Stations carry minimum age or age tier;filter server-side for authenticated requests; anonymous users see policy without age gate or a conservative default (product decision).
3. Technology stack (fixed)
| Layer | Choice |
|---|---|
| tvOS UI | SwiftUI |
| tvOS local persistence | SwiftData |
| tvOS packaging | Swift Package Manager (SPM); app split into modules |
| Backend | Swift Vapor 4.x |
| Database | PostgreSQL 18.2 |
| Local / dev DB | Docker Compose |
| Admin UI | React + React Bootstrap |
4. Apple TV app — SPM module layout
| Module | Responsibility |
|---|---|
| Core | Shared types (RadioStation, stream type enum), formatting, keys, small utilities, design tokens if shared. |
| API | HTTP client, request builders, decodable DTOs, auth header injection (anonymous token / SIWA bearer), error mapping. |
| Player | Controller + SwiftUI view: inputs (title, artwork URL, stream URL, type: mp3 | m3u8); integrates AVPlayer / appropriate pipeline for HLS vs progressive audio. |
| App | Targets: tvOS app entry; Splash → Catalog (grouped lists) → detail/play; Profile (Sign in with Apple button when logged out; name + listen history when logged in); wires API + Player + SwiftData repositories. |
Data flow: API fills SwiftData models for offline catalog where useful; favorites and history sync via REST; Player module stays UI + playback only.
5. Backend (Vapor) — REST-only outline
Suggested resource groups (names illustrative):
GET /api/v1/catalog— public structured tree or flattened stations with grouping metadata (respect geo + age + hide flags for anonymous vs authenticated via optional auth).GET /api/v1/stations/:id— single station if needed for deep links.POST /api/v1/device/register— issue anonymous session / user stub.POST /api/v1/auth/apple— verify Apple token, return API JWT or session; triggers merge.GET|PUT /api/v1/me/favorites— list / replace favorites.POST /api/v1/me/listen-events— batched history append.- Admin — prefix
/admin/v1/...with separate auth (API keys or JWT for staff): stations CRUD, visibility, geo/age rules, user list, impersonation off by default.
PostgreSQL: migrations for users (anonymous + apple_sub), stations, genres, countries, geos, junction tables, favorites, listen_history, visibility_rules.
Docker Compose: service postgres image pinning 18.2; optional future service for vapor app or run Vapor locally against Compose DB.
6. Admin React app
- Stack: React (Vite or CRA — prefer Vite), React Router, React Bootstrap components for forms, tables, navbar, alerts.
- Screens: Login → Dashboard → Stations (edit, hide, assign geo/country/genre, age/geo restrictions) → Users (read-only list + identifiers for support).
- API: same backend admin routes; CORS configured for dev origin.
7. Implementation phases
Phase 0 — Repository & infra
- Docker Compose: Postgres 18.2, volumes, env vars,
.env.example. - Monorepo folders:
backend/,admin-web/,RadioStoreApp/(orApps/tvOS/+Packages/).
Phase 1 — Backend foundation
- Vapor project, config, Fluent + Postgres driver.
- Migrations: taxonomy + stations + users + favorites + history + visibility rules.
- Public catalog endpoint with hide/geo/age logic (stub age until SIWA path exists).
- Anonymous registration + merge logic unit-tested.
Phase 2 — Auth & user APIs
- Apple ID token verification endpoint.
- JWT/session for clients; merge anonymous → Apple user.
- Favorites + listen history endpoints.
Phase 3 — tvOS app (vertical slice)
- SPM packages: Core, API, Player (minimal playable prototype).
- SwiftData models mirroring server IDs.
- Splash → catalog (mock API then real) → Player.
- Profile: SIWA; persist tokens via Keychain; sync favorites/history.
Phase 4 — Admin web
- React app with React Bootstrap; admin auth; CRUD stations and policies; user list.
Phase 5 — Hardening
- Rate limits, logging, pagination, image CDN strategy, App Store / entitlement checklist for SIWA and age-related APIs, load testing on catalog.
8. Open decisions (resolve early)
- Exact catalog hierarchy — strict Geo→Country→Genre vs genre as cross-filter.
- Anonymous age policy — show all non-age-gated stations vs conservative minimum set.
- Artwork hosting — S3-compatible vs backend-served URLs.
- Admin authentication — SSO vs email/password vs API keys for MVP.
9. Definition of done (MVP)
- tvOS: browse grouped catalog, play mp3 and m3u8, SIWA, favorites + visible history.
- Backend: REST satisfies above; Postgres via Compose; merge on SIWA.
- Admin: manage stations and hides; list users; configure geo/age visibility.
Last updated: 2026-05-04