import "server-only"; import { randomUUID } from "node:crypto"; import { after } from "next/server"; import { admin, required } from "./supabase"; import { LinearSupportClient } from "./linear-support-client"; import { scheduleBugCleanup } from "./bug-lifecycle"; export function supportReady() { return !!( process.env.LINEAR_API_KEY && process.env.LINEAR_TEAM_ID && process.env.LINEAR_WEBHOOK_SECRET ); } export async function syncBugReport(id: string) { if (!supportReady()) return; const db = admin(), worker = randomUUID(); if ( !required( await db.rpc("claim_bug_sync", { p_report: id, p_worker: worker }), ) ) return; try { const linear = new LinearSupportClient( process.env.LINEAR_API_KEY!, process.env.LINEAR_TEAM_ID!, process.env.LINEAR_PROJECT_ID, ); const report = required( await db.from("bug_reports").select("*").eq("id", id).single(), ); if (!report.issue_synced) { if (report.screenshot_path && !report.linear_asset_url) { const image = required( await db.storage .from("minegeo-support") .download(report.screenshot_path), ); report.linear_asset_url = await linear.upload( new Uint8Array(await image.arrayBuffer()), `bug-${id}.webp`, ); required( await db .from("bug_reports") .update({ linear_asset_url: report.linear_asset_url }) .eq("id", id) .eq("lease_owner", worker) .select("id") .single(), ); } const issue = await linear.createIssue(report); required( await db .from("bug_reports") .update({ issue_synced: true, linear_identifier: issue.identifier, }) .eq("id", id) .eq("lease_owner", worker) .select("id") .single(), ); required( await db .from("bug_reports") .update({ state: "open" }) .eq("id", id) .eq("state", "pending") .select("id"), ); } required( await db .from("bug_report_messages") .update({ delivered: true }) .eq("id", id) .select("id"), ); const messages = required( await db .from("bug_report_messages") .select("*") .eq("report_id", id) .eq("kind", "user") .eq("delivered", false) .neq("id", id) .order("created_at") .limit(50), ); for (const message of messages) { await linear.comment(message.id, id, message.author_name, message.body); required( await db .from("bug_report_messages") .update({ delivered: true }) .eq("id", message.id) .select("id"), ); } required( await db .from("bug_reports") .update({ lease_owner: null, lease_until: null, retry_at: null }) .eq("id", id) .eq("lease_owner", worker) .select("id"), ); await scheduleBugCleanup(id); } catch { console.error("Bug report delivery paused; the saved report will retry."); required( await db .from("bug_reports") .update({ lease_owner: null, lease_until: null, retry_at: new Date(Date.now() + 60_000).toISOString(), }) .eq("id", id) .eq("lease_owner", worker) .select("id"), ); } } export function syncBugAfterResponse(id: string) { after(() => syncBugReport(id)); }