corpus

Log | Files | Refs | README | LICENSE

commit 709e79732448b73df60384f3cc765cb8d3d65a64
parent d58489b29506923144e34b634541427f8dbcc3c2
Author: mtmn <miro@haravara.org>
Date:   Mon, 13 Apr 2026 18:55:21 +0200

chore: rename to corpus

Diffstat:
M.env.example | 4++--
MContainerfile | 2+-
MREADME.md | 8++++----
Mdevenv.nix | 2+-
Mdocs/architecture.md | 10+++++-----
Mdocs/duckdb.md | 6+++---
Mflake.nix | 30+++++++++++++++---------------
Mjustfile | 4++--
Mpackage-lock.json | 4++--
Mpackage.json | 2+-
Mspago.lock | 2+-
Mspago.yaml | 2+-
Msrc/Db.purs | 2+-
Msrc/Main.purs | 4++--
Mtest/Main.purs | 20++++++++++----------
15 files changed, 51 insertions(+), 51 deletions(-)

diff --git a/.env.example b/.env.example @@ -2,14 +2,14 @@ LISTENBRAINZ_USER=mtmn LASTFM_USER= DISCOGS_TOKEN= LASTFM_API_KEY= -S3_BUCKET=scorpus-covers +S3_BUCKET=corpus-covers S3_REGION=us-east-1 AWS_ACCESS_KEY_ID= AWS_SECRET_ACCESS_KEY= AWS_S3_ADDRESSING_STYLE=path AWS_ENDPOINT_URL=http://localhost:8333 INITIAL_SYNC=true -DATABASE_FILE=/tmp/scorpus.db +DATABASE_FILE=/tmp/corpus.db COVER_CACHE_ENABLED=true BACKUP_ENABLED=true BACKUP_INTERVAL_HOURS=1 diff --git a/Containerfile b/Containerfile @@ -40,7 +40,7 @@ RUN mkdir -p /app/data && chown -R node:node /app/data USER node ENV PORT=8321 -ENV DATABASE_FILE=/app/data/scorpus.db +ENV DATABASE_FILE=/app/data/corpus.db ENV NODE_ENV=production CMD ["node", "--no-deprecation", "server.js"] diff --git a/README.md b/README.md @@ -1,8 +1,8 @@ -# Scorpus +# corpus | | | | :--- | ---: | -| **Scorpus** is an alternative [ListenBrainz](https://listenbrainz.org) and [Last.fm](https://last.fm) frontend that stores metadata and cover images.<br><br>Includes scrobbles fetching, metadata enrichment, and an interactive [PureScript](https://purescript.org) frontend for exploration of your listening habits.<br><br>[Live instance running here.](https://scrobbler.mtmn.name) | <img src="docs/korpus.webp" width="400" alt="Korpus"> | +| **corpus** is an alternative [ListenBrainz](https://listenbrainz.org) and [Last.fm](https://last.fm) frontend that stores metadata and cover images.<br><br>Includes scrobbles fetching, metadata enrichment, and an interactive [PureScript](https://purescript.org) frontend for exploration of your listening habits.<br><br>[Live instance running here.](https://scrobbler.mtmn.name) | <img src="docs/korpus.webp" width="400" alt="Korpus"> | ## Documentation @@ -50,7 +50,7 @@ npx spago bundle --module Client --outfile client.js --platform browser | `LISTENBRAINZ_USER` | — | ListenBrainz username — enables scrobble sync when set | | `LASTFM_USER` | — | Last.fm username — enables scrobble sync when set | | `LASTFM_API_KEY` | — | Last.fm API key (required when `LASTFM_USER` is set; also used for genre and cover art fallback) | -| `DATABASE_FILE` | `scorpus.db` | Path to the DuckDB database file | +| `DATABASE_FILE` | `corpus.db` | Path to the DuckDB database file | | `PORT` | `8000` | HTTP port to listen on | | `COVER_CACHE_ENABLED` | `true` | Set to `false` to disable S3 cover art caching | | `S3_BUCKET` | — | S3 bucket name for cover art caching | @@ -63,4 +63,4 @@ npx spago bundle --module Client --outfile client.js --platform browser | `BACKUP_ENABLED` | — | Set to `true` to enable periodic local database backups | | `BACKUP_INTERVAL_HOURS` | `1` | How often to back up the database (in hours) | -Backups are written to a `backup/` directory alongside `DATABASE_FILE`, named `scorpus-<timestamp>.db`. +Backups are written to a `backup/` directory alongside `DATABASE_FILE`, named `corpus-<timestamp>.db`. diff --git a/devenv.nix b/devenv.nix @@ -1,3 +1,3 @@ { - cachix.push = "scorpus"; + cachix.push = "corpus"; } diff --git a/docs/architecture.md b/docs/architecture.md @@ -1,6 +1,6 @@ -# Scorpus Architecture +# Corpus Architecture -Scorpus is a personal music listening history dashboard and analytics service. It synchronizes scrobbles from ListenBrainz and Last.fm and provides a performant web interface for data exploration and statistics. +Corpus is a personal music listening history dashboard and analytics service. It synchronizes scrobbles from ListenBrainz and Last.fm and provides a performant web interface for data exploration and statistics. ## System Components @@ -19,7 +19,7 @@ A Single Page Application (SPA) built with PureScript and the [Halogen](https:// - **Responsive UI**: Designed for both desktop and mobile viewing with a "retro-modern" aesthetic. ### Database -Scorpus uses **DuckDB** for its primary data storage. +Corpus uses **DuckDB** for its primary data storage. - **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). @@ -73,7 +73,7 @@ When a cover is requested: ## Foreign Function Interface (FFI) -Scorpus relies on FFI to interact with the Node.js and browser ecosystems where native PureScript wrappers are unavailable or where direct JS access is required. Key FFI integrations include: +Corpus relies on FFI to interact with the Node.js and browser ecosystems where native PureScript wrappers are unavailable or where direct JS access is required. Key FFI integrations include: - **Database (`Db.js`)**: Provides a high-performance interface to the native `duckdb` library. It includes custom logic to handle BigInt conversions, ensuring database results are compatible with standard JSON serialization. - **Cloud Storage (`S3.js`)**: Leverages the official AWS SDK (`@aws-sdk/client-s3`) to manage cover art caching in S3-compatible storage. @@ -91,7 +91,7 @@ graph TD CAA[Cover Art Archive] end - subgraph Scorpus Server + subgraph Corpus Server LBSync[ListenBrainz Sync] LFSync[Last.fm Sync] Enrich[Enrichment Task] diff --git a/docs/duckdb.md b/docs/duckdb.md @@ -1,6 +1,6 @@ -# DuckDB in Scorpus +# DuckDB in Corpus -Scorpus uses [DuckDB](https://duckdb.org/) as its primary analytical database. DuckDB's columnar storage and efficient query engine allow Scorpus to provide fast filtering, pagination, and statistics over large sets of listening history data. +Corpus uses [DuckDB](https://duckdb.org/) as its primary analytical database. DuckDB's columnar storage and efficient query engine allow Corpus to provide fast filtering, pagination, and statistics over large sets of listening history data. ## Database Schema @@ -37,7 +37,7 @@ The application interacts with DuckDB via a PureScript FFI layer (`src/Db.js` an ## Common Analytical Queries -You can run these queries directly against your `scorpus.db` file using the DuckDB CLI or any compatible tool. +You can run these queries directly against your `corpus.db` file using the DuckDB CLI or any compatible tool. ### Top 10 Artists of All Time ```sql diff --git a/flake.nix b/flake.nix @@ -5,8 +5,8 @@ }; nixConfig = { - extra-substituters = ["https://attic.saatana.cat/scorpus"]; - extra-trusted-public-keys = ["scorpus:mtDW9MGPAwrpye91ZDh3dMPBBzyz597OHNs4KempuLY="]; + extra-substituters = ["https://attic.saatana.cat/corpus"]; + extra-trusted-public-keys = ["corpus:mtDW9MGPAwrpye91ZDh3dMPBBzyz597OHNs4KempuLY="]; }; outputs = { @@ -39,7 +39,7 @@ }; spagoDeps = pkgs.stdenv.mkDerivation { - name = "scorpus-spago-deps"; + name = "corpus-spago-deps"; src = pkgs.lib.cleanSourceWith { src = self; @@ -93,8 +93,8 @@ ); }; - scorpus = pkgs.buildNpmPackage { - pname = "scorpus"; + corpus = pkgs.buildNpmPackage { + pname = "corpus"; version = "1.0.0"; inherit src; @@ -130,30 +130,30 @@ ''; installPhase = '' - mkdir -p $out/lib/scorpus - cp -r server.js client.js package.json node_modules assets $out/lib/scorpus/ + mkdir -p $out/lib/corpus + cp -r server.js client.js package.json node_modules assets $out/lib/corpus/ if [ -f .env ]; then - cp .env $out/lib/scorpus/ + cp .env $out/lib/corpus/ else - cp .env.example $out/lib/scorpus/.env + cp .env.example $out/lib/corpus/.env fi mkdir -p $out/bin - makeWrapper ${pkgs.nodejs}/bin/node $out/bin/scorpus-server \ + makeWrapper ${pkgs.nodejs}/bin/node $out/bin/corpus-server \ --add-flags "--no-deprecation" \ - --add-flags "$out/lib/scorpus/server.js" \ - --chdir "$out/lib/scorpus" + --add-flags "$out/lib/corpus/server.js" \ + --chdir "$out/lib/corpus" ''; }; in { - packages.default = scorpus; + packages.default = corpus; packages.container = pkgs.dockerTools.buildLayeredImage { - name = "scorpus"; + name = "corpus"; tag = "latest"; contents = [pkgs.nodejs pkgs.cacert]; config = { - Cmd = ["${scorpus}/bin/scorpus-server"]; + Cmd = ["${corpus}/bin/corpus-server"]; WorkingDir = "/app"; ExposedPorts = { "8321/tcp" = {}; diff --git a/justfile b/justfile @@ -31,7 +31,7 @@ nix command: @if [ "{{ command }}" = "build" ]; then \ nix build .; \ elif [ "{{ command }}" = "run" ]; then \ - ./result/bin/scorpus-server; \ + ./result/bin/corpus-server; \ else \ echo "Unknown nix command: {{ command }}"; \ exit 1; \ @@ -52,7 +52,7 @@ container command: elif [ "{{ command }}" = "push" ]; then \ skopeo copy \ --dest-precompute-digests \ - docker-archive:result docker://ghcr.io/mtmn/scorpus:latest; \ + docker-archive:result docker://ghcr.io/mtmn/corpus:latest; \ else \ echo "Unknown container command: {{ command }}"; \ exit 1; \ diff --git a/package-lock.json b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "scorpus", + "name": "corpus", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "scorpus", + "name": "corpus", "version": "1.0.0", "dependencies": { "@aws-sdk/client-s3": "^3.1029.0", diff --git a/package.json b/package.json @@ -1,5 +1,5 @@ { - "name": "scorpus", + "name": "corpus", "version": "1.0.0", "description": "ListenBrainz frontend in PureScript", "main": "index.js", diff --git a/spago.lock b/spago.lock @@ -1,7 +1,7 @@ { "workspace": { "packages": { - "scorpus": { + "corpus": { "path": "./", "core": { "dependencies": [ diff --git a/spago.yaml b/spago.yaml @@ -1,5 +1,5 @@ package: - name: scorpus + name: corpus dependencies: - aff - affjax diff --git a/src/Db.purs b/src/Db.purs @@ -73,7 +73,7 @@ performBackup conn dbFile = do Left _ -> "unknown" let dir = dirName dbFile <> "backup/" void $ try $ FSA.mkdir' dir { recursive: true, mode: Perms.mkPerms Perms.all Perms.all Perms.read } - let dest = dir <> "scorpus-" <> ts <> ".db" + let dest = dir <> "corpus-" <> ts <> ".db" FSA.copyFile dbFile dest Log.info $ "Backup saved locally: " <> dest diff --git a/src/Main.purs b/src/Main.purs @@ -1036,7 +1036,7 @@ type MbData = { genre :: Maybe String, label :: Maybe String, year :: Maybe Int fetchMusicBrainzRelease :: String -> Aff (Maybe MbData) fetchMusicBrainzRelease mbid = do let url = "https://musicbrainz.org/ws/2/release/" <> mbid <> "?inc=genres+labels+release-groups&fmt=json" - result <- try $ fetch url { method: GET, headers: { "User-Agent": "Scorpus/1.0 +https://codeberg.org/mtmn/scorpus" } } + result <- try $ fetch url { method: GET, headers: { "User-Agent": "corpus/1.0 +https://codeberg.org/mtmn/corpus" } } case result of Left err -> do Log.error $ "MusicBrainz fetch error for " <> mbid <> ": " <> Exception.message err @@ -1257,7 +1257,7 @@ main = do env <- getEnv let port = getEnvInt env "PORT" 8000 - dbFile = getEnvStr env "DATABASE_FILE" "scorpus.db" + dbFile = getEnvStr env "DATABASE_FILE" "corpus.db" username = getEnvStr env "LISTENBRAINZ_USER" "" backupEnabled = getEnvBool env "BACKUP_ENABLED" false backupIntervalHours = getEnvInt env "BACKUP_INTERVAL_HOURS" 1 diff --git a/test/Main.purs b/test/Main.purs @@ -28,7 +28,7 @@ main = do Process.setEnv "S3_BUCKET" "my-bucket" runSpecAndExitProcess [consoleReporter] do - describe "Scorpus Main Utils" do + describe "Corpus Main Utils" do it "should build ListenBrainz URLs correctly" do listenBrainzUrl "user1" `shouldEqual` "https://api.listenbrainz.org/1/user/user1/listens" @@ -37,7 +37,7 @@ main = do sanitizeKey "T.est-123" `shouldEqual` "T.est-123" sanitizeKey "multiple spaces" `shouldEqual` "multiple_spaces" - describe "Scorpus Types" do + describe "Corpus Types" do describe "MbidMapping Codecs" do it "should roundtrip MbidMapping" do let mbid = MbidMapping { releaseMbid: Just "release-123", caaReleaseMbid: Just "caa-456" } @@ -111,7 +111,7 @@ main = do Left err -> fail $ "Decoding failed: " <> show err - describe "Scorpus Database" do + describe "Corpus Database" do it "should handle scrobble and metadata operations" do conn <- connect ":memory:" initDb conn @@ -157,18 +157,18 @@ main = do listensEmpty <- getScrobbles conn 10 0 (Just { field: "genre", value: "Jazz" }) length listensEmpty `shouldEqual` 0 - describe "Scorpus Backup" do + describe "Corpus Backup" do describe "dirName" do it "extracts directory from an absolute path" do - dirName "/app/data/scorpus.db" `shouldEqual` "/app/data/" + dirName "/app/data/corpus.db" `shouldEqual` "/app/data/" it "extracts directory from a nested path" do - dirName "/tmp/test/scorpus.db" `shouldEqual` "/tmp/test/" + dirName "/tmp/test/corpus.db" `shouldEqual` "/tmp/test/" it "returns ./ for a bare filename" do - dirName "scorpus.db" `shouldEqual` "./" + dirName "corpus.db" `shouldEqual` "./" it "local backup creates a file in backup/ alongside the db" do - let testDir = "/tmp/scorpus-backup-test" - let dbPath = testDir <> "/scorpus.db" + let testDir = "/tmp/corpus-backup-test" + let dbPath = testDir <> "/corpus.db" let backupDir = testDir <> "/backup" -- clean up any previous run void $ try $ FSA.rm' testDir { force: true, recursive: true, maxRetries: 0, retryDelay: 100 } @@ -288,7 +288,7 @@ main = do listens <- getScrobbles conn 10 0 Nothing length listens `shouldEqual` 1 - describe "Scorpus S3" do + describe "Corpus S3" do it "should generate virtual-host style S3 URLs" do liftEffect $ Process.setEnv "AWS_S3_ADDRESSING_STYLE" "virtual" let url = getS3Url "covers/test.jpg"