Spaces:
Paused
Paused
| /** | |
| * Database module: Webhooks | |
| * CRUD operations for webhook event subscriptions | |
| */ | |
| import { getDbInstance } from "./core"; | |
| import crypto from "crypto"; | |
| export interface Webhook { | |
| id: string; | |
| url: string; | |
| events: string[]; | |
| secret: string | null; | |
| enabled: boolean; | |
| description: string; | |
| created_at: string; | |
| last_triggered_at: string | null; | |
| last_status: number | null; | |
| failure_count: number; | |
| } | |
| interface WebhookRow { | |
| id: string; | |
| url: string; | |
| events: string; | |
| secret: string | null; | |
| enabled: number; | |
| description: string; | |
| created_at: string; | |
| last_triggered_at: string | null; | |
| last_status: number | null; | |
| failure_count: number; | |
| } | |
| function rowToWebhook(row: WebhookRow): Webhook { | |
| return { | |
| ...row, | |
| events: JSON.parse(row.events || '["*"]'), | |
| enabled: row.enabled === 1, | |
| }; | |
| } | |
| export function getWebhooks(): Webhook[] { | |
| const db = getDbInstance(); | |
| const rows = db.prepare("SELECT * FROM webhooks ORDER BY created_at DESC").all() as WebhookRow[]; | |
| return rows.map(rowToWebhook); | |
| } | |
| export function getWebhook(id: string): Webhook | null { | |
| const db = getDbInstance(); | |
| const row = db.prepare("SELECT * FROM webhooks WHERE id = ?").get(id) as WebhookRow | undefined; | |
| return row ? rowToWebhook(row) : null; | |
| } | |
| export function getEnabledWebhooks(): Webhook[] { | |
| const db = getDbInstance(); | |
| const rows = db.prepare("SELECT * FROM webhooks WHERE enabled = 1").all() as WebhookRow[]; | |
| return rows.map(rowToWebhook); | |
| } | |
| export function createWebhook(data: { | |
| url: string; | |
| events?: string[]; | |
| secret?: string; | |
| description?: string; | |
| }): Webhook { | |
| const db = getDbInstance(); | |
| const id = crypto.randomUUID(); | |
| const secret = data.secret || `whsec_${crypto.randomBytes(24).toString("hex")}`; | |
| db.prepare( | |
| `INSERT INTO webhooks (id, url, events, secret, description) | |
| VALUES (?, ?, ?, ?, ?)` | |
| ).run(id, data.url, JSON.stringify(data.events || ["*"]), secret, data.description || ""); | |
| return getWebhook(id)!; | |
| } | |
| export function updateWebhook( | |
| id: string, | |
| data: Partial<{ | |
| url: string; | |
| events: string[]; | |
| secret: string; | |
| enabled: boolean; | |
| description: string; | |
| }> | |
| ): Webhook | null { | |
| const db = getDbInstance(); | |
| const existing = getWebhook(id); | |
| if (!existing) return null; | |
| const fields: string[] = []; | |
| const values: any[] = []; | |
| if (data.url !== undefined) { | |
| fields.push("url = ?"); | |
| values.push(data.url); | |
| } | |
| if (data.events !== undefined) { | |
| fields.push("events = ?"); | |
| values.push(JSON.stringify(data.events)); | |
| } | |
| if (data.secret !== undefined) { | |
| fields.push("secret = ?"); | |
| values.push(data.secret); | |
| } | |
| if (data.enabled !== undefined) { | |
| fields.push("enabled = ?"); | |
| values.push(data.enabled ? 1 : 0); | |
| } | |
| if (data.description !== undefined) { | |
| fields.push("description = ?"); | |
| values.push(data.description); | |
| } | |
| if (fields.length === 0) return existing; | |
| values.push(id); | |
| db.prepare(`UPDATE webhooks SET ${fields.join(", ")} WHERE id = ?`).run(...values); | |
| return getWebhook(id); | |
| } | |
| export function deleteWebhook(id: string): boolean { | |
| const db = getDbInstance(); | |
| const result = db.prepare("DELETE FROM webhooks WHERE id = ?").run(id); | |
| return (result as any).changes > 0; | |
| } | |
| export function recordWebhookDelivery(id: string, status: number, success: boolean): void { | |
| const db = getDbInstance(); | |
| if (success) { | |
| db.prepare( | |
| `UPDATE webhooks SET last_triggered_at = datetime('now'), last_status = ?, failure_count = 0 WHERE id = ?` | |
| ).run(status, id); | |
| } else { | |
| db.prepare( | |
| `UPDATE webhooks SET last_triggered_at = datetime('now'), last_status = ?, failure_count = failure_count + 1 WHERE id = ?` | |
| ).run(status, id); | |
| } | |
| } | |
| export function disableWebhooksWithHighFailures(threshold = 10): number { | |
| const db = getDbInstance(); | |
| const result = db | |
| .prepare(`UPDATE webhooks SET enabled = 0 WHERE failure_count >= ? AND enabled = 1`) | |
| .run(threshold); | |
| return (result as any).changes; | |
| } | |