Adds GET /admin/webhooks gated by an ADMIN_DIDS env-var allowlist of
verified AT Proto DIDs. Fail-closed: if ADMIN_DIDS is unset, the route
always returns 403 — no accidental exposure on deploys that forget it.
- GET /:did/webhooks lists subscriptions for the authenticated owner
(token field excluded — write-only as elsewhere).
- DELETE /:did/webhooks/:id deletes a single subscription. The query
scopes on (did, id) so a verified caller cannot delete rows that
belong to a different DID even with a valid id.
Also extracts the auth gate into requireDidOwnership now that three
endpoints share it.
Subscriptions now declare a `verb` (create | delete | bulk-create).
POST /:did/webhooks defaults to inserting both create and delete rows
when no verb is given, preserving existing all-events behavior. Update
events fold into the create verb. The new bulk-create verb debounces
creates per DID over 400 ms and delivers a `records` array.
Migration adds the verb column with default 'create' and clones every
existing row for the delete verb so legacy subscriptions keep firing
on both events.
Set busy_timeout before journal_mode=WAL in db.ts so SQLite retries
for 10s instead of failing immediately with the default 0ms timeout.
Extract migration into a dedicated Docker Compose service so both
jetstream and api wait for it to complete before opening the database.
Pure rename — no behavioral change. "discoverable" more clearly
communicates that the field controls whether a note can be found
by others in public listings.
Notes with listed=false are filtered out from all GET /notes queries.
The field defaults to true so existing and new notes without it remain visible.
Migration handles existing databases with ALTER TABLE ADD COLUMN.
- db.ts: getWebhooksByDid returns the 10 most recent subscriptions (ORDER BY id DESC LIMIT 10)
- jetstream.ts: fireWebhooks fans out to registered URLs via Promise.allSettled after each create/update/delete event
- Migration: CREATE TABLE webhook_subscription (id, did, method, url) with index on did
- db.ts: addWebhookSubscription and deleteWebhooksByDid helpers
- server.ts: POST /:did/webhooks (201) and DELETE /:did/webhooks (204)
Jetstream was running backgrounded in the same container as the API server,
so crashes went undetected and Docker never restarted it. Now each process
runs as a separate docker-compose service with independent restart policies.
Also adds cursor persistence to SQLite (saved every 5s) so restarts resume
from where they left off, moves event destructuring inside try/catch blocks,
and adds global unhandled error/rejection handlers for crash visibility.
Verify the caller owns the DID by resolving their PDS via plc.directory
and validating the session token before allowing note deletion.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add Docker containerization with Deno and persistent SQLite volume.
Add GitLab CI pipeline with build and deploy stages.
Use SQLITE_PATH env var for configurable DB location.