Files
RadioStore/docs/plan.md
T
2026-05-04 23:54:47 +03:00

7.7 KiB
Raw Blame History

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)
  • visibilitypublished / 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:

  1. Geo (e.g. continent or macro-region)
  2. Country (within geo)
  3. 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_id or JWT pairing; used until Sign in with Apple completes.
  • Authenticated account — Apple ID subject (sub) after SIWA; server verifies identity token (Apples 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 Apples 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; SplashCatalog (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/ (or Apps/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)

  1. Exact catalog hierarchy — strict Geo→Country→Genre vs genre as cross-filter.
  2. Anonymous age policy — show all non-age-gated stations vs conservative minimum set.
  3. Artwork hosting — S3-compatible vs backend-served URLs.
  4. 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