// Forgejo issue integration for the in-app "Report Issue" button. // Issues are created on the configured repo and labelled so the // automated Claude watcher can pick them up. const FORGEJO_URL = process.env.FORGEJO_URL ?? "https://git.pelagiamarine.com"; const FORGEJO_REPO = process.env.FORGEJO_REPO ?? "shad0w/pelagia-portal"; interface CreateIssueInput { title: string; body: string; labels: string[]; } interface ForgejoIssue { number: number; html_url: string; } async function forgejoFetch(path: string, init?: RequestInit): Promise { const token = process.env.FORGEJO_TOKEN; if (!token) throw new Error("FORGEJO_TOKEN is not configured"); return fetch(`${FORGEJO_URL}/api/v1${path}`, { ...init, headers: { Authorization: `token ${token}`, "Content-Type": "application/json", ...init?.headers, }, }); } /** Resolve label names to IDs (the create-issue API only accepts IDs). */ async function resolveLabelIds(names: string[]): Promise { const res = await forgejoFetch(`/repos/${FORGEJO_REPO}/labels?limit=50`); if (!res.ok) return []; const labels: { id: number; name: string }[] = await res.json(); return labels.filter((l) => names.includes(l.name)).map((l) => l.id); } export async function createForgejoIssue(input: CreateIssueInput): Promise { const labelIds = await resolveLabelIds(input.labels); const res = await forgejoFetch(`/repos/${FORGEJO_REPO}/issues`, { method: "POST", body: JSON.stringify({ title: input.title, body: input.body, labels: labelIds }), }); if (!res.ok) { const text = await res.text(); throw new Error(`Forgejo issue creation failed (${res.status}): ${text.slice(0, 200)}`); } const issue = await res.json(); return { number: issue.number, html_url: issue.html_url }; }