133 lines
3.5 KiB
TypeScript
133 lines
3.5 KiB
TypeScript
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));
|
|
}
|