From 373b7a6777c1dc36f46df50b62706c5aa1da2ca5 Mon Sep 17 00:00:00 2001 From: Julien Calixte Date: Wed, 25 Feb 2026 22:46:45 +0100 Subject: [PATCH] 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) --- server.ts | 27 ++++++++++++++++++++++++++- src/data/db.ts | 25 +++++++++++++++++++++++++ src/migrations/init.ts | 14 ++++++++++++++ 3 files changed, 65 insertions(+), 1 deletion(-) diff --git a/server.ts b/server.ts index 4e88683..8584c93 100644 --- a/server.ts +++ b/server.ts @@ -1,5 +1,10 @@ import { Application, Router } from "@oak/oak"; -import { getNotes, getNotesByDid } from "./src/data/db.ts"; +import { + addWebhookSubscription, + deleteWebhooksByDid, + getNotes, + getNotesByDid, +} from "./src/data/db.ts"; import { log } from "./src/log.ts"; const router = new Router(); @@ -23,6 +28,26 @@ router.get("/:did/notes", (ctx) => { ctx.response.body = getNotesByDid(did, cursor, limit); }); +router.post("/:did/webhooks", async (ctx) => { + const { did } = ctx.params; + const body = await ctx.request.body.json(); + const { method, url } = body ?? {}; + if (!method || !url) { + ctx.response.status = 400; + ctx.response.body = { error: "method and url are required" }; + return; + } + const subscription = addWebhookSubscription({ did, method, url }); + ctx.response.status = 201; + ctx.response.body = subscription; +}); + +router.delete("/:did/webhooks", (ctx) => { + const { did } = ctx.params; + deleteWebhooksByDid(did); + ctx.response.status = 204; +}); + // router.delete("/:did/:rkey", async (ctx) => { // const { did, rkey } = ctx.params; // let verifiedDid: string; diff --git a/src/data/db.ts b/src/data/db.ts index ea2baf3..80dc0d9 100644 --- a/src/data/db.ts +++ b/src/data/db.ts @@ -67,6 +67,31 @@ export const saveCursor = (cursor: number) => { ); }; +type WebhookSubscriptionRow = { + id: number; + did: string; + method: string; + url: string; +}; + +export const addWebhookSubscription = ( + { did, method, url }: Omit, +): WebhookSubscriptionRow => { + db.exec( + "INSERT INTO webhook_subscription (did, method, url) VALUES (?, ?, ?)", + did, + method, + url, + ); + return db.prepare( + "SELECT id, did, method, url FROM webhook_subscription WHERE id = last_insert_rowid()", + ).get()!; +}; + +export const deleteWebhooksByDid = (did: string): void => { + db.exec("DELETE FROM webhook_subscription WHERE did = ?", did); +}; + export const upsertNote = (note: Note) => { const now = new Date().toISOString(); db.exec( diff --git a/src/migrations/init.ts b/src/migrations/init.ts index 61207a1..edd7c69 100644 --- a/src/migrations/init.ts +++ b/src/migrations/init.ts @@ -18,4 +18,18 @@ db.exec(` ); `); +db.exec(` + CREATE TABLE IF NOT EXISTS webhook_subscription ( + id INTEGER PRIMARY KEY, + did TEXT NOT NULL, + method TEXT NOT NULL, + url TEXT NOT NULL + ); +`); + +db.exec(` + CREATE INDEX IF NOT EXISTS idx_webhook_subscription_did + ON webhook_subscription(did); +`); + db.close();