Add account based multiplayer hosting and bug report retention

This commit is contained in:
2026-10-10 16:24:34 +05:00
parent 9af71c85c2
commit fc6da6756d
33 changed files with 1538 additions and 102 deletions

View File

@@ -2,6 +2,7 @@ import "server-only";
import { createHash, randomBytes, randomUUID } from "node:crypto";
import { NextRequest, NextResponse } from "next/server";
import { z } from "zod";
import { after } from "next/server";
import { admin, required, user } from "./supabase";
import { accountProfile } from "./profile";
import { requestOrigin } from "./request-origin";
@@ -14,6 +15,7 @@ import {
} from "../shared/bug-reports";
import { supportScreenshot } from "./support-images";
import { supportReady, syncBugAfterResponse } from "./support-sync";
import { scheduleBugCleanup, reconcileBugStatus } from "./bug-lifecycle";
type Dependencies = {
jsonBody: (request: NextRequest) => Promise<unknown>;
@@ -51,9 +53,14 @@ export async function bugReportApi(
? record.owner_id === account?.id
: !!saved && record.access_hash === hash;
async function owned(id: string) {
const record = required(
await db.from("bug_reports").select("*").eq("id", id).maybeSingle(),
);
const result = await db
.from("bug_reports")
.select("*")
.eq("id", id)
.maybeSingle();
if (result.error) required(result);
const record = result.data;
if (!record) throw new ServiceError("Report not found", 404);
if (!owns(record)) throw new ServiceError("Report not found", 404);
return record;
}
@@ -79,6 +86,10 @@ export async function bugReportApi(
description: record.description,
page_path: record.page_path,
state: record.state,
closed_at: record.closed_at,
delete_after: record.delete_after,
linear_done_synced: record.linear_done_synced,
cleanup_error: record.cleanup_error,
linear_identifier: record.linear_identifier,
glow_requested: record.glow_requested,
created_at: record.created_at,
@@ -102,6 +113,12 @@ export async function bugReportApi(
.order("created_at", { ascending: false })
.limit(50),
);
after(async () => {
for (let i = 0; i < rows.length; i += 5)
await Promise.allSettled(
rows.slice(i, i + 5).map((row) => reconcileBugStatus(row.id)),
);
});
return respond({
ready: supportReady(),
signedIn: !!account,
@@ -179,8 +196,14 @@ export async function bugReportApi(
const id = z.string().uuid().parse(path[1]);
const record = await owned(id);
if (path.length === 2 && request.method === "GET") {
try {
await reconcileBugStatus(id);
} catch {
console.error("Bug report status will retry.");
}
if (record.state !== "draft") syncBugAfterResponse(id);
return respond(await view(record));
if (record.state === "done") await scheduleBugCleanup(id);
return respond(await view(await owned(id)));
}
if (path.length !== 3 || request.method !== "POST")
return NextResponse.json({ error: "Not found" }, { status: 404 });
@@ -243,6 +266,8 @@ export async function bugReportApi(
return respond(await view(await owned(id)));
}
if (path[2] === "messages") {
if (record.state === "done")
throw new ServiceError("This bug report is Done", 409);
if (record.state === "draft")
throw new ServiceError("Submit this report before sending messages", 409);
const body = bugMessageSchema.parse(await jsonBody(request));
@@ -289,6 +314,36 @@ export async function bugReportApi(
syncBugAfterResponse(id);
return respond(await view(record));
}
if (path[2] === "close") {
z.object({})
.strict()
.parse(await jsonBody(request));
if (!record.issue_synced)
throw new ServiceError(
"Wait for support to receive this report before closing it",
409,
);
required(
await db.rpc("set_bug_report_done", { p_report: id, p_done: true }),
);
await scheduleBugCleanup(id);
return respond(await view(await owned(id)));
}
if (path[2] === "retry-cleanup" && record.state === "done") {
z.object({})
.strict()
.parse(await jsonBody(request));
required(
await db
.from("bug_reports")
.update({ cleanup_lease_until: null })
.eq("id", id)
.is("cleanup_run_id", null)
.select("id"),
);
await scheduleBugCleanup(id);
return respond(await view(await owned(id)));
}
if (path[2] === "claim") {
if (!account) throw new ServiceError("Sign in to link this report", 401);
required(