Commit Graph

13 Commits

Author SHA1 Message Date
Mistral Vibe
b5c84ec58c WIP: Working on player 2026-04-08 15:10:52 +02:00
Mistral Vibe
fdf9f52f6f Rework song player view to match design system
- New split layout: waveform/transport/queue left, comment panel right
- Avatar pins above waveform positioned by timestamp with hover tooltips
- Transport bar: speed selector, ±30s skip, 46px amber play/pause, volume
- Comment compose: live timestamp pill, suggestion/issue/keeper tag buttons
- Comment list: per-author colour avatars, amber timestamp seek chips,
  playhead-proximity highlight, delete only shown on own comments
- Queue panel showing other songs in the same session
- Waveform colours updated to amber/dim palette (104px height)
- Add GET /songs/{song_id} endpoint for song metadata
- Add tag field to SongComment (model, schema, router, migration 0005)
- Fix migration 0005 down_revision to use short ID "0004"
- Fix ESLint no-unused-expressions in keyboard shortcut handler

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 21:14:56 +02:00
Mistral Vibe
c1941ed9ac security: httpOnly cookies, rate limiting, nginx headers, SSE sanitization
Auth / token storage:
- JWT is now set as an httpOnly Secure SameSite=Lax cookie on login
- Add POST /auth/logout endpoint that clears the cookie
- get_current_member falls back to rh_token cookie when no Authorization header
- WebSocket auth now accepts cookie (rh_token) or optional ?token= query param
- Frontend removes all localStorage JWT access; uses credentials:"include" on
  every fetch so the httpOnly cookie is sent automatically
- Replace clearToken() with logout() that calls the server logout endpoint
- Non-sensitive rh_session flag in localStorage used only for client-side routing

Rate limiting:
- Add slowapi>=0.1.9 dependency
- /auth/login limited to 10 req/min per IP
- /auth/register limited to 5 req/min per IP

Nginx security headers:
- Add X-Frame-Options, X-Content-Type-Options, Referrer-Policy,
  X-XSS-Protection, Permissions-Policy to all responses

SSE error leakage:
- songs.py nc-scan/stream no longer leaks str(exc) to clients

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 21:11:53 +02:00
Mistral Vibe
a8aba72b3a WIP: Add timestamp to comments and fix frontend errors 2026-03-29 22:06:36 +02:00
Mistral Vibe
02fd556372 feat: remove global Nextcloud config, enforce member-specific storage providers
- Remove global Nextcloud settings from config
- Make NextcloudClient require explicit credentials
- Update for_member() to return None when no credentials
- Modify services to accept optional storage client
- Update routers to pass member storage to services
- Add 403 responses when no storage provider configured
- Update internal endpoints to use member storage credentials

This change enforces that each member must configure their own
Nextcloud storage provider. If no provider is configured,
file operations will return 403 FORBIDDEN instead of falling
back to global placeholders.
2026-03-29 20:06:12 +02:00
Steffen Schuhmann
7c643ff67b fix: replace model_validate(..., update=) with .model_copy(update=)
Pydantic v2 model_validate() does not accept an update kwarg; the correct
pattern is model_validate(obj).model_copy(update={...}).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 15:20:42 +02:00
Steffen Schuhmann
7cad3e544a feat: incremental SSE scan, recursive NC traversal, custom folder support
- nc_scan.py: recursive collect_audio_files (fixes depth-1 bug); scan_band_folder
  yields ndjson events (progress/song/session/skipped/done) for streaming
- songs.py: replace old flat scan with scan_band_folder; add GET nc-scan/stream
  endpoint using _member_from_request so ?token= auth works for fetch-based SSE
- BandPage.tsx: scan button now consumes ndjson stream via fetch+ReadableStream;
  sessions/unattributed invalidated as each song/session event arrives
- session.py: add extract_session_folder() for YYMMDD path extraction
- rehearsal_session.py: get_or_create uses begin_nested() savepoint to handle races
- band.py: add get_by_nc_folder_prefix() for custom nc_folder_path band lookup
- internal.py: nc-upload falls back to prefix match when slug lookup fails
- event_loop.py: remove hardcoded bands/ guard; let internal API handle filtering

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 15:09:42 +02:00
Steffen Schuhmann
dc6dd9dcfd fix: scan visibility, NC folder validation, watcher logging
- nc-scan: detailed INFO logging of every path found, subfolder
  contents and skip reasons; 502 now includes the exact folder and
  error so user sees a real message instead of a blank result
- band creation: if nc_base_path is explicitly given, verify the
  folder exists in Nextcloud before saving — returns 422 with a
  clear message to the user; auto-generated paths still do MKCOL
