mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 13:22:14 +00:00
The bug-report form accepted an image attachment that was stored in the
local DB but never reached the published GitHub issue — GitHub does not
accept inline base64 data URIs in issue bodies and offers no public API
to attach images to an issue programmatically. The publisher only
appended a placeholder line ("**Screenshot:** A screenshot was attached
in the original report.") which was visibly misleading to anyone
reading the issue (#108).
- Drop the file input, preview, validation, and unused icons from
src/app/bugreport/page.tsx.
- Remove hasScreenshot from FeedbackInput and the corresponding line
from publish-github.ts so feedback promotion no longer adds the
misleading note (applies to legacy rows whose screenshots are still
stored locally).
- Update the admin feedback→GitHub call site to match the new shape.
The screenshotBase64 column, the screenshot ingest path on /api/feedback,
and the admin-side preview of previously-submitted screenshots are
unchanged — existing reports keep their attachments locally.
100 lines
3.2 KiB
TypeScript
100 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;
|
|
}
|
|
},
|
|
);
|