feat(subsonic): browsing, search, media, playlist, annotation endpoints
Thin adapters over the existing services/repositories (no business logic): - system: ping (auth check), getLicense - browsing: getArtists/getArtist/getAlbum, getAlbumList(2) (newest/alpha/random), getSong, getGenres, getMusicFolders/getIndexes/getMusicDirectory (one folder) - search: search3 (delegates to the library repos) - media: stream + download (reuse StreamingService, honor Range); getCoverArt returns a placeholder until the cover pipeline lands - playlists: get/create/update/delete over the playlist repo (owner-scoped) - annotation: star/unstar → append-only like log, scrobble → play history, setRating → clean no-op - all endpoints also accept the .view suffix and GET+POST for client compat Repo support: album list ordering (newest/random), track genre facets. README documents the mandatory-HTTPS requirement and app-password workflow. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -70,3 +70,37 @@ The DB URL is injected from app settings — never hardcoded in `alembic.ini`.
|
||||
All settings come from environment variables (or `.env` in dev). See
|
||||
[`.env.example`](.env.example). External services (ML, AcoustID, MusicBrainz)
|
||||
are **optional** — the backend degrades gracefully when they are absent.
|
||||
|
||||
## Subsonic API (`/rest`)
|
||||
|
||||
A Subsonic-compatible API is mounted at `/rest`, so standard clients (Symfonium,
|
||||
DSub, play:Sub, …) can browse the library and stream. It is a thin adapter over
|
||||
the native services — it adds no business logic of its own.
|
||||
|
||||
**HTTPS is mandatory.** Subsonic authentication puts the credential in the URL
|
||||
(`t=md5(password+salt)&s=…`, or the legacy `p=`), so `/rest` must only ever be
|
||||
exposed behind TLS (terminate at the reverse proxy). Never serve it over plain
|
||||
HTTP.
|
||||
|
||||
### App-passwords
|
||||
|
||||
Subsonic auth needs a recoverable secret, but login passwords are stored as a
|
||||
one-way argon2 hash. So Subsonic clients authenticate against a separate,
|
||||
per-user **app-password** — high-entropy, random, and encrypted at rest with a
|
||||
key derived from `SUBSONIC_SECRET_KEY` (set this to a strong random string in
|
||||
prod; rotating it invalidates all stored app-passwords).
|
||||
|
||||
Self-service lifecycle (native API, needs a normal JWT login):
|
||||
|
||||
```bash
|
||||
GET /api/v1/users/me/subsonic-password # reveal (generated lazily on first read)
|
||||
POST /api/v1/users/me/subsonic-password # rotate
|
||||
# admin, for any user:
|
||||
POST /api/v1/admin/users/{user_id}/subsonic-password
|
||||
```
|
||||
|
||||
Point the client at the instance URL, use your **username** + the revealed
|
||||
**app-password** (not your login password).
|
||||
|
||||
> **Cover art** (`getCoverArt`) currently returns a placeholder — the cover
|
||||
> pipeline (`/api/v1/.../cover` endpoints) is not implemented yet.
|
||||
|
||||
Reference in New Issue
Block a user