From fc6da6756d4a86b2280927a058294534cad29ffc Mon Sep 17 00:00:00 2001 From: pavel444-byte Date: Sat, 10 Oct 2026 16:24:34 +0500 Subject: [PATCH] Add account based multiplayer hosting and bug report retention --- app/api/support/linear/route.ts | 22 ++ app/dashboard/[id]/page.tsx | 7 +- app/servers/[id]/page.tsx | 4 +- components/DraftEditor.tsx | 12 +- components/StartGame.tsx | 37 ++-- components/Support.tsx | 48 ++++- docs/bug-reports.md | 37 +++- docs/discord-integration.md | 4 + docs/vercel-deployment.md | 7 +- lib/bug-cleanup-workflow.ts | 146 +++++++++++++ lib/bug-lifecycle.ts | 98 +++++++++ lib/bug-report-api.ts | 63 +++++- lib/bug-retention.ts | 38 ++++ lib/discord-announcements.ts | 15 +- lib/linear-support-client.ts | 66 +++++- lib/multiplayer-api.ts | 62 +++--- lib/support-sync.ts | 11 +- scripts/test-bug-reports-sql.mjs | 127 ++++++++++++ scripts/test-bug-retention-http.mjs | 146 +++++++++++++ scripts/test-discord-http.mjs | 7 +- scripts/test-multiplayer-browser.mjs | 30 ++- scripts/test-multiplayer-http.mjs | 98 ++++++++- scripts/test-multiplayer-sql.mjs | 110 ++++++++++ scripts/test-profile-appearance-browser.mjs | 1 - shared/bug-reports.ts | 18 +- shared/database.ts | 39 ++++ shared/locales/ru.json | 13 +- shared/multiplayer.ts | 13 +- .../20261010053310_bug_report_retention.sql | 80 +++++++ ...261010053429_multiplayer_account_hosts.sql | 65 ++++++ tests/bug-reports.test.ts | 2 + tests/bug-retention.test.ts | 195 ++++++++++++++++++ tests/multiplayer.test.ts | 19 +- 33 files changed, 1538 insertions(+), 102 deletions(-) create mode 100644 lib/bug-cleanup-workflow.ts create mode 100644 lib/bug-lifecycle.ts create mode 100644 lib/bug-retention.ts create mode 100644 scripts/test-bug-retention-http.mjs create mode 100644 supabase/migrations/20261010053310_bug_report_retention.sql create mode 100644 supabase/migrations/20261010053429_multiplayer_account_hosts.sql create mode 100644 tests/bug-retention.test.ts diff --git a/app/api/support/linear/route.ts b/app/api/support/linear/route.ts index c79f685..3a70051 100644 --- a/app/api/support/linear/route.ts +++ b/app/api/support/linear/route.ts @@ -3,6 +3,7 @@ import { z } from "zod"; import { admin, required } from "@/lib/supabase"; import { verifySupportWebhook } from "@/lib/support-signature"; import { supportCommand } from "@/shared/bug-reports"; +import { reconcileBugStatus, scheduleBugCleanup } from "@/lib/bug-lifecycle"; export const runtime = "nodejs"; export async function POST(request: NextRequest) { @@ -59,6 +60,17 @@ export async function POST(request: NextRequest) { .safeParse(decoded); if (!payload.success) return new NextResponse(null, { status: 400 }); const event = payload.data; + if (event.type === "Issue" && ["create", "update"].includes(event.action)) { + try { + await reconcileBugStatus(event.data.id, true); + return NextResponse.json({ ok: true }); + } catch { + return NextResponse.json( + { error: "Support status delivery failed" }, + { status: 503 }, + ); + } + } // Only explicit new staff comments are public. Internal comments and user echoes stay private. if ( event.action !== "create" || @@ -71,6 +83,16 @@ export async function POST(request: NextRequest) { const command = supportCommand(event.data.body ?? ""); if (!command) return NextResponse.json({ ok: true }); try { + if (command.kind === "close") { + required( + await admin().rpc("receive_bug_close", { + p_report: event.data.issueId, + p_external: event.data.id, + }), + ); + await scheduleBugCleanup(event.data.issueId); + return NextResponse.json({ ok: true }); + } required( await admin().rpc("receive_bug_reply", { p_report: event.data.issueId, diff --git a/app/dashboard/[id]/page.tsx b/app/dashboard/[id]/page.tsx index 6e4c4ed..35332bf 100644 --- a/app/dashboard/[id]/page.tsx +++ b/app/dashboard/[id]/page.tsx @@ -2,6 +2,7 @@ import { getI18n } from "@/lib/i18n"; import { Header, Footer } from "@/components/Shell"; import { DraftEditor } from "@/components/DraftEditor"; import { user } from "@/lib/supabase"; +import { accountProfile } from "@/lib/profile"; import { redirect } from "next/navigation"; export default async function Draft({ params, @@ -10,15 +11,17 @@ export default async function Draft({ }) { const { t } = await getI18n(); const { id } = await params; - if (!(await user())) + const account = await user(); + if (!account) redirect(`/log-in?next=${encodeURIComponent("/dashboard/" + id)}`); + const profile = await accountProfile(account); return ( <>
{t("YOUR WORLD · YOUR LOCATIONS")}

{t("Map workshop")}

- +