This repository has no description
2

Configure Feed

Select the types of activity you want to include in your feed.

TypeScript 55.0%
JavaScript 30.0%
Lua 11.0%
HTML 3.5%
Dockerfile 0.5%
4 1 0

Clone this repository

https://tangled.org/robin.berjon.com/podium https://tangled.org/did:plc:w3pcqq2i25y4ywpvdn5xaxuo
git@tangled.org:robin.berjon.com/podium git@tangled.org:did:plc:w3pcqq2i25y4ywpvdn5xaxuo

For self-hosted knots, clone URLs may differ based on your setup.



README.md

🪩 Podium#

A search engine for open data published on the Atmosphere: Matadisco dataset records, Astrosky/Nebra transient astronomy events, and whatever else gets added over time.

Live at https://podium.mycopunk.it.

How it works#

podium.mycopunk.it
        │
   caddy-front  ──────────── reverse_proxy http://podium:80 (supramundane network)
        │
 ┌──────┴─────────────────────────────────────────────┐
 │ podium (caddy)                                     │
 │   /            → the frontend                      │
 │   /xrpc/*      → rewritten to /hv/xrpc/*           │
 │   /hv/*        → podium-hv                         │
 ├────────────────────────────────────────────────────┤
 │ podium-hv (HappyView)                              │
 │  indexes cx.vmx.matadisco + eco.astrosky.transient │
 │  .gcn from Jetstream + backfill, SQLite on /data,  │
 │   serves XRPC, Lua query scripts, admin dashboard  │
 └────────────────────────────────────────────────────┘
  • Backend is HappyView: a lexicon-driven AppView. The collections to index, Podium's own query lexicons (it.mycopunk.podium.*), and the Lua scripts that implement them all live in this repo (backend/, wired up by podium.config.json) and are pushed to the instance through its admin API — no pasting into dashboards.
  • Frontend is Lit 3 + refrakt.
  • Deployment is a Supramundane service: service.json + a hand-authored multi-container compose.yaml on the supramundane network. HappyView's SQLite lives in ${SM_DATA_ROOT}/podium, so it's backed up in prod.

XRPC endpoints#

Query What it does
it.mycopunk.podium.search?q=&collection=&limit= full-text search across collections (per-collection field lists in backend/scripts/search.lua)
it.mycopunk.podium.listRecords?collection=&limit=&cursor= freshest records, merged or per collection
it.mycopunk.podium.getRecord?uri= one full record by AT URI (GCN records keep their raw data here; list/search replace it with a summary)
it.mycopunk.podium.stats per-collection record counts

All available on both /xrpc/… and /hv/xrpc/….

The podium CLI#

npm link once (or call node bin/podium.js). Hits prod by default; add --local for the local stack.

podium deploy [--local] [--no-push]  # env file → .env, sm deploy, then push
podium push [--local] [--watch]      # sync domains, lexicons + Lua scripts to HappyView
podium backfill [collection] [--local]  # (re)start historical backfill
podium bootstrap <handle> [--local]  # first-boot: seed super user + API key (prod goes over SSH)
podium status [--local]              # what the instance currently has

push --watch re-pushes on every save under backend/ — that's the dev loop for Lua/lexicon work. Frontend dev is npm run dev (Vite on :5173, proxying XRPC to the local HappyView on 127.0.0.1:3300).

First boot#

Local (needs Docker, the supramundane network from a running caddy-front, and .bast DNS):

cp .env.example .env.local            # fill in PODIUM_SESSION_SECRET, check SM_DATA_ROOT
podium deploy --local                 # brings the stack up (no push yet — no key)
podium bootstrap robin.berjon.com --local   # seeds super user + API key into .env.local
podium push --local                   # domains, lexicons, scripts, kicks off backfill

The atproto OAuth dance can't run against a .bast domain (the PDS must fetch the client metadata), which is why local uses bootstrap (a direct SQLite seed, done with the container stopped) and PODIUM_PUBLIC_URL points at the loopback port. The dashboard is at http://127.0.0.1:3300/hv/dashboard/, and logging in there with the bootstrapped handle works because the seeded user is that identity.

Prod (needs $SUPRAMUNDANE for the SSH steps):

cp .env.example .env.prod             # prod URLs, fresh PODIUM_SESSION_SECRET, no key yet
podium deploy --no-push               # rsyncs + builds on $SUPRAMUNDANE, wires the front
podium bootstrap robin.berjon.com     # seeds super user + API key on the server, over SSH
podium push                           # domains, lexicons, scripts, kicks off backfill

podium bootstrap is the no-browser path; dashboard OAuth login at https://podium.mycopunk.it/hv also works from first boot (first login becomes super user) provided PODIUM_PUBLIC_URL is the bare origin — see "Careful" below.

Careful#

  • PUBLIC_URL must be the bare origin — never include /hv. HappyView appends BASE_PATH itself (effective_public_url()), so a path-ful value yields /hv/hv/ OAuth metadata URLs (login fails with invalid_client_metadata) and a primary domain whose host key contains the path, which matches no Host header → everything domain-gated 421s.
  • HappyView 421s any Host it doesn't know. Its primary domain comes from PUBLIC_URL; every other public origin (e.g. https://podium.bast) must be registered, which podium push does from PODIUM_DOMAINS. Domain URLs are UNIQUE in its DB and the boot-time primary sync panics on conflict, so never register an extra domain equal to what the primary will sync to (push skips ones that already exist, which covers this).
  • Don't touch the SQLite file while the container runs. It's on a Docker bind mount; host-side writes against the live WAL corrupt it (this is why bootstrap stops the container first). If it happens anyway: stop the stack, delete happyview.db*, start, re-bootstrap, re-push.
  • Re-POSTing a network lexicon is not idempotent — it re-fetches and re-kicks sync machinery. podium push therefore only creates missing ones, and only auto-backfills on creation.
  • The records in the wild don't all match the published lexicons. Matadisco has (at least) three shapes: resource/publishedAt(/tags), and metadata/created with a text or image preview. Search fields and the result cards handle all of them; keep that in mind when adding sources.
  • GCN data payloads can be tens of KB, so list/search responses strip them server-side (slim() in the Lua scripts) and keep a human summary extracted from the event JSON.
  • .env is a deploy-time artifact. podium deploy writes it from .env.prod or .env.local, and restores the local flavor after a prod deploy (the prod copy lives on in the server's service dir). If local docker compose ever fails on a /srv/... mount, .env has prod values — cp .env.local .env.
  • A dashboard login poisons same-origin XRPC. HappyView allows anonymous XRPC but hard-rejects authenticated calls without a client key — so once you've logged into the dashboard, your session cookie would break the frontend's fetches ("Missing client identification"). The frontend fetches with credentials: 'omit' to stay anonymous; keep that if you touch api.ts.

Adding a data source#

  1. Add its NSID to networkLexicons in podium.config.json (or a local lexicon file under backend/lexicons/ if it isn't resolvable on-network).
  2. Add a source entry (collection + searchable fields) to backend/scripts/search.lua, and to the COLLECTIONS lists in listRecords.lua / stats.lua.
  3. Teach frontend/src/components/podium-result-card.ts how to render it (falls back to a generic card otherwise).
  4. podium push --local, check, podium push.