mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-11 05:42:19 +00:00
Long-overdue prettier sweep; prettier had drifted vs. checked-in code across ~130 files (mostly long-argument line-break wrapping in TS and table-padding in markdown). Pure formatting — no semantic changes — and required to clear the v1.4.6 release gate's `pnpm format:check`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
97 lines
3.2 KiB
TypeScript
97 lines
3.2 KiB
TypeScript
/**
|
|
* POST /api/admin/feedback/[id]/github — promote feedback to a GitHub Issue.
|
|
*
|
|
* Reuses publishFeedbackToGithub() so the issue body matches the legacy
|
|
* /api/bugreport flow. Idempotent: if gitHubIssueUrl is already set we return
|
|
* the existing URL instead of creating a duplicate.
|
|
*/
|
|
import { apiHandler, requireAdmin } from "@/lib/api-handler";
|
|
import { apiError, apiSuccess, getClientIp } from "@/lib/api-response";
|
|
import { annotate } from "@/lib/logging/context";
|
|
import { auditLog } from "@/lib/auth/audit";
|
|
import { prisma } from "@/lib/db";
|
|
import {
|
|
publishFeedbackToGithub,
|
|
GithubPublishError,
|
|
} from "@/lib/feedback/publish-github";
|
|
import type { NextRequest } from "next/server";
|
|
|
|
export const POST = apiHandler(
|
|
async (request: NextRequest, ctx: { params: Promise<{ id: string }> }) => {
|
|
const { user: admin } = await requireAdmin();
|
|
const { id } = await ctx.params;
|
|
|
|
const feedback = await prisma.feedback.findUnique({
|
|
where: { id },
|
|
select: {
|
|
id: true,
|
|
category: true,
|
|
subject: true,
|
|
description: true,
|
|
gitHubIssueUrl: true,
|
|
screenshotBase64: true,
|
|
metadata: true,
|
|
user: { select: { username: true } },
|
|
},
|
|
});
|
|
if (!feedback) return apiError("Feedback not found", 404);
|
|
|
|
if (feedback.gitHubIssueUrl) {
|
|
return apiError(`Already published: ${feedback.gitHubIssueUrl}`, 409);
|
|
}
|
|
|
|
// Atomic claim to prevent concurrent admin clicks from creating two
|
|
// duplicate GitHub issues. Only the first caller whose updateMany
|
|
// matches (gitHubIssueUrl still null) proceeds. We write a PENDING
|
|
// sentinel now and replace it with the real URL after the API call.
|
|
const PENDING = "pending://creating";
|
|
const claim = await prisma.feedback.updateMany({
|
|
where: { id, gitHubIssueUrl: null },
|
|
data: { gitHubIssueUrl: PENDING },
|
|
});
|
|
if (claim.count !== 1) {
|
|
return apiError("Already being published by another request", 409);
|
|
}
|
|
|
|
try {
|
|
const result = await publishFeedbackToGithub({
|
|
category: feedback.category,
|
|
subject: feedback.subject,
|
|
description: feedback.description,
|
|
username: feedback.user?.username ?? "anonymous",
|
|
metadata: feedback.metadata,
|
|
});
|
|
|
|
await prisma.feedback.update({
|
|
where: { id },
|
|
data: { gitHubIssueUrl: result.issueUrl },
|
|
});
|
|
|
|
await auditLog("admin.feedback.github_publish", {
|
|
userId: admin.id,
|
|
ipAddress: getClientIp(request),
|
|
details: { feedbackId: id, issueNumber: result.issueNumber },
|
|
});
|
|
|
|
annotate({
|
|
action: { name: "admin.feedback.github_publish" },
|
|
meta: { feedback_id: id, issue_number: result.issueNumber },
|
|
});
|
|
|
|
return apiSuccess({ issueUrl: result.issueUrl });
|
|
} catch (err) {
|
|
// Release the PENDING claim so a retry can succeed.
|
|
await prisma.feedback
|
|
.updateMany({
|
|
where: { id, gitHubIssueUrl: PENDING },
|
|
data: { gitHubIssueUrl: null },
|
|
})
|
|
.catch(() => undefined);
|
|
if (err instanceof GithubPublishError) {
|
|
return apiError(err.message, err.status);
|
|
}
|
|
throw err;
|
|
}
|
|
},
|
|
);
|