- songs search: add ?unattributed=true to return songs with no
  session_id (files not in a YYMMDD folder)
- BandPage: show "Unattributed Recordings" section below sessions
  so scanned files without a dated folder always appear
- watcher event_loop: promote all per-activity log lines from DEBUG
  to INFO so they're visible in default Docker Compose log output;
  log normalized path and skip reason for every activity

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 14:11:07 +02:00
Steffen Schuhmann
b882c9ea6d feat(api): auto-link rehearsal sessions on watcher upload and nc-scan
parse_rehearsal_date() extracts YYMMDD / YYYYMMDD from the file path
and get_or_create() a RehearsalSession. Both the watcher nc-upload
endpoint and the nc-scan endpoint now set song.session_id when a
dated folder is detected. Existing songs without a session_id are
back-filled on the next import of the same folder.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 13:41:01 +02:00
Steffen Schuhmann
a779c57a26 feat(api): song search endpoint and PATCH /songs/{id}
GET /bands/{id}/songs/search — filter by title (ILIKE), tags (contains
all), key, BPM range, session_id. All params optional and composable.
PATCH /songs/{id} — update title, status, notes, tags, key, BPM.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 13:39:32 +02:00
Steffen Schuhmann
47bc802775 fix: robust NC activity filter, title extraction, scan result detail
Watcher:
- Accept both NC 22+ (type="file_created") and older NC (subject="created_self")
  so the upload filter works across all Nextcloud versions
- Add .opus to audio_extensions
- Fix tests: set nc.username on mocks, use realistic activity dicts with type field
- Add tests for old NC style, non-band path filter, normalize_nc_path, cursor advance

API:
- Fix internal.py title extraction: always use filename stem (was using
  parts[-2] for >3-part paths, which gave folder name instead of song title)
- nc-scan now returns NcScanResult with folder, files_found, imported, skipped counts
  instead of bare song list — gives the UI actionable feedback

Web:
- Show rich scan result message: folder scanned, count imported, count already registered

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 12:26:58 +02:00
Steffen Schuhmann
b28472c32f fix: resolve job-not-found race and YYMMDD scan folder structure
Race condition (worker "Job not found in DB"):
- RedisJobQueue.enqueue() was pushing job IDs to Redis immediately after
  flush() but before the API transaction committed, so the worker would
  read an ID that didn't exist yet in the DB from its own session.
- Fix: defer the Redis rpush until after session.commit() via a pending-
  push list drained by get_session() after each successful commit.
- Worker: drain stale Redis queue entries on startup to clear any IDs
  left over from previously uncommitted transactions.
- Worker: add 3-attempt retry with 200ms sleep when a job is not found,
  as a safety net for any remaining propagation edge cases.

NC scan folder structure (YYMMDD rehearsal subfolders):
- Previously used dir_name as song title for all files in a subdirectory,
  meaning every file got the folder name (e.g. "231015") as its title.
- Fix: derive song title from Path(sub_rel).stem so each audio file gets
  its own name; use the file's parent path as nc_folder for version grouping.
- Rehearsal folder name stored in song.notes as "Rehearsal: YYMMDD".
- Added structured logging throughout the scan: entries found, per-folder
  file counts, skip/create/import decisions, and final summary count.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 21:58:46 +01:00
Steffen Schuhmann
f7be1b994d Initial commit: RehearsalHub POC
Full-stack self-hosted band rehearsal platform:

Backend (FastAPI + SQLAlchemy 2.0 async):
- Auth with JWT (register, login, /me, settings)
- Band management with Nextcloud folder integration
- Song management with audio version tracking
- Nextcloud scan to auto-import audio files
- Band membership with link-based invite system
- Song comments
- Audio analysis worker (BPM, key, loudness, waveform)
- Nextcloud activity watcher for auto-import
- WebSocket support for real-time annotation updates
- Alembic migrations (0001–0003)
- Repository pattern, Ruff + mypy configured

Frontend (React 18 + Vite + TypeScript strict):
- Login/register page with post-login redirect
- Home page with band list and creation form
- Band page with member panel, invite link, song list, NC scan
- Song page with waveform player, annotations, comment thread
- Settings page for per-user Nextcloud credentials
- Invite acceptance page (/invite/:token)
- ESLint v9 flat config + TypeScript strict mode

Infrastructure:
- Docker Compose: PostgreSQL, Redis, API, worker, watcher, nginx
- nginx reverse proxy for static files + /api/ proxy
- make check runs all linters before docker compose build

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 21:53:03 +01:00