2026-04-08 18:38:28 +02:00
2026-03-29 20:44:23 +02:00
2026-03-28 21:53:03 +01: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
2026-04-08 08:12:05 +00:00
2026-04-08 15:10:52 +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%