import { Elysia, t } from "elysia"; import { sql } from "kysely"; import config from "../config.ts"; import { ALLOWED_REACTIONS, COMMENT_MAX_PER_MIN, LABEL_WRITE_MAX_PER_MIN, PATCH_CREATE_MAX_PER_MIN, PATCHES_PER_PAGE, RATE_WINDOW_MIN_MS, REACTION_MAX_PER_MIN, } from "../constants.ts"; import { patchesLabelFilter } from "../db/helpers.ts"; import { db, getRepo, type LabelRow } from "../db/index.ts"; import { authorizeCommentEdit } from "../lib/commentAuth.ts"; import { paginate } from "../lib/pagination.ts"; import { rateLimit } from "../lib/rateLimiter.ts"; import { requireAdmin, requireAuth, resolveSession, } from "../middleware/session.ts"; import { extractPatchMeta, git } from "../services/git.ts"; import { prepareDiff } from "../services/highlightWorker.ts"; import { renderMarkdown } from "../services/markdown.ts"; import { patchCache } from "../services/patchCache.ts"; import { buildReactionCounts } from "../services/reactions.ts"; import { NewPatch } from "../views/patches/NewPatch.tsx"; import { PatchDetail } from "../views/patches/PatchDetail.tsx"; import { PatchList } from "../views/patches/PatchList.tsx"; import { html } from "../views/render.tsx"; function isValidPatch(content: string): boolean { const lines = content.split("\n"); return lines.some( (l) => l.startsWith("diff --git ") || l.startsWith("--- ") || l.startsWith("+++ ") || l.startsWith("@@ ") || l.startsWith("Index: "), ); } async function runPatchCheck( repoName: string, patchId: number, patchContent: string, ) { const result = await git.checkPatch(repoName, patchContent); const applyResult = { status: result.clean ? ("clean" as const) : ("conflict" as const), output: result.output, }; patchCache.set(patchId, applyResult); return applyResult; } export const patchRoutes = new Elysia() .guard({ cookie: t.Cookie({ session: t.Optional(t.String()) }), }) .get( "/:repo/patches", async ({ params, query, cookie }) => { const user = await resolveSession(cookie.session.value); const repo = await getRepo(params.repo, user?.isAdmin ?? false); if (!repo) return new Response("Not found", { status: 404 }); const status = ["open", "merged", "closed"].includes( query.status ?? "", ) ? query.status! : "open"; // Parse label filter const rawLabels = query.labels; const labelIds: number[] = ( Array.isArray(rawLabels) ? rawLabels : rawLabels ? [rawLabels] : [] ) .map((v) => parseInt(v, 10)) .filter((n) => !Number.isNaN(n)); const repoLabels = await db .selectFrom("labels") .selectAll() .where("repo_id", "=", repo.id) .orderBy("name", "asc") .execute(); let countQuery = db .selectFrom("patches") .select([ "patches.status", db.fn.countAll().as("count"), ]) .where("patches.repo_id", "=", repo.id); if (labelIds.length > 0) { countQuery = countQuery.where((eb) => patchesLabelFilter(eb, labelIds), ); } const allCounts = await countQuery .groupBy("patches.status") .execute(); const counts: Record = Object.fromEntries( allCounts.map((r) => [r.status, Number(r.count)]), ); const { page: safePage, totalPages, offset, } = paginate(query.page, counts[status] ?? 0, PATCHES_PER_PAGE); let listQuery = db .selectFrom("patches") .leftJoin("users", "users.id", "patches.author_id") .select([ "patches.id", "patches.repo_id", "patches.author_id", "patches.number", "patches.title", "patches.description", "patches.patch_content", "patches.status", "patches.author_name", "patches.author_email", "patches.created_at", "patches.updated_at", "patches.edited_at", "patches.version", "users.username as author_username", "users.avatar_version as author_avatar_version", ]) .where("patches.repo_id", "=", repo.id) .where("patches.status", "=", status); if (labelIds.length > 0) { listQuery = listQuery.where((eb) => patchesLabelFilter(eb, labelIds), ); } const patches = await listQuery .orderBy("patches.number", "desc") .limit(PATCHES_PER_PAGE) .offset(offset) .execute(); // Batch-fetch labels for displayed patches const patchIds = patches.map((p) => p.id); const patchLabelsRows = patchIds.length > 0 ? await db .selectFrom("patch_labels") .innerJoin( "labels", "labels.id", "patch_labels.label_id", ) .select([ "patch_labels.patch_id", "labels.id", "labels.name", "labels.color", ]) .where("patch_labels.patch_id", "in", patchIds) .execute() : []; const labelsByPatchId = new Map(); for (const row of patchLabelsRows) { const list = labelsByPatchId.get(row.patch_id) ?? []; list.push({ id: row.id, repo_id: repo.id, name: row.name, color: row.color, created_at: "", }); labelsByPatchId.set(row.patch_id, list); } const labelsParam = labelIds.length > 0 ? `&labels=${labelIds.map(String).join(",")}` : ""; const pagination = { page: safePage, totalPages, pageUrlTemplate: `/${repo.name}/patches?status=${status}${labelsParam}&page={page}`, }; return html( , ); }, { query: t.Object({ status: t.Optional(t.String()), page: t.Optional(t.Numeric()), labels: t.Optional(t.Union([t.String(), t.Array(t.String())])), }), }, ) .get("/:repo/patches/new", async ({ params, cookie }) => { const user = await resolveSession(cookie.session.value); const deny = requireAuth(user); if (deny) return deny; const repo = await getRepo(params.repo, user?.isAdmin ?? false); if (!repo) return new Response("Not found", { status: 404 }); const labels = await db .selectFrom("labels") .selectAll() .where("repo_id", "=", repo.id) .orderBy("name", "asc") .execute(); return html( , ); }) .post( "/:repo/patches", async ({ params, body, cookie, request, server }) => { const user = await resolveSession(cookie.session.value); const deny = requireAuth(user); if (deny) return deny; const limited = rateLimit( request, server, user?.id ?? null, "patch-create", PATCH_CREATE_MAX_PER_MIN, RATE_WINDOW_MIN_MS, ); if (limited) return limited; const repo = await getRepo(params.repo, user?.isAdmin ?? false); if (!repo) return new Response("Not found", { status: 404 }); const getLabels = () => db .selectFrom("labels") .selectAll() .where("repo_id", "=", repo.id) .orderBy("name", "asc") .execute(); if (!body.title?.trim()) { return html( , ); } if (!body.patch_file) { return html( , ); } if (body.patch_file.size > config.MAX_USER_UPLOAD_BYTES) { return html( , ); } const patchContent = await body.patch_file.text(); if (!patchContent.trim()) { return html( , ); } // Validate it looks like a patch file if (!isValidPatch(patchContent)) { return html( , ); } const uploadMeta = extractPatchMeta(patchContent); if (!uploadMeta.subject) { return html( , ); } if (!uploadMeta.author || !uploadMeta.email) { return html( , ); } if (!uploadMeta.date) { return html( , ); } const rawIds = user!.isAdmin || repo.allow_user_labels === 1 ? body.label_ids : undefined; const labelIds = rawIds ? (Array.isArray(rawIds) ? rawIds : [rawIds]) .map(Number) .filter(Boolean) : []; const now = new Date().toISOString(); const { number, result } = await db .transaction() .execute(async (trx) => { const { patch_seq } = await trx .updateTable("repositories") .set({ patch_seq: sql`patch_seq + 1` }) .where("id", "=", repo.id) .returning("patch_seq") .executeTakeFirstOrThrow(); const inserted = await trx .insertInto("patches") .values({ repo_id: repo.id, author_id: user?.id, number: patch_seq, title: body.title!.trim(), description: body.description?.trim() ?? "", patch_content: patchContent, status: "open", author_name: uploadMeta.author, author_email: uploadMeta.email, created_at: now, updated_at: now, version: crypto.randomUUID(), }) .returning("id") .executeTakeFirstOrThrow(); if (labelIds.length > 0) { const validLabels = await trx .selectFrom("labels") .select("id") .where("repo_id", "=", repo.id) .where("id", "in", labelIds) .execute(); if (validLabels.length > 0) { await trx .insertInto("patch_labels") .values( validLabels.map((l) => ({ patch_id: inserted.id, label_id: l.id, })), ) .onConflict((oc) => oc.doNothing()) .execute(); } } return { number: patch_seq, result: inserted }; }); await runPatchCheck(repo.name, result.id, patchContent); return new Response(null, { status: 302, headers: { Location: `/${repo.name}/patches/${number}` }, }); }, { body: t.Object({ title: t.Optional( t.String({ maxLength: config.MAX_TITLE_BYTES }), ), description: t.Optional( t.String({ maxLength: config.MAX_TEXT_BODY_BYTES }), ), patch_file: t.Optional(t.File()), label_ids: t.Optional( t.Union([t.String(), t.Array(t.String())]), ), }), }, ) .get( "/:repo/patches/:number", async ({ params, query, cookie }) => { const user = await resolveSession(cookie.session.value); const repo = await getRepo(params.repo, user?.isAdmin ?? false); if (!repo) return new Response("Not found", { status: 404 }); const patchNum = parseInt(params.number, 10); const patch = await db .selectFrom("patches") .leftJoin("users", "users.id", "patches.author_id") .select([ "patches.id", "patches.repo_id", "patches.author_id", "patches.number", "patches.title", "patches.description", "patches.patch_content", "patches.status", "patches.author_name", "patches.author_email", "patches.created_at", "patches.updated_at", "patches.edited_at", "patches.version", "users.username as author_username", "users.avatar_version as author_avatar_version", ]) .where("patches.repo_id", "=", repo.id) .where("patches.number", "=", patchNum) .executeTakeFirst(); if (!patch) return new Response("Not found", { status: 404 }); const descriptionHtml = patch.description ? renderMarkdown(patch.description) : ""; let applyResult = patchCache.get(patch.id) ?? null; // Cold cache (e.g. server restart) — re-check synchronously for open patches only if (!applyResult && patch.status === "open") { applyResult = await runPatchCheck( repo.name, patch.id, patch.patch_content, ); } const files = await prepareDiff( patch.patch_content, `patch:${patch.id}`, ); const comments = await db .selectFrom("patch_comments") .leftJoin("users", "users.id", "patch_comments.author_id") .select([ "patch_comments.id", "patch_comments.patch_id", "patch_comments.author_id", "patch_comments.body", "patch_comments.created_at", "patch_comments.edited_at", "users.username as author_username", "users.avatar_version as author_avatar_version", ]) .where("patch_comments.patch_id", "=", patch.id) .orderBy("patch_comments.created_at", "asc") .execute(); const commentsWithHtml = comments.map((c) => ({ ...c, bodyHtml: renderMarkdown(c.body), })); const allReactions = await db .selectFrom("patch_reactions") .selectAll() .where("patch_id", "=", patch.id) .execute(); const reactions = buildReactionCounts(allReactions, null, user?.id); const commentReactions = new Map( comments.map((c) => [ c.id, buildReactionCounts(allReactions, c.id, user?.id), ]), ); const tab = query.tab === "changes" ? ("changes" as const) : ("conversation" as const); const patchMeta = extractPatchMeta(patch.patch_content); const patchLabels = await db .selectFrom("patch_labels") .innerJoin("labels", "labels.id", "patch_labels.label_id") .select([ "labels.id", "labels.repo_id", "labels.name", "labels.color", "labels.created_at", ]) .where("patch_labels.patch_id", "=", patch.id) .execute(); const repoLabels = await db .selectFrom("labels") .selectAll() .where("repo_id", "=", repo.id) .orderBy("name", "asc") .execute(); return html( , ); }, { query: t.Object({ tab: t.Optional(t.String()) }), }, ) .post( "/:repo/patches/:number/merge", async ({ params, body, cookie }) => { const user = await resolveSession(cookie.session.value); const deny = requireAdmin(user); if (deny) return deny; const repo = await getRepo(params.repo, true); if (!repo) return new Response("Not found", { status: 404 }); const patchNum = parseInt(params.number, 10); const patch = await db .selectFrom("patches") .select(["id", "patch_content", "status", "version"]) .where("repo_id", "=", repo.id) .where("number", "=", patchNum) .executeTakeFirst(); if (!patch) return new Response("Not found", { status: 404 }); // Reject if the patch file was changed after the admin loaded the page if (body.version !== patch.version) { return new Response( "The patch file was updated after you loaded this page. Please review the new version before merging.", { status: 409 }, ); } // Atomically claim the merge slot before the slow git operation to // prevent two concurrent requests from both applying the same patch. const claimed = await db .updateTable("patches") .set({ status: "merged", updated_at: new Date().toISOString() }) .where("id", "=", patch.id) .where("status", "=", "open") .where("version", "=", patch.version) .executeTakeFirst(); if (!claimed || claimed.numUpdatedRows === 0n) return new Response("Patch is not open", { status: 400 }); const mergeMeta = extractPatchMeta(patch.patch_content); try { await git.applyPatch( repo.name, patch.patch_content, mergeMeta.author, mergeMeta.email, config.COMMITTER_NAME, config.COMMITTER_EMAIL, ); } catch (err) { // Roll back the status if the git operation fails await db .updateTable("patches") .set({ status: "open", updated_at: new Date().toISOString(), }) .where("id", "=", patch.id) .execute(); throw err; } patchCache.invalidate(patch.id); return new Response(null, { status: 302, headers: { Location: `/${repo.name}/patches/${patchNum}` }, }); }, { body: t.Object({ version: t.String() }), }, ) .post( "/:repo/patches/:number/upload", async ({ params, body, cookie, request, server }) => { const user = await resolveSession(cookie.session.value); const deny = requireAuth(user); if (deny) return deny; const limited = rateLimit( request, server, user?.id ?? null, "patch-create", PATCH_CREATE_MAX_PER_MIN, RATE_WINDOW_MIN_MS, ); if (limited) return limited; const repo = await getRepo(params.repo, user?.isAdmin ?? false); if (!repo) return new Response("Not found", { status: 404 }); const patchNum = parseInt(params.number, 10); const patch = await db .selectFrom("patches") .select(["id", "author_id", "status"]) .where("repo_id", "=", repo.id) .where("number", "=", patchNum) .executeTakeFirst(); if (!patch) return new Response("Not found", { status: 404 }); if (patch.author_id !== user?.id && !user?.isAdmin) return new Response("Forbidden", { status: 403 }); if (patch.status !== "open") return new Response("Patch is not open", { status: 400 }); if (!body.patch_file || body.patch_file.size === 0) { return new Response("Patch file is required", { status: 400 }); } if (body.patch_file.size > config.MAX_USER_UPLOAD_BYTES) { return new Response("Patch file is too large", { status: 400 }); } const patchContent = await body.patch_file.text(); if (!patchContent.trim()) { return new Response("Patch file is empty", { status: 400 }); } if (!isValidPatch(patchContent)) { return new Response( "File does not appear to be a valid patch file", { status: 400 }, ); } const uploadMeta = extractPatchMeta(patchContent); if ( !uploadMeta.subject || !uploadMeta.author || !uploadMeta.email || !uploadMeta.date ) { return new Response( "Patch is missing required headers (Subject, From, Date)", { status: 400 }, ); } const newVersion = crypto.randomUUID(); await db .updateTable("patches") .set({ patch_content: patchContent, author_name: uploadMeta.author, author_email: uploadMeta.email, version: newVersion, updated_at: new Date().toISOString(), }) .where("id", "=", patch.id) .execute(); patchCache.invalidate(patch.id); runPatchCheck(repo.name, patch.id, patchContent); return new Response(null, { status: 302, headers: { Location: `/${repo.name}/patches/${patchNum}` }, }); }, { body: t.Object({ patch_file: t.Optional(t.File()), }), }, ) .post("/:repo/patches/:number/close", async ({ params, cookie }) => { const user = await resolveSession(cookie.session.value); const deny = requireAdmin(user); if (deny) return deny; const repo = await getRepo(params.repo, true); if (!repo) return new Response("Not found", { status: 404 }); const patchNum = parseInt(params.number, 10); const patch = await db .selectFrom("patches") .select("id") .where("repo_id", "=", repo.id) .where("number", "=", patchNum) .executeTakeFirst(); if (!patch) return new Response("Not found", { status: 404 }); // Toggle open↔closed atomically; exclude merged patches from the WHERE // so that numUpdatedRows = 0 means the patch is merged (or gone). const toggled = await db .updateTable("patches") .set({ status: sql`CASE WHEN status = 'open' THEN 'closed' ELSE 'open' END`, updated_at: new Date().toISOString(), }) .where("id", "=", patch.id) .where("status", "!=", "merged") .executeTakeFirst(); if (!toggled || toggled.numUpdatedRows === 0n) return new Response("Patch is merged", { status: 400 }); return new Response(null, { status: 302, headers: { Location: `/${repo.name}/patches/${patchNum}` }, }); }) .post("/:repo/patches/:number/delete", async ({ params, cookie }) => { const user = await resolveSession(cookie.session.value); const deny = requireAuth(user); if (deny) return deny; const repo = await getRepo(params.repo, user?.isAdmin ?? false); if (!repo) return new Response("Not found", { status: 404 }); const patchNum = parseInt(params.number, 10); const patch = await db .selectFrom("patches") .select(["id", "author_id"]) .where("repo_id", "=", repo.id) .where("number", "=", patchNum) .executeTakeFirst(); if (!patch) return new Response("Not found", { status: 404 }); if (patch.author_id !== user?.id && !user?.isAdmin) return new Response("Forbidden", { status: 403 }); patchCache.invalidate(patch.id); await db.deleteFrom("patches").where("id", "=", patch.id).execute(); return new Response(null, { status: 302, headers: { Location: `/${repo.name}/patches` }, }); }) .post( "/:repo/patches/:number/comments", async ({ params, body, cookie, request, server }) => { const user = await resolveSession(cookie.session.value); const deny = requireAuth(user); if (deny) return deny; const limited = rateLimit( request, server, user?.id ?? null, "comment", COMMENT_MAX_PER_MIN, RATE_WINDOW_MIN_MS, ); if (limited) return limited; const repo = await getRepo(params.repo, user?.isAdmin ?? false); if (!repo) return new Response("Not found", { status: 404 }); const patchNum = parseInt(params.number, 10); const patch = await db .selectFrom("patches") .select(["id", "status"]) .where("repo_id", "=", repo.id) .where("number", "=", patchNum) .executeTakeFirst(); if (!patch) return new Response("Not found", { status: 404 }); const { body: commentBody } = body; if (!commentBody?.trim()) { return new Response(null, { status: 302, headers: { Location: `/${repo.name}/patches/${patchNum}` }, }); } await db.transaction().execute(async (trx) => { const now = new Date().toISOString(); await trx .insertInto("patch_comments") .values({ patch_id: patch.id, author_id: user?.id, body: commentBody.trim(), created_at: now, }) .execute(); await trx .updateTable("patches") .set({ updated_at: now }) .where("id", "=", patch.id) .execute(); }); return new Response(null, { status: 302, headers: { Location: `/${repo.name}/patches/${patchNum}` }, }); }, { body: t.Object({ body: t.String({ maxLength: config.MAX_TEXT_BODY_BYTES }), }), }, ) .post( "/:repo/patches/:number/react", async ({ params, body, cookie, request, server }) => { const user = await resolveSession(cookie.session.value); const deny = requireAuth(user); if (deny) return deny; const limited = rateLimit( request, server, user?.id ?? null, "reaction", REACTION_MAX_PER_MIN, RATE_WINDOW_MIN_MS, ); if (limited) return limited; const repo = await getRepo(params.repo, user?.isAdmin ?? false); if (!repo) return new Response("Not found", { status: 404 }); const { emoji, comment_id } = body; if (!ALLOWED_REACTIONS.has(emoji)) { return new Response("Invalid emoji", { status: 400 }); } const patchNum = parseInt(params.number, 10); const patch = await db .selectFrom("patches") .select(["id"]) .where("repo_id", "=", repo.id) .where("number", "=", patchNum) .executeTakeFirst(); if (!patch) return new Response("Not found", { status: 404 }); const commentId = comment_id ? parseInt(comment_id, 10) : null; await db.transaction().execute(async (trx) => { const existing = await trx .selectFrom("patch_reactions") .select(["id", "emoji"]) .where("patch_id", "=", patch.id) .where((eb) => commentId !== null ? eb("comment_id", "=", commentId) : eb("comment_id", "is", null), ) .where("user_id", "=", user!.id) .executeTakeFirst(); if (existing) { if (existing.emoji === emoji) { await trx .deleteFrom("patch_reactions") .where("id", "=", existing.id) .execute(); } else { await trx .updateTable("patch_reactions") .set({ emoji }) .where("id", "=", existing.id) .execute(); } } else { await trx .insertInto("patch_reactions") .values({ patch_id: patch.id, comment_id: commentId, user_id: user!.id, emoji, }) .execute(); } }); return new Response(null, { status: 303, headers: { Location: `/${repo.name}/patches/${patchNum}` }, }); }, { body: t.Object({ emoji: t.String(), comment_id: t.Optional(t.String()), }), }, ) .post( "/:repo/patches/:number/comments/:id/edit", async ({ params, body, cookie, request, server }) => { const user = await resolveSession(cookie.session.value); const deny = requireAuth(user); if (deny) return deny; const limited = rateLimit( request, server, user?.id ?? null, "comment", COMMENT_MAX_PER_MIN, RATE_WINDOW_MIN_MS, ); if (limited) return limited; const repo = await getRepo(params.repo, user?.isAdmin ?? false); if (!repo) return new Response("Not found", { status: 404 }); const denyComment = await authorizeCommentEdit( "patch", params.id, repo.id, user, ); if (denyComment) return denyComment; const patchNum = parseInt(params.number, 10); await db .updateTable("patch_comments") .set({ body: body.edit_body.trim(), edited_at: new Date().toISOString(), }) .where("id", "=", params.id) .execute(); return new Response(null, { status: 302, headers: { Location: `/${repo.name}/patches/${patchNum}` }, }); }, { params: t.Object({ repo: t.String(), number: t.String(), id: t.Numeric(), }), body: t.Object({ edit_body: t.String({ maxLength: config.MAX_TEXT_BODY_BYTES }), }), }, ) .post( "/:repo/patches/:number/edit", async ({ params, body, cookie, request, server }) => { const user = await resolveSession(cookie.session.value); const deny = requireAuth(user); if (deny) return deny; const limited = rateLimit( request, server, user?.id ?? null, "comment", COMMENT_MAX_PER_MIN, RATE_WINDOW_MIN_MS, ); if (limited) return limited; const repo = await getRepo(params.repo, user?.isAdmin ?? false); if (!repo) return new Response("Not found", { status: 404 }); const patchNum = parseInt(params.number, 10); const patch = await db .selectFrom("patches") .select(["id", "author_id", "status"]) .where("repo_id", "=", repo.id) .where("number", "=", patchNum) .executeTakeFirst(); if (!patch) return new Response("Not found", { status: 404 }); if (patch.author_id !== user?.id && !user?.isAdmin) return new Response("Forbidden", { status: 403 }); if (patch.status !== "open" && !user?.isAdmin) return new Response("Forbidden", { status: 403 }); await db .updateTable("patches") .set({ title: body.title.trim(), description: body.edit_description ?? "", edited_at: new Date().toISOString(), updated_at: new Date().toISOString(), }) .where("id", "=", patch.id) .execute(); return new Response(null, { status: 302, headers: { Location: `/${repo.name}/patches/${patchNum}` }, }); }, { body: t.Object({ title: t.String({ maxLength: config.MAX_TITLE_BYTES }), edit_description: t.Optional( t.String({ maxLength: config.MAX_TEXT_BODY_BYTES }), ), }), }, ) .post( "/:repo/patches/:number/labels/add", async ({ params, body, cookie, request, server }) => { const user = await resolveSession(cookie.session.value); if (!user) return new Response("Unauthorized", { status: 401 }); const limited = rateLimit( request, server, user.id, "label-write", LABEL_WRITE_MAX_PER_MIN, RATE_WINDOW_MIN_MS, ); if (limited) return limited; const repo = await getRepo(params.repo, user.isAdmin); if (!repo) return new Response("Not found", { status: 404 }); const patchNum = parseInt(params.number, 10); const patch = await db .selectFrom("patches") .select(["id", "author_id"]) .where("repo_id", "=", repo.id) .where("number", "=", patchNum) .executeTakeFirst(); if (!patch) return new Response("Not found", { status: 404 }); const canManage = user.isAdmin || (repo.allow_user_labels === 1 && user.id === patch.author_id); if (!canManage) return new Response("Forbidden", { status: 403 }); const label = await db .selectFrom("labels") .select(["id"]) .where("id", "=", body.label_id) .where("repo_id", "=", repo.id) .executeTakeFirst(); if (!label) { return new Response(null, { status: 302, headers: { Location: `/${repo.name}/patches/${patchNum}` }, }); } await db .insertInto("patch_labels") .values({ patch_id: patch.id, label_id: label.id }) .onConflict((oc) => oc.doNothing()) .execute(); return new Response(null, { status: 302, headers: { Location: `/${repo.name}/patches/${patchNum}` }, }); }, { params: t.Object({ repo: t.String(), number: t.String() }), body: t.Object({ label_id: t.Numeric() }), }, ) .post( "/:repo/patches/:number/labels/remove", async ({ params, body, cookie, request, server }) => { const user = await resolveSession(cookie.session.value); if (!user) return new Response("Unauthorized", { status: 401 }); const limited = rateLimit( request, server, user.id, "label-write", LABEL_WRITE_MAX_PER_MIN, RATE_WINDOW_MIN_MS, ); if (limited) return limited; const repo = await getRepo(params.repo, user.isAdmin); if (!repo) return new Response("Not found", { status: 404 }); const patchNum = parseInt(params.number, 10); const patch = await db .selectFrom("patches") .select(["id", "author_id"]) .where("repo_id", "=", repo.id) .where("number", "=", patchNum) .executeTakeFirst(); if (!patch) return new Response("Not found", { status: 404 }); const canManage = user.isAdmin || (repo.allow_user_labels === 1 && user.id === patch.author_id); if (!canManage) return new Response("Forbidden", { status: 403 }); await db .deleteFrom("patch_labels") .where("patch_id", "=", patch.id) .where("label_id", "=", body.label_id) .execute(); return new Response(null, { status: 302, headers: { Location: `/${repo.name}/patches/${patchNum}` }, }); }, { params: t.Object({ repo: t.String(), number: t.String() }), body: t.Object({ label_id: t.Numeric() }), }, );