- Library view redesigned to match mockup: unified view with search input, filter pills, date-group headers, and recording-row style - Mini waveform bars moved to SessionPage individual recording rows - Play buttons removed from Library session rows - Fixed Invalid Date for API datetime strings (slice to date part) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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 |
| uv | Python package manager (backend) | curl -Lsf https://astral.sh/uv/install.sh | sh |
| Task | Task runner (Taskfile.yml) |
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-alpinevia podman automatically.
Quick start
1. Configure environment
cp .env.example .env
# Edit .env — set SECRET_KEY, INTERNAL_SECRET, Nextcloud credentials, domain
Generate secrets:
openssl rand -hex 32 # paste as SECRET_KEY
openssl rand -hex 32 # paste as INTERNAL_SECRET
2. Start all services
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:
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:
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:
task dev # foreground, streams all logs
Follow logs:
task logs # all services
task dev:logs SERVICE=api # single service
Restart a single service after a code change:
task dev:restart SERVICE=api
Database migrations
# Apply pending migrations
task migrate
# Create a new migration from model changes
task migrate:auto M="add instrument field to band_member"
Useful shells
task shell:api # bash in the API container
task shell:db # psql
task shell:redis # redis-cli
Testing
After every feature — run this
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).
task ci
Stages:
lint ──► typecheck ──► test:web ──► test:api (unit + integration)
──► test:worker
──► test:watcher
Individual test commands
# 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:
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.