163 lines · 5.2 KB
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);