Files
RadioStore/docs/DEVELOPMENT.md
T
2026-05-05 20:56:06 +03:00

13 KiB

RadioStore — development guide

This document describes the implemented Apple TV client, Swift packages, and React admin web app, how they fit together, and how to run them quickly. Roadmap and product intent remain in plan.md; compact editor handoff notes live in CURSOR_SESSION_CONTEXT.md.


1. Repository layout (monorepo)

Path Role
Apps/RadioStoreTV/ Xcode tvOS 17 application. Depends on the local SPM package Packages/App (RadioStoreApp).
Packages/Core/ Shared domain types (RadioStation, StreamKind, mock sample station).
Packages/API/ Swift package RadioStoreAPI: REST client, DTOs, file-based mock client, RadioStoreAPIServing protocol.
Packages/Player/ Playback: RadioPlayerController + RadioPlayerView (Siri Remote Play/Pause and surface up/down for mix volume).
Packages/App/ Swift package RadioStoreApp: splash, tab shell, browse UI, profile placeholder, environment-driven API wiring, bundled JSON fixtures.
admin-web/ React admin (Vite): login, stations, users — section 8 below and admin-web/README.md.
backend/ Minimal Vapor 4 scaffold + Package.resolvedbackend/README.md. Full /api/v1 + /admin/v1 still per plan.md.
docker-compose.yml Profile compile: compile backend (swift:5.10-jammy) and admin (node:20-bookworm-slim); scripts/docker-compile-all.sh.

2. Apple TV application (what ships today)

2.1 Entry point

2.2 User-facing flow

  1. SplashRadioStoreSplashView: branding; Continue or short auto-advance.
  2. Main shellRadioStoreMainView: TabView with two tabs:
    • BrowseCatalogBrowseView: Geo → Country → genres (sections) → station → player.
    • ProfileProfileView: placeholder for Sign in with Apple and synced data (not wired yet).

2.3 Catalog data sources (Browse)

Browse resolves geos in this order:

  1. If an RadioStoreAPIServing implementation is active, fetchCatalog(bearerToken: nil) runs; success maps CatalogPayload into UI models via CatalogBrowseMapping.
  2. On failure or when no API service is configured, the UI falls back to in-memory CatalogMockData.

Which service is created is determined by RadioStoreProcessEnvironment (see §5) unless you pass an explicit live configuration into RadioStoreRootView.

2.4 Playback


3. Swift packages and responsibilities

3.1 Core (Packages/Core)

Piece Description
StreamKind mp3 | m3u8 (Codable).
RadioStation id, title, imageURL, streamURL, streamKind; Hashable for navigation.
MockRadioStation.eftelingLive Sample MP3 URL for simulator-friendly checks.

Platforms: tvOS 17, macOS 14 (macOS supports running RadioStoreAPI unit tests that depend on Core).

3.2 RadioStoreAPI (Packages/API, Xcode SPM folder name API)

Product: library RadioStoreAPI.

Piece Description
RadioStoreAPIServing Protocol implemented by both the HTTP client and the file mock; Browse depends on any RadioStoreAPIServing. Includes catalogTaskIdentity for SwiftUI .task(id:).
RadioStoreAPIConfiguration Live API base URL only (no trailing slash); paths such as api/v1/catalog are appended by the client.
RadioStoreAPIClient URLSession-based REST implementation aligned with plan.md §5.
FileMockRadioStoreAPIClient Reads canned JSON from a directory; no network (§6).
DTOs CatalogPayload / regions / countries / genres / stations (CatalogStationDTO uses Core.StreamKind). Session: DeviceRegisterRequest, DeviceRegisterResponse, AppleAuthRequest, AppleAuthResponse. User: FavoritesPayload, ListenEventsBatchRequest, ListenEventDTO. JSON uses snake_case keys via explicit CodingKeys.
RadioStoreAPIError invalidURL, invalidResponse, httpStatus, encoding, decoding, fixtureMissing.

Tests: run from repo root:

cd Packages/API && swift test

3.3 Player (Packages/Player)

Depends on Core. Exposes RadioPlayerController and RadioPlayerView only (UI + playback).

3.4 RadioStoreApp (Packages/App)

Depends on Core, Player, RadioStoreAPI.

Piece Description
RadioStoreRootView Public entry: init(apiConfiguration:bundle:). Explicit RadioStoreAPIConfiguration forces live RadioStoreAPIClient and skips process-environment routing. Otherwise RadioStoreProcessEnvironment.resolveAPIBackend(bundle:) decides file mocks vs live vs none.
RadioStoreProcessEnvironment Maps ProcessInfo.processInfo.environment to none | live | fileMocks.
Bundled fixtures Packages/App/Sources/RadioStoreApp/Resources/MockAPI/ included via SPM resources: [.process("Resources")].

4. REST surface (client expectations)

The HTTP client calls paths relative to the configured base URL:

Method Path Response / notes
GET api/v1/catalog CatalogPayload
GET api/v1/stations/{uuid} CatalogStationDTO
POST api/v1/device/register DeviceRegisterResponse
POST api/v1/auth/apple AppleAuthResponse
GET api/v1/me/favorites FavoritesPayload
PUT api/v1/me/favorites body FavoritesPayload, empty success
POST api/v1/me/listen-events body ListenEventsBatchRequest, empty success

