# RehearsalHub A web platform for bands to relisten to recorded rehearsals, drop timestamped comments, annotate moments, and collaborate asynchronously — all on top of your own storage (Nextcloud, Google Drive, S3, local). --- ## Architecture ``` ┌─────────┐ HTTP/WS ┌──────────────┐ asyncpg ┌──────────┐ │ React │ ──────────► │ FastAPI │ ──────────► │ Postgres │ │ (Vite) │ │ (Python) │ └──────────┘ └─────────┘ └──────┬───────┘ │ Redis pub/sub ┌──────────┴──────────┐ │ │ ┌──────▼──────┐ ┌──────────▼──────┐ │ Audio Worker │ │ NC Watcher │ │ (waveforms) │ │ (file polling) │ └─────────────┘ └─────────────────┘ ``` | Service | Language | Purpose | |---|---|---| | `web` | TypeScript / React | UI — player, library, settings | | `api` | Python / FastAPI | REST + WebSocket backend | | `worker` | Python | Audio analysis, waveform generation | | `watcher` | Python | Polls Nextcloud for new files | | `db` | PostgreSQL 16 | Primary datastore | | `redis` | Redis 7 | Task queue, pub/sub | Files are **never copied** to RehearsalHub servers. The platform reads recordings directly from your own storage. --- ## Prerequisites | Tool | Purpose | Install | |---|---|---| | **Podman** + `podman-compose` | Container runtime | [podman.io](https://podman.io) | | **uv** | Python package manager (backend) | `curl -Lsf https://astral.sh/uv/install.sh \| sh` | | **Task** | Task runner (`Taskfile.yml`) | [taskfile.dev](https://taskfile.dev) | | **Node 20** | Frontend (runs inside podman — not needed locally) | via `podman run node:20-alpine` | > Node is only required inside a container. All frontend commands pull `node:20-alpine` via podman automatically. --- ## Quick start ### 1. Configure environment ```bash cp .env.example .env # Edit .env — set SECRET_KEY, INTERNAL_SECRET, Nextcloud credentials, domain ``` Generate secrets: ```bash openssl rand -hex 32 # paste as SECRET_KEY openssl rand -hex 32 # paste as INTERNAL_SECRET ``` ### 2. Start all services ```bash task up # starts db, redis, api, audio-worker, nc-watcher, web (nginx) task migrate # run database migrations ``` Or for first-time setup with Nextcloud scaffolding: ```bash task setup # up + wait for NC + configure NC + seed data ``` ### 3. Open the app Visit `http://localhost:8080` (or your configured `DOMAIN`). --- ## Development Start the backend with hot reload and mount source directories: ```bash task dev:detach # start db, redis, api, worker, watcher in dev mode (background) task dev:web # start Vite dev server at http://localhost:3000 (proxies /api) ``` Or run both together: ```bash task dev # foreground, streams all logs ``` Follow logs: ```bash task logs # all services task dev:logs SERVICE=api # single service ``` Restart a single service after a code change: ```bash task dev:restart SERVICE=api ``` ### Database migrations ```bash # Apply pending migrations task migrate # Create a new migration from model changes task migrate:auto M="add instrument field to band_member" ``` ### Useful shells ```bash task shell:api # bash in the API container task shell:db # psql task shell:redis # redis-cli ``` --- ## Testing ### After every feature — run this ```bash task test:feature ``` This runs the full **post-feature pipeline** (no external services required): | Step | What it checks | |---|---| | `typecheck:web` | TypeScript compilation errors | | `test:web` | React component tests (via podman + vitest) | | `test:api:unit` | Python unit tests (no DB needed) | | `test:worker` | Worker unit tests | | `test:watcher` | Watcher unit tests | Typical runtime: **~60–90 seconds**. --- ### Full CI pipeline Runs everything including integration tests against a live database. **Requires services to be up** (`task dev:detach && task migrate`). ```bash task ci ``` Stages: ``` lint ──► typecheck ──► test:web ──► test:api (unit + integration) ──► test:worker ──► test:watcher ``` --- ### Individual test commands ```bash # Frontend task test:web # React/vitest tests (podman, no local Node needed) task typecheck:web # TypeScript type check only # Backend — unit (no services required) task test:api:unit # API unit tests task test:worker # Worker tests task test:watcher # Watcher tests # Backend — all (requires DB + services) task test:api # unit + integration tests with coverage task test # all backend suites # Integration only task test:integration # API integration tests (DB required) # Lint task lint # ruff + mypy (Python), eslint (TS) task format # auto-format Python with ruff ``` --- ### Frontend test details Frontend tests run inside a `node:20-alpine` container via podman and do not require Node installed on the host: ```bash task test:web # equivalent to: podman run --rm -v ./web:/app:Z -w /app node:20-alpine \ sh -c "npm install --legacy-peer-deps --silent && npm run test" ``` Tests use **vitest** + **@testing-library/react** and are located alongside the source files they test: ``` web/src/pages/ BandPage.tsx BandPage.test.tsx ← 7 tests: library view cleanliness BandSettingsPage.tsx BandSettingsPage.test.tsx ← 24 tests: routing, access control, mutations web/src/test/ setup.ts ← jest-dom matchers helpers.tsx ← QueryClient + MemoryRouter wrapper ``` --- ## Project structure ``` rehearshalhub/ ├── api/ Python / FastAPI backend │ ├── src/rehearsalhub/ │ │ ├── routers/ HTTP endpoints │ │ ├── models/ SQLAlchemy ORM models │ │ ├── repositories/ DB access layer │ │ ├── services/ Business logic │ │ └── schemas/ Pydantic request/response schemas │ └── tests/ │ ├── unit/ Pure unit tests (no DB) │ └── integration/ Full HTTP tests against a real DB │ ├── web/ TypeScript / React frontend │ └── src/ │ ├── api/ API client functions │ ├── components/ Shared components (AppShell, etc.) │ ├── pages/ Route-level page components │ └── test/ Test helpers and setup │ ├── worker/ Audio analysis service (Python) ├── watcher/ Nextcloud file polling service (Python) ├── scripts/ nc-setup.sh, seed.sh ├── traefik/ Reverse proxy config ├── docker-compose.yml Production compose ├── docker-compose.dev.yml Dev overrides (hot reload, source mounts) ├── Taskfile.yml Task runner (preferred) └── Makefile Makefile aliases (same targets) ``` --- ## Key design decisions - **Storage is always yours.** RehearsalHub never copies audio files. It reads them directly from Nextcloud (or other providers) on demand. - **Date is the primary axis.** The library groups recordings by session date. Filters narrow within that structure — they never flatten it. - **Band switching is tenant-level.** Switching bands re-scopes the library, settings, and all band-specific views. - **Settings are band-scoped.** Member management, storage configuration, and band identity live at `/bands/:id/settings`, not in the library view. --- ## Environment variables | Variable | Required | Description | |---|---|---| | `SECRET_KEY` | ✅ | 32-byte hex, JWT signing key | | `INTERNAL_SECRET` | ✅ | 32-byte hex, service-to-service auth | | `DATABASE_URL` | ✅ | PostgreSQL connection string | | `REDIS_URL` | ✅ | Redis connection string | | `NEXTCLOUD_URL` | ✅ | Full URL to your Nextcloud instance | | `NEXTCLOUD_USER` | ✅ | Nextcloud service account username | | `NEXTCLOUD_PASS` | ✅ | Nextcloud service account password | | `DOMAIN` | ✅ | Public domain (used by Traefik TLS) | | `ACME_EMAIL` | ✅ | Let's Encrypt email | | `POSTGRES_DB` | ✅ | Database name | | `POSTGRES_USER` | ✅ | Database user | | `POSTGRES_PASSWORD` | ✅ | Database password | See `.env.example` for the full template.