Add account based multiplayer hosting and bug report retention
This commit is contained in:
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user