# 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](https://tangled.org/@mokkenstorm.dev/gleam-atproto): [`atproto_client`](https://hex.pm/packages/atproto_client) + [`atproto_codegen`](https://hex.pm/packages/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`. ```sh 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: ```sh curl "https:///xrpc/com.atproto.repo.listRecords?repo=&collection=dev.mokkenstorm.crate.shelf.entry" ``` ## With Docker ```sh make docker-build # or: docker compose build make up # server + Postgres on http://localhost:8080 make down ``` ## Tests ```sh make test # codegen, then shared + server + web suites ``` Real OAuth and cross-account flows are covered by a separate [e2e suite](./e2e/README.md) against a pinned local atproto devnet: ```sh make e2e-setup # once make e2e ```