85 Commits

Author SHA1 Message Date
Julien Calixte
6f4d8d3b56 chore: add dockerignore for scripts, local artifacts, and env files
The image previously inherited everything from a `COPY . .`, including
.env (secrets), local notes.db copies, and admin scripts that should
not run in prod containers.
2026-05-05 15:04:43 +02:00
Julien Calixte
355fc45316 refactor(scripts): switch webhooks:all to api fetch
Hits GET /admin/webhooks instead of opening the local SQLite directly,
so the task can be run from a developer laptop without ssh or file
access to the server. Drops the FFI/read/write task permissions in
favour of net/env.
2026-05-05 14:07:31 +02:00
Julien Calixte
1c160b6c53 refactor(scripts): extract atproto session helpers to shared module 2026-05-05 14:07:26 +02:00
Julien Calixte
34faa10be2 feat(webhooks): add admin endpoint to list every subscription
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.
2026-05-05 14:07:22 +02:00
Julien Calixte
c00f3d631c chore(scripts): add admin task to list every webhook
Direct SQLite read with no DID filter — complements the API-backed
`webhooks list`, which is scoped per DID.
2026-05-05 14:00:10 +02:00
Julien Calixte
911d062423 chore(jetstream): log bulk-create record count on flush 2026-05-05 13:46:00 +02:00
Julien Calixte
8055060af3 fix: increase create debounce time 2026-05-05 12:48:15 +02:00
Julien Calixte
5cb581123d chore(scripts): add list and delete commands to manage-webhooks
`deno task webhooks list` prints the authenticated DID's subscriptions
(id, url, method, verb). `deno task webhooks delete --id <id>` removes
a single one — pair with `list` to pick which to drop instead of
nuking everything via delete-all.
2026-05-05 12:38:32 +02:00
Julien Calixte
bcea56c529 feat(webhooks): add list and granular delete endpoints
- 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.
2026-05-05 12:38:26 +02:00
Julien Calixte
a3c92254ea refactor(scripts): auto-resolve PDS from handle, drop BSKY_PDS
Inputs are now just ATPROTO_HANDLE, ATPROTO_APP_PASSWORD, REMANSO_API
(or matching --handle, --app-password, --api). The script resolves the
handle to a DID via public.api.bsky.app, then resolves the DID to a
PDS via plc.directory — same path the server's verifier uses. Works
for any AT Protocol PDS (Bluesky, eurosky, self-hosted) without the
caller having to know the PDS URL.
2026-05-05 12:36:22 +02:00
Julien Calixte
e0fe4ce16f chore(scripts): add deno task webhooks for register and delete-all
Wraps the createSession + Authorization: Bearer flow so callers don't
need to assemble curl by hand. Reads BSKY_IDENTIFIER / BSKY_PASSWORD /
BSKY_PDS / REMANSO_API from env (or matching flags). Defaults to
bsky.social so non-bsky-hosted accounts must set BSKY_PDS explicitly,
e.g. BSKY_PDS=https://eurosky.social.
2026-05-05 12:32:43 +02:00
Julien Calixte
1ce5d9150d feat(auth): require DID owner JWT to manage webhooks
Both POST /:did/webhooks and DELETE /:did/webhooks were unauthenticated:
anyone could register a webhook for someone else's DID (privacy leak)
or wipe a DID's webhook list (DoS on legitimate subscribers). Now both
endpoints require a Bluesky session bearer token, verified end-to-end
against the DID's PDS via the existing authenticateRequest helper, and
the verified DID must match the URL :did.
2026-05-05 12:27:22 +02:00
Julien Calixte
7b53909c52 feat(webhooks): add per-verb subscriptions and bulk-create debounce
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.
2026-05-05 12:25:54 +02:00
Julien Calixte
8e967ba43c prettier 2026-03-21 12:36:33 +01:00
Julien Calixte
555bffaa89 fix: disable restart on migrate service 2026-03-21 12:33:07 +01:00
Julien Calixte
ac2b70a260 fix: prevent database is locked on concurrent startup
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.
2026-03-21 12:30:17 +01:00
Julien Calixte
92e0dbe0d4 feat: add GitHub OAuth proxy endpoint at /auth/github 2026-03-21 10:35:49 +01:00
Julien Calixte
32ee135099 fix: install curl for Docker healthcheck 2026-03-17 02:27:21 +01:00
Julien Calixte
282b797206 feat: add /health endpoint and Docker healthcheck 2026-03-17 02:21:24 +01:00
Julien Calixte
98d76c24ee fix: no confusion for unicode chars 2026-03-17 01:15:56 +01:00
Julien Calixte
cedb29949c chore: docker compose for coolify 2026-03-14 20:27:15 +01:00
Julien Calixte
4e54d51d14 fix: skip body and Content-Type for GET/HEAD webhooks 2026-03-14 18:32:01 +01:00
Julien Calixte
8c9ab34565 feat: add optional bearer token support for webhook subscriptions
Token is stored in the DB but never returned in API responses (write-only).
fireWebhooks() sends Authorization: Bearer <token> header when present.
2026-03-14 18:16:28 +01:00
Julien Calixte
06ac3142a8 feat: add POST /notes/feed endpoint for multi-DID filtering 2026-03-10 15:51:10 +01:00
Julien Calixte
425dd96872 feat: update content length 2026-03-08 09:00:03 +01:00
Julien Calixte
8ff1e4acaa feat: get language in server too 2026-03-01 18:49:43 +01:00
Julien Calixte
6374566316 feat: add language support 2026-03-01 18:34:17 +01:00
Julien Calixte
2b54c8dd00 feat: add language support for better filtering 2026-03-01 17:37:59 +01:00
Julien Calixte
29e8a63cb3 refactor: rename listed → discoverable on note
Pure rename — no behavioral change. "discoverable" more clearly
communicates that the field controls whether a note can be found
by others in public listings.
2026-02-25 23:20:37 +01:00
Julien Calixte
f39f62f1c7 feat: add listed field to note for public listing visibility
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.
2026-02-25 23:03:00 +01:00
Julien Calixte
62f981dd93 feat: fire webhooks on jetstream events, cap at 10 per DID
- 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
2026-02-25 22:51:48 +01:00
Julien Calixte
373b7a6777 feat: add webhook_subscription table and CRUD endpoints
- 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)
2026-02-25 22:46:45 +01:00
Julien Calixte
6cc7866080 chore: move to France Relay 2026-02-19 11:51:25 +01:00
Julien Calixte
cce66e26d5 logs: remove log of cursors 2026-02-17 15:38:37 +01:00
Julien Calixte
c9edd63e76 refacto: use @db/sqlite for better usage 2026-02-17 15:32:05 +01:00
Julien Calixte
1c7a4d3a11 fix: remove db-journal from git 2026-02-17 14:07:09 +01:00
Julien Calixte
8e6ac4faa9 fix: add multiple access to db 2026-02-17 14:02:39 +01:00
Julien Calixte
c84b4c5f97 robustness: split jetstream into own container, add cursor persistence
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.
2026-02-17 01:17:42 +01:00
Julien Calixte
1a7841b728 adding logs again... 2026-02-16 22:21:21 +01:00
Julien Calixte
37b95d9196 revert to not polute logs 2026-02-16 22:16:20 +01:00
Julien Calixte
627168cf30 logs: add log for intention 2026-02-16 22:06:55 +01:00
Julien Calixte
d1afdd6499 prune: remove unused imports 2026-02-15 13:10:50 +01:00
Julien Calixte
5c31be6aa9 logs: better error handling 2026-02-15 12:35:44 +01:00
Julien Calixte
e0e095f7e5 fix: deletion should come from pds events 2026-02-15 09:24:40 +01:00
Julien Calixte
efb78ff14e logs: add ISO timestamp to all log output 2026-02-15 08:59:37 +01:00
Julien Calixte
562a3b061f refacto: no magic numbers 2026-02-14 22:47:48 +01:00
Julien Calixte
d950d5cb48 logs: add log for upserts 2026-02-14 22:47:06 +01:00
Julien Calixte
a7a90ea075 feat: authenticate DELETE endpoint with AT Protocol identity
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>
2026-02-14 20:38:40 +01:00
Julien Calixte
51ea8a8f17 feat: implement delete note endpoint in server
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 20:28:30 +01:00
Julien Calixte
1534b1be4f rebranding to Remanso 2026-02-14 12:08:22 +01:00