gotta store my records
Gleam 89%
CSS 5%
3%
JavaScript 1%
TypeScript <1%
Shell <1%
Makefile <1%
HCL <1%
Dockerfile <1%
HTML <1%

README.md

at-record #

A Discogs-like, atproto-native record collection app, written in Gleam. Your crate (the vinyl kind) is stored as records (the atproto kind) in your own PDS. Gleam reincarnation of the crate design: same lexicons (dev.mokkenstorm.crate.*), same PDS-as-storage idea, different stack.

What it does today #

  • atproto OAuth login (PAR + PKCE + DPoP); tokens live server-side, encrypted at rest.
  • Your crate is an append-only event log, folded into current crate, wishlist, and history.
  • Discogs seed search, account connection, and collection import.
  • Barcode scan-to-import (Chromium's BarcodeDetector only, for now).
  • Cover art stored as blobs in your repo, not hotlinked.
  • via @handle attribution chip on entries adopted from someone else's catalog release.

Layout #

Gleam multi-target monorepo. server/web depend on shared by path; the generic atproto plumbing comes from Hex (gleam-atproto: atproto_client + atproto_codegen).

Package Target What it is
shared erlang+js Generated lexicon codecs (gen/) + StoredItem.
server erlang Wisp BFF: routes, OAuth, sessions, Discogs client.
web javascript Lustre SPA, talks to the BFF (+ the public Bluesky AppView directly for handle search).

Lexicons live under lexicons/. shared/src/at_record/gen/ is generated and gitignored: make gen runs atproto_codegen over lexicons/. Run make gen (or make test, which depends on it) before building a fresh checkout.

Run it locally #

Needs gleam, erlang, node, docker, make.

make dev        # watch loop: Postgres in docker, rebuilds server/web on change

Browse http://127.0.0.1:8080, not localhost (cookies are host-specific and OAuth callbacks land on the loopback IP).

Config comes from the environment (scripts/dev.sh / docker-compose provide dev defaults, read .env for secrets):

Variable Required Purpose
SECRET_KEY_BASE yes cookie signing secret
STORE_KEY yes at-rest encryption key (base64, 32 bytes)
DATABASE_URL no Postgres (in-memory fallback)
DISCOGS_CONSUMER_KEY / DISCOGS_CONSUMER_SECRET no Discogs search + import
BASE_URL, OAUTH_CLIENT_JWK deploys switches OAuth to the confidential client
PORT, SLINGSHOT_URL no port/identity-resolver overrides

Confirm your data lives on your PDS:

curl "https://<your-pds>/xrpc/com.atproto.repo.listRecords?repo=<your-did>&collection=dev.mokkenstorm.crate.shelf.entry"

With Docker #

make docker-build          # or: docker compose build
make up                    # server + Postgres on http://localhost:8080
make down

Tests #

make test                  # codegen, then shared + server + web suites

Real OAuth and cross-account flows are covered by a separate e2e suite against a pinned local atproto devnet:

make e2e-setup              # once
make e2e