Mistral Vibe 659598913b Remove play button from Library session rows
Play buttons don't make sense at the session level since sessions
group multiple recordings. Removed from both session rows and
unattributed song rows.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 18:44:02 +02:00
2026-03-29 20:44:23 +02:00
2026-03-28 21:53:03 +01:00
2026-04-01 09:56:19 +02:00
2026-03-28 21:53:03 +01:00
2026-04-01 09:56:19 +02:00
2026-03-29 20:44:23 +02:00

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-alpine via 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: ~6090 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.

Description
No description provided
Readme 722 KiB
Languages
Python 55.6%
TypeScript 42.2%
Dockerfile 0.6%
Makefile 0.5%
Shell 0.5%
Other 0.6%