import { env } from "cloudflare:workers"; import { repoDb } from "../scoped-db"; import { repoActivityStatement } from "../issue-scope"; import type { CodeIndexMessage } from "../code/search-index"; import type { NotificationCategory } from "~/lib/notification-categories"; export type { WebhookEvent } from "~/lib/webhook-events"; import type { WebhookEvent } from "~/lib/webhook-events"; export type NotifyMessage = { kind: "notify"; repoId: string; issueId: string | null; actorId: string | null; reason: "comment" | "mention" | "review_requested" | "assigned" | "state_change" | "ci" | "review" | "release"; title: string; url: string; mentionText: string | null; directRecipients: string[]; }; /** * A categorised notification to explicit recipients. `access` is re-checked per recipient at send time: * "read" needs repo read access, "admin" repo admin, "security" repo admin or security manager, and * "account" owner/admin of `accountId`, and "advisory" admin or the author/collaborator of advisory `subject`. * Details must never contain secret values. */ export type DirectNotifyMessage = { kind: "notify.direct"; /** Idempotency key: redelivering the same id never notifies a recipient twice. */ id: string; category: NotificationCategory; repoId: string | null; accountId: string | null; access: "read" | "admin" | "security" | "account" | "advisory"; subject?: string | null; recipients: string[]; title: string; details: string[]; url: string; }; export type QueueMessage = | { kind: "webhook.dispatch"; repoId: string; event: WebhookEvent; payload: Record } | { kind: "webhook.deliver"; webhookId: string; event: WebhookEvent; deliveryId: string; payload: Record; } | NotifyMessage | DirectNotifyMessage | CodeIndexMessage | { kind: "email.send"; to: string; subject: string; text: string; html: string; headers?: Record; replyTo?: string; }; export async function emitWebhook( repoId: string, event: WebhookEvent, payload: Record, ): Promise { const message: QueueMessage = { kind: "webhook.dispatch", repoId, event, payload }; await env.EVENTS.send(message); } export async function emitNotification(message: Omit): Promise { const body: QueueMessage = { kind: "notify", ...message }; await env.EVENTS.send(body); } /** Activity lives in the repository's RepoState; account-level events without a repository are not recorded. */ export async function recordActivity( actorId: string | null, repoId: string | null, kind: string, data: Record, ): Promise { if (!repoId) return; await repoDb(repoId).batch([repoActivityStatement(actorId, repoId, kind, data)]); } export function publicUrl(path: string): string { return new URL(path, env.PUBLIC_URL).toString(); }