Search hits omit album covers and artist images. The worker now reads the full song and artist, stores the description, and leaves Genius image URLs unchanged on mobile.
Co-authored-by: Cursor <cursoragent@cursor.com>
- Migration 00008: upload_batches, upload_files, upload_quota.
- Quota reserved under a row lock when a batch is created; every status
change goes through one transition that frees space in the same
transaction. SHA-256 duplicates are rejected before any upload.
- tus 1.0.0 core (HEAD/PATCH) for the author only; offset is the real
staging file size, data beyond the declared size is refused (ADR-015).
- Processing worker: ffprobe checks, SHA-256 verification, exact and
Chromaprint duplicates against library and queue, metadata from tags
or the dropped folder path, possible_duplicate on metadata match, and
the target path from write_template reusing existing folder spelling.
- Import worker: edited tags written with ffmpeg stream copy, file
written by storage-agent, point scan of the new file, imported status;
idempotent and resumed after restart. Stale uploads expire after 24 h,
orphan staging files are removed at startup.
- API: quota, precheck, batches, my uploads, withdraw, admin list,
metadata edit, bulk review, retry and queue settings.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GET /api/v1/app/android/latest reads the latest Gitea release with the
server token (cached for 10 minutes); the download endpoint caches the
APK under /updates, verifies its SHA-256 and serves it with Range.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- /wave/options, POST /wave/sessions (activity, mood, seed),
/next with reactions, PATCH to switch the context on the fly, DELETE
- the recommender is called with an 800 ms budget; on failure the api
serves a fallback wave of likes, favourite artists and random
available tracks with the same dislike / recent / served filters
- every served track is an impression with its source and model; wave
events (context_type wave) set its outcome, reactions win
- library status shows the analysis queue and 7-day wave metrics:
listen-through, early skip and like rate
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- tracks carry a stream_url signed for the user for 12 h and the
user's reaction; /stream checks the signature, expiry and that the
user is not blocked, then hands the file to nginx by X-Accel-Redirect
(or serves it with Range support when X_ACCEL=false)
- original by default; formats browsers can't play and explicit
high/medium/low go through Opus transcoding, never upwards: a lossy
source under the target bitrate stays original; transcodes are
cached per file and size, deduplicated in flight, limited by
TRANSCODE_CONCURRENCY and trimmed hourly by LRU to the size limit
- /events takes up to 500 events with client UUIDs, drops repeats and
events older than 30 days, counts plays (≥ half or 4 min, or
finished), early skips and history entries (≥ 30 s)
- history with clearing, likes and dislikes with cursors and totals,
track reactions, playback state with last-write-wins, tracks batch
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- artists, albums and tracks indexes are rebuilt from PostgreSQL next
to the live ones and swapped in, so search keeps working; a rebuild
runs on start, after every scan and when a source is removed
- transliteration fields (Кино ↔ kino) and a retry in the other
keyboard layout (ghbdtn → привет) reported as corrected_query
- /search/suggest (3 artists, 3 albums, 5 tracks), /search by type
with cursor pages, /admin/search/reindex; Meilisearch is part of
/readyz
- found ids are hydrated from the catalog, so a lagging index never
returns deleted entries
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- schema for sources, path patterns, media files, artists, albums,
tracks, genres (migration 00003)
- path patterns (RE2 with named groups, skip), merge with tags by
source priority, fallbacks from the file name and folder, name
normalization and multi-artist split
- scanner: walk, skip unchanged files by size and mtime, ffprobe in 8
workers with a single catalog writer, renames by content hash keep
the track, duplicates fold into one track with the best file as
primary, missing files are kept 30 days
- storage-agent mounts NFS with validated server, export and an option
whitelist, no shell; test mount counts audio files
- admin API: sources CRUD, connection test, patterns with preview,
scan, library status, file errors, host folder picker; scheduled
scans and health checks; mounts are restored on start
- catalog API: artist, artist albums, album with tracks, track
- api image is alpine with ffmpeg for ffprobe
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- /admin/users: list with search, role filter and cursor pages; create
with a one-time temporary password; rename, change role, block;
reset password; delete
- every change is checked against the role policy, a forbidden field
rejects the whole patch; the last active admin is locked in the
transaction and cannot be demoted, blocked or deleted
- role change and blocking revoke access tokens at once; blocking and
password reset also end all sessions
- /admin/audit: newest-first journal with actor names
- path and query parameter errors are problem+json now
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- users, refresh_tokens and audit_log schema (migration 00002)
- argon2id passwords with rehash on login, password policy, lockout
with exponential back-off and a per-IP rate limit in Redis
- Ed25519 JWT access tokens with kid-based key rotation; rotating
refresh tokens with reuse detection (cookie for web, body for Android)
- bootstrap admin on first start; must_change_credentials and
must_change_password gate every other endpoint
- /auth/login, /auth/refresh, /auth/logout(-all), /me, /me/password,
/me/bootstrap-credentials, /me/sessions; server-info reports
bootstrap_required
- role policy from docs/auth-and-roles.md with table tests
- problem+json errors with field errors; ru/en translations of the codes
- install.sh and update.sh generate JWT_PRIVATE_KEY
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ci / docker (образы собираются — их сервер собирает сам при обновлении) (services/api/Dockerfile, storage-agent, storage-agent) (push) Canceled after 0s
Security and robustness:
- storage-agent rejects an empty INTERNAL_TOKEN (config notEmpty + middleware)
- api takes the client IP only from X-Real-IP (chi RealIP trusted True-Client-IP)
- /readyz reports ok/fail and logs the cause; checks run in parallel with a timeout
- storage-agent shuts down gracefully; shared httpx.NewServer/Serve with IdleTimeout
- goose migrations take a Postgres advisory lock
- recommender readyz: per-check timeout, redis socket timeouts
Clients:
- Android release allows cleartext HTTP for LAN servers (expo-build-properties)
- checkServer times out after 10s; connect button is disabled while checking
Deploy and CI:
- install.sh hands cache/uploads/updates/models to the non-root service users
- update.sh refuses downgrades unless PIPU_ALLOW_DOWNGRADE=1
- recommender/analyzer get only the env they need; required secrets fail fast
- nginx security headers and CSP; strip True-Client-IP
- dev compose binds ports to 127.0.0.1; postgres/redis images pinned
- version.mjs fails on a malformed explicit tag instead of releasing 0.0.0-dev
- contract check catches untracked generated files
- final releases fail without a release keystore; changelog skips rc tags
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>