corpus

Log | Files | Refs | README | LICENSE

commit 81d101a0dd916cdeed3664734be04a7789e0d63c
parent 7fadcf16e174044eb37a5c203ad919a8da5b5213
Author: mtmn <miro@haravara.org>
Date:   Thu, 23 Apr 2026 15:06:56 +0200

feat: add docs for submission endpoint

Diffstat:
MREADME.md | 25+++++++++++++++++++++++++
Mdocs/architecture.md | 4+++-
Mdocs/duckdb.md | 8++++++++
3 files changed, 36 insertions(+), 1 deletion(-)

diff --git a/README.md b/README.md @@ -47,6 +47,31 @@ npm run release npx spago run ``` +### Scrobbling API + +Corpus provides a [ListenBrainz-compatible](https://listenbrainz.readthedocs.io/en/latest/users/api-resources.html#post--1-submit-listens) endpoint for submitting scrobbles directly. This allows you to use any scrobbler that supports custom ListenBrainz endpoints (like [Pano Scrobbler](https://github.com/kawaiiDoge/PanoScrobbler) or [Simple Scrobbler](https://github.com/waicool20/Simple-Scrobbler)). + +#### Endpoint + +`POST /1/submit-listens` + +#### Authentication + +The API uses token-based authentication. A unique API token is automatically generated for each user when they first start the application. You can find your token in the server logs on startup: + +```text +[INFO] User 'mtmn' token: 550e8400-e29b-41d4-a716-446655440000 +``` + +Include the token in the `Authorization` header of your requests: + +```text +Authorization: Token <your-token> +``` + +#### Payload Format + +The endpoint accepts standard ListenBrainz JSON payloads. See the [ListenBrainz API documentation](https://listenbrainz.readthedocs.io/en/latest/users/api-resources.html#post--1-submit-listens) for details. ### Environment variables diff --git a/docs/architecture.md b/docs/architecture.md @@ -6,7 +6,7 @@ Corpus is a self-hosted music listening history dashboard and analytics service. ### Web Server The server is built with PureScript running on Node.js. It handles several core responsibilities: -- **HTTP API**: Serves the frontend, scrobble data (with filtering/pagination), statistics, and similar tracks (via [cosine.club](https://cosine.club)). +- **HTTP API**: Serves the frontend, scrobble data (with filtering/pagination), statistics, and similar tracks (via [cosine.club](https://cosine.club)). It also provides a **ListenBrainz-compatible scrobble submission endpoint**. - **ListenBrainz Sync**: A background process that polls the ListenBrainz API every 60 seconds to fetch new scrobbles. - **Last.fm Sync**: A background process that polls the Last.fm API every 60 seconds to fetch new scrobbles. Both syncs write to the same `scrobbles` table; duplicate timestamps are silently ignored. - **Metadata Enrichment**: A background process that identifies scrobbles with missing metadata (genres, labels, release years) and fetches information from MusicBrainz, Last.fm, and Discogs. @@ -27,6 +27,7 @@ Corpus uses **DuckDB** for its primary data storage. Each user has their own dat - **Schema**: - `scrobbles`: Stores the core listening history (timestamp, track, artist, album, MBIDs). The `listened_at` Unix timestamp is the primary key — scrobbles from ListenBrainz and Last.fm deduplicate naturally. - `release_metadata`: Stores enriched metadata indexed by MusicBrainz Release ID (MBID), including genre, label, and release year. + - `api_tokens`: Stores hashed user API tokens for scrobble submission. Tokens are hashed with SHA-256 before storage. - **Performance**: DuckDB's columnar storage allows for extremely fast analytical queries across large listening histories. ### Storage @@ -41,6 +42,7 @@ Corpus runs as a single server process serving multiple users. User configuratio - `/` and `/u/<slug>` — serve the Elm SPA for the root user and named users respectively - `/proxy?user=<slug>`, `/stats?user=<slug>`, `/cover?user=<slug>` — shared API endpoints, user selected via query parameter - `/healthz?user=<slug>` — liveness check; pings the user's DuckDB connection +- `/1/submit-listens` — ListenBrainz-compatible scrobble submission endpoint (requires `Authorization: Token <token>` header) - `/metrics` — Prometheus metrics (no user parameter; covers all users; only available when `METRICS_ENABLED=true`) ### Configuration diff --git a/docs/duckdb.md b/docs/duckdb.md @@ -29,6 +29,14 @@ Stores enriched metadata fetched from MusicBrainz, Last.fm, and Discogs. | `release_year` | INTEGER | Year of release | | `genre_checked_at` | INTEGER | Timestamp of last enrichment attempt | +### `api_tokens` +Stores hashed user API tokens for scrobble submission. + +| Column | Type | Description | +| :--- | :--- | :--- | +| `slug` | VARCHAR | User slug (Primary Key) | +| `hashed_token` | VARCHAR | SHA-256 hash of the API token (Unique) | + ## Application Usage The application interacts with DuckDB via a PureScript FFI layer (`src/Db.js` and `src/Db.purs`).