import { z } from "zod";
import { all, first, run } from "../db";
import { randomId } from "../crypto";
import { notFoundError } from "../errors";
import { deliverWebhook, isForgeWebhookUrl } from "../events/consumer";
import type { WebhookEvent } from "../events/emit";
import { assertBelowCap, CAPS, limitWrite } from "../limits";
import { noContent, readJson, requireScopedRepo } from "./http";
import type { ApiContext } from "./http";
import { WEBHOOK_EVENTS } from "~/lib/webhook-events";
const DELIVERY_LIMIT = 100;
type WebhookRow = { id: string; url: string; secret: string | null; events: string; active: number; created_at: number };
function hookJson(hook: WebhookRow) {
return {
id: hook.id,
url: hook.url,
has_secret: Boolean(hook.secret),
events: JSON.parse(hook.events) as string[],
active: hook.active === 1,
created_at: hook.created_at,
};
}
/** Repo webhooks need an admin-area grant, matching the settings page's requireRepoAccess(..., "admin"). */
async function adminRepo(ctx: ApiContext, owner: string, name: string) {
return (await requireScopedRepo(ctx, owner, name, "admin", "admin")).repo;
}
async function loadHook(repoId: string, id: string): Promise<WebhookRow> {
const hook = await first<WebhookRow>("SELECT * FROM webhooks WHERE id = ? AND repo_id = ?", id, repoId);
if (!hook) throw notFoundError("Webhook not found");
return hook;
}
export async function listWebhooksApi(ctx: ApiContext, owner: string, name: string): Promise<Response> {
const repo = await adminRepo(ctx, owner, name);
const hooks = await all<WebhookRow>("SELECT * FROM webhooks WHERE repo_id = ? ORDER BY created_at", repo.id);