Optional Authorization: Bearer … is supported where the protocol allows a token.


5. Debug mode: process environment (no server)

When RadioStoreRootView() is constructed without an explicit apiConfiguration, RadioStoreProcessEnvironment reads:

Variable Meaning
RADIOSTORE_USE_FILE_MOCKS Truthy values: 1, true, YES, yes, TRUE. Enables FileMockRadioStoreAPIClient.
RADIOSTORE_MOCK_FIXTURES_DIR Absolute path to a folder of JSON files. If unset while file mocks are on, the app uses the bundled MockAPI directory resolved via the RadioStoreApp resource bundle.
RADIOSTORE_API_BASE_URL Live REST base URL when file mocks are off.

Precedence: file mocks → live URL → no API client (Browse uses CatalogMockData only).

Xcode: Product → Scheme → Edit Scheme → Run → Environment Variables. The shared scheme RadioStoreTV.xcscheme ships disabled templates for these variables; enable RADIOSTORE_USE_FILE_MOCKS (and optionally RADIOSTORE_MOCK_FIXTURES_DIR) when debugging without a backend.

If RADIOSTORE_MOCK_FIXTURES_DIR points into the repo (for example the MockAPI source folder), you can edit JSON without rebuilding resource copies inside the app bundle. Verify the path matches your machine layout (especially $(SRCROOT)).


6. File mock fixture names

Under the chosen fixtures directory (bundled or RADIOSTORE_MOCK_FIXTURES_DIR):

File Used for
catalog.json CatalogPayload
station_{lowercase-uuid}.json Per-id station; if absent, station.json is used
device_register.json DeviceRegisterResponse
auth_apple.json AppleAuthResponse
favorites.json FavoritesPayload

replaceFavorites and appendListenEvents succeed without extra fixture files.

Shape reference: bundled catalog.json and Packages/API/Tests/RadioStoreAPITests/.


7. Quick start (development)

7.1 Prerequisites

  • Xcode with tvOS 17 SDK.
  • Apple TV Simulator (e.g. Apple TV) installed.

7.2 Run the app in Xcode

  1. Open Apps/RadioStoreTV/RadioStoreTV.xcodeproj.
  2. Select the RadioStoreTV scheme and an Apple TV simulator.
  3. Run (⌘R).

Default behavior (no scheme env vars): no HTTP client → Browse uses CatalogMockData; playback still works with the bundled demo stream URLs.

7.3 Browse using bundled JSON “API” (no server)

  1. Edit Scheme → Run → Environment Variables.
  2. Enable RADIOSTORE_USE_FILE_MOCKS (1).
  3. Optional: enable RADIOSTORE_MOCK_FIXTURES_DIR and point it at
    Packages/App/Sources/RadioStoreApp/Resources/MockAPI
    (adjust path / use $(SRCROOT) as in the shared scheme).
  4. Run again: Browse loads catalog.json through FileMockRadioStoreAPIClient.

7.4 Browse against a real API

  1. Ensure file mocks are off.
  2. Set RADIOSTORE_API_BASE_URL to your server origin (no trailing slash), for example http://localhost:8080.
  3. Mind ATS and simulator networking if you use plain HTTP.

Alternatively, pass RadioStoreRootView(apiConfiguration: RadioStoreAPIConfiguration(baseURL:)) from the app entry (forces live client; ignores env routing).

7.5 Command-line build (CI or sanity check)

From the repo root:

cd Apps/RadioStoreTV && xcodebuild \
  -scheme RadioStoreTV \
  -destination 'platform=tvOS Simulator,name=Apple TV' \
  build

7.6 API package tests (macOS host)

cd Packages/API && swift test

7.7 Admin panel (React)

See admin-web/README.md. Short version:

cd admin-web && cp .env.example .env && npm install && npm run dev

Configure VITE_ADMIN_API_BASE_URL and CORS on the server. Screens: Login (email/password or pasted bearer token), Dashboard, Stations (list + edit/create), Users (read-only).

7.8 Docker: compile backend + admin

docker-compose.yml defines profile compile (nothing starts by default). Bind-mounts ./backend and ./admin-web, writes backend/.build (Swift PM layout inside that folder) and admin-web/dist.

# Optional: repo-root `.env` for `VITE_ADMIN_API_BASE_URL` during admin production build (see `.env.example`).
docker compose --profile compile run --rm backend-compile
docker compose --profile compile run --rm admin-compile

Or: scripts/docker-compile-all.sh.

The swift:5.10-jammy image used for the backend is large on first pull.


8. Admin web (admin-web/)

Stack matches plan.md §6: Vite, React 18, React Router 6, Bootstrap 5 + react-bootstrap.

Area Location
Routes / shell admin-web/src/App.tsx, AdminLayout.tsx
Auth (stored bearer token) AuthContext.tsx, ProtectedRoute.tsx
HTTP client api/client.ts (Authorization: Bearer …)
Typed admin calls api/adminApi.ts
Shared TS shapes types/admin.ts

JSON field names use snake_case (image_url, stream_url, …) so payloads align naturally with a future Vapor API.


9. Further reading