Files
MineGeo/lib/bug-cleanup-workflow.ts

147 lines
3.7 KiB
TypeScript

import {
FatalError,
RetryableError,
getWorkflowMetadata,
sleep,
} from "workflow";
import { admin, required } from "./supabase";
import {
LinearSupportClient,
LinearPermissionError,
} from "./linear-support-client";
import { purgeClosedBugReport } from "./bug-retention";
const linearClient = () =>
new LinearSupportClient(
process.env.LINEAR_API_KEY!,
process.env.LINEAR_TEAM_ID!,
process.env.LINEAR_PROJECT_ID,
);
async function record(id: string) {
const result = await admin()
.from("bug_reports")
.select("*")
.eq("id", id)
.maybeSingle();
if (result.error) required(result);
return result.data;
}
export async function cleanupDoneBugReport(
id: string,
closedAt: string,
claim: string,
) {
"use workflow";
const run = getWorkflowMetadata().workflowRunId;
try {
const deadline = await markLinearDone(id, closedAt);
if (deadline) {
await sleep(new Date(deadline));
await purgeDone(id, closedAt);
}
} catch {
await releaseCleanup(id, closedAt, claim, run);
throw new Error("Bug report cleanup paused; the saved report will retry.");
}
}
async function markLinearDone(
id: string,
closedAt: string,
): Promise<string | null> {
"use step";
try {
const report = await record(id);
if (!report || report.state !== "done" || report.closed_at !== closedAt)
return null;
if (!report.linear_done_synced) {
await linearClient().done(id);
required(
await admin()
.from("bug_reports")
.update({ linear_done_synced: true, cleanup_error: null })
.eq("id", id)
.eq("state", "done")
.eq("closed_at", closedAt)
.select("id"),
);
}
return report.delete_after ?? null;
} catch (error) {
if (error instanceof LinearPermissionError)
throw new FatalError("The Linear key needs Write permission.");
throw new RetryableError(
"Could not move the Linear report to Done. Check the API key's Write permission.",
{ retryAfter: "1m" },
);
}
}
markLinearDone.maxRetries = 10;
async function purgeDone(id: string, closedAt: string) {
"use step";
try {
await purgeClosedBugReport(id, closedAt, {
record: () => record(id),
linear: linearClient(),
begin: async () =>
required(
await admin().rpc("begin_bug_purge", {
p_report: id,
p_closed_at: closedAt,
}),
),
reopen: async () => {
required(
await admin().rpc("set_bug_report_done", {
p_report: id,
p_done: false,
}),
);
},
removeFiles: async (paths) => {
required(await admin().storage.from("minegeo-support").remove(paths));
},
finish: async () => {
required(
await admin().rpc("finish_bug_purge", {
p_report: id,
p_closed_at: closedAt,
}),
);
},
});
} catch {
throw new RetryableError(
"Report deletion could not be confirmed. The saved record will retry.",
{ retryAfter: "5m" },
);
}
}
purgeDone.maxRetries = 10;
async function releaseCleanup(
id: string,
closedAt: string,
claim: string,
run: string,
) {
"use step";
required(
await admin()
.from("bug_reports")
.update({
cleanup_run_id: null,
cleanup_lease_until: new Date(Date.now() + 60000).toISOString(),
cleanup_error:
"Support cleanup paused. Check Linear Write permission and retry.",
})
.eq("id", id)
.eq("closed_at", closedAt)
.in("cleanup_run_id", [claim, run])
.select("id"),
);
console.error("Bug report cleanup paused", { id, run });
}
releaseCleanup.maxRetries = 10;