- Add BandSettingsPage (/bands/:id/settings/:panel) with Members, Storage, and Band Settings panels matching the mockup design - Strip members list, invite controls, and NC folder config from BandPage — library view now focuses purely on recordings workflow - Add band-scoped nav section to AppShell sidebar (Members, Storage, Band Settings) with correct per-panel active states - Fix amAdmin bug: was checking if any member is admin; now correctly checks if the current user holds the admin role - Add 31 vitest tests covering BandPage cleanliness, routing, access control (admin vs member), and per-panel mutation behaviour - Add test:web, test:api:unit, test:feature (post-feature pipeline), and ci tasks to Taskfile; frontend tests run via podman node:20-alpine - Add README with architecture overview, setup guide, and test docs - Add @testing-library/dom and @testing-library/jest-dom to package.json Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
281 lines
8.8 KiB
Markdown
281 lines
8.8 KiB
Markdown
# 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.
|