Commit Graph

41 Commits

Author SHA1 Message Date
Julien Calixte
02f3139f46 refactor(search): read OPENSEARCH_* env per call
Some checks failed
CI / check (push) Failing after 12s
Tests can't override values captured at module load because ES
imports hoist before any Deno.env.set call. Reading env on each
helper invocation makes the module trivially testable and has
no production cost (env doesn't change at runtime).
2026-06-07 22:23:36 +02:00
Julien Calixte
62a55fd3e4 fix(jetstream): use ReturnType<typeof setTimeout> for bulk timer
All checks were successful
CI / check (push) Successful in 14s
setTimeout returns an opaque Timeout type in newer Deno
releases, so the previous timer: number annotation fails type
checking in CI. Using ReturnType<typeof setTimeout> is portable
across Deno versions and runtimes.
2026-06-07 22:14:03 +02:00
Julien Calixte
49608cff20 refactor(search,auth): export pure helpers for direct testing
encodeCursor/decodeCursor/firstSnippet (opensearch) and
decodeJwtPayload (verify) become public exports so tests can
hit them without round-tripping through fetch.
2026-06-07 22:06:49 +02:00
Julien Calixte
01048c753e refactor(jetstream): extract webhook dispatch and bulk-create modules
dispatchAll, fireWebhooks, and the bulk-create debounce buffer
move out of jetstream.ts into src/jetstream/{webhooks,bulk}.ts.
fireWebhooks and the bulk helpers now take their DB lookup and
dispatch as parameters so tests can inject stubs. Top-level
Jetstream construction, event handlers, ensureIndex call, and
the cursor-saving interval are gated behind import.meta.main so
importing jetstream.ts no longer opens a WebSocket.
2026-06-07 22:06:19 +02:00
Julien Calixte
6760c434b4 refactor(db): lazy-init connection and add test seam
The Database handle is now constructed on first use and held in
a module-level slot. New _setDbForTest and _resetDbForTest
exports let tests swap in a :memory: handle without rewriting
callers. closeDb() replaces the previous db.close() pattern used
by short-lived scripts. Adds listDistinctNoteDids() to keep the
backfill script from poking the raw connection.
2026-06-07 22:04:12 +02:00
Julien Calixte
dc6daf3f54 refactor(migrations): extract applyMigrations as callable function 2026-06-07 22:01:57 +02:00
Julien Calixte
7875d24d48 refactor(search): use typed OpenSearch SDK request body
Some checks failed
CI / check (push) Failing after 1m0s
Pull in @opensearch-project/opensearch only for its
Search_RequestBody type so the inline search body is
type-checked. Also drops the unsupported
highlight.content.boundary_scanner_locale option.
2026-06-07 12:04:43 +02:00
Julien Calixte
45db68c34d feat(search): add OpenSearch client module
Exposes ensureIndex, indexNote, removeNote, and searchNotes. searchNotes
runs a fuzzy multi_match across title+content, returns sentence-bounded
highlights, and paginates via base64-encoded search_after cursors. All
operations no-op when OPENSEARCH_URL is unset so callers stay safe.
2026-06-07 11:26:02 +02:00
Julien Calixte
db1e07060c feat(note): add optional content field to Note type 2026-06-07 11:25:44 +02:00
Julien Calixte
6163092d27 refactor(auth): export resolvePds for reuse 2026-06-07 11:25:39 +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
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
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
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
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
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
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
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
5c31be6aa9 logs: better error handling 2026-02-15 12:35:44 +01:00
Julien Calixte
efb78ff14e logs: add ISO timestamp to all log output 2026-02-15 08:59:37 +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
a30c1ce43e fix: default dates to now 2026-02-13 20:26:15 +01:00
Julien Calixte
de92a554ee feat: handle note deletion from jetstream 2026-02-10 17:43:00 +01:00
Julien Calixte
59b3db29c3 fix: remove one value in upsert note 2026-02-09 23:02:16 +01:00
Julien Calixte
e46714230f prune: remove content prop 2026-02-09 22:12:58 +01:00
Julien Calixte
5fb8167263 refacto: add extension 2026-02-09 22:01:32 +01:00
Julien Calixte
6e22fd2f56 feat: add endpoints to get notes 2026-02-09 14:07:17 +01:00
Julien Calixte
ed157cb4df lint: format and lint 2026-02-09 12:03:27 +01:00
Julien Calixte
ee8421c773 feat: add Dockerfile and GitLab CI/CD pipeline
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.
2026-02-09 00:30:21 +01:00
Julien Calixte
6988450446 feat: upsert is easier 2026-02-09 00:17:38 +01:00
Julien Calixte
2312240d10 feat: init db 2026-02-08 23:26:52 +01:00