fix: embed images inline in Gitea issues, fix retry dropping attachments (v0.51.6)
Two related bugs in the Gitea issue body: 1. Attachments were always plain bullet links (`- url`), even for images — so they never rendered as a preview in the Gitea issue, just a clickable URL. Image content-types now use markdown image embed (``). 2. The admin "Create Gitea issue" retry action built its own body from scratch (`ticket.description` only) and never queried attachments at all — a retry always lost them, regardless of point 1's fix. Extracted buildGiteaIssueBody() to lib/gitea.ts so both the initial ticket-creation path and the retry path share one body-building implementation instead of two independent (and inevitably diverging) ones. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,11 @@
|
|||||||
|
|
||||||
All notable changes to Epicure are documented here. This file is mirrored in-app at `/changelog` (and in the admin dashboard) via `apps/web/lib/changelog.ts` — update both together.
|
All notable changes to Epicure are documented here. This file is mirrored in-app at `/changelog` (and in the admin dashboard) via `apps/web/lib/changelog.ts` — update both together.
|
||||||
|
|
||||||
|
## 0.51.6 — 2026-07-19 13:20
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Image attachments on support tickets now embed inline in the Gitea issue (rendered preview) instead of just a plain link. Also fixed the admin "Create Gitea issue" retry action, which was dropping attachments entirely and only ever sent the ticket description.
|
||||||
|
|
||||||
## 0.51.5 — 2026-07-19 13:05
|
## 0.51.5 — 2026-07-19 13:05
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { NextRequest, NextResponse } from "next/server";
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { db, supportTickets, eq } from "@epicure/db";
|
import { db, supportTickets, supportTicketAttachments, eq } from "@epicure/db";
|
||||||
import { requireAdmin } from "@/lib/api-auth";
|
import { requireAdmin } from "@/lib/api-auth";
|
||||||
import { createGiteaIssue } from "@/lib/gitea";
|
import { createGiteaIssue, buildGiteaIssueBody } from "@/lib/gitea";
|
||||||
|
|
||||||
const UpdateTicketBody = z.object({
|
const UpdateTicketBody = z.object({
|
||||||
status: z.enum(["open", "triaged", "closed"]).optional(),
|
status: z.enum(["open", "triaged", "closed"]).optional(),
|
||||||
@@ -30,10 +30,15 @@ export async function PATCH(req: NextRequest, { params }: { params: Promise<{ id
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (parsed.data.retryGitea) {
|
if (parsed.data.retryGitea) {
|
||||||
|
const attachments = await db
|
||||||
|
.select({ key: supportTicketAttachments.storageKey, contentType: supportTicketAttachments.contentType })
|
||||||
|
.from(supportTicketAttachments)
|
||||||
|
.where(eq(supportTicketAttachments.ticketId, id));
|
||||||
|
|
||||||
const { url, error } = await createGiteaIssue({
|
const { url, error } = await createGiteaIssue({
|
||||||
type: ticket.type,
|
type: ticket.type,
|
||||||
title: ticket.title,
|
title: ticket.title,
|
||||||
body: `${ticket.description}\n\n---\nReported via Epicure support form by user \`${ticket.userId}\`.`,
|
body: buildGiteaIssueBody({ description: ticket.description, userId: ticket.userId, attachments }),
|
||||||
});
|
});
|
||||||
updates.giteaIssueUrl = url;
|
updates.giteaIssueUrl = url;
|
||||||
updates.giteaError = error;
|
updates.giteaError = error;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { z } from "zod";
|
|||||||
import { db, supportTickets, supportTicketAttachments, eq, desc } from "@epicure/db";
|
import { db, supportTickets, supportTicketAttachments, eq, desc } from "@epicure/db";
|
||||||
import { requireSession } from "@/lib/api-auth";
|
import { requireSession } from "@/lib/api-auth";
|
||||||
import { sendEmail, supportTicketReceivedHtml } from "@/lib/email";
|
import { sendEmail, supportTicketReceivedHtml } from "@/lib/email";
|
||||||
import { createGiteaIssue } from "@/lib/gitea";
|
import { createGiteaIssue, buildGiteaIssueBody } from "@/lib/gitea";
|
||||||
import { getPublicUrl, isOwnedSupportAttachmentKey } from "@/lib/storage";
|
import { getPublicUrl, isOwnedSupportAttachmentKey } from "@/lib/storage";
|
||||||
|
|
||||||
const MAX_ATTACHMENTS = 5;
|
const MAX_ATTACHMENTS = 5;
|
||||||
@@ -65,17 +65,10 @@ export async function POST(req: NextRequest) {
|
|||||||
const id = crypto.randomUUID();
|
const id = crypto.randomUUID();
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
|
|
||||||
const attachmentLinks = attachments.map((a) => getPublicUrl(a.key));
|
|
||||||
const giteaBody = [
|
|
||||||
description,
|
|
||||||
attachmentLinks.length > 0 ? `\n**Attachments:**\n${attachmentLinks.map((u) => `- ${u}`).join("\n")}` : "",
|
|
||||||
`\n---\nReported via Epicure support form by user \`${session!.user.id}\`.`,
|
|
||||||
].join("\n");
|
|
||||||
|
|
||||||
const { url: giteaIssueUrl, error: giteaError } = await createGiteaIssue({
|
const { url: giteaIssueUrl, error: giteaError } = await createGiteaIssue({
|
||||||
type,
|
type,
|
||||||
title,
|
title,
|
||||||
body: giteaBody,
|
body: buildGiteaIssueBody({ description, userId: session!.user.id, attachments }),
|
||||||
});
|
});
|
||||||
|
|
||||||
await db.insert(supportTickets).values({
|
await db.insert(supportTickets).values({
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Mirrors CHANGELOG.md at the repo root — update both together.
|
// Mirrors CHANGELOG.md at the repo root — update both together.
|
||||||
export const APP_VERSION = "0.51.5";
|
export const APP_VERSION = "0.51.6";
|
||||||
|
|
||||||
export type ChangelogEntry = {
|
export type ChangelogEntry = {
|
||||||
version: string;
|
version: string;
|
||||||
@@ -11,6 +11,13 @@ export type ChangelogEntry = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const CHANGELOG: ChangelogEntry[] = [
|
export const CHANGELOG: ChangelogEntry[] = [
|
||||||
|
{
|
||||||
|
version: "0.51.6",
|
||||||
|
date: "2026-07-19 13:20",
|
||||||
|
fixed: [
|
||||||
|
"Image attachments on support tickets now embed inline in the Gitea issue (rendered preview) instead of just a plain link. Also fixed the admin \"Create Gitea issue\" retry action, which was dropping attachments entirely and only ever sent the ticket description.",
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
version: "0.51.5",
|
version: "0.51.5",
|
||||||
date: "2026-07-19 13:05",
|
date: "2026-07-19 13:05",
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { getSiteSetting } from "@/lib/site-settings";
|
import { getSiteSetting } from "@/lib/site-settings";
|
||||||
|
import { getPublicUrl } from "@/lib/storage";
|
||||||
|
|
||||||
const LABEL_NAMES: Record<string, string[]> = {
|
const LABEL_NAMES: Record<string, string[]> = {
|
||||||
bug: ["bug"],
|
bug: ["bug"],
|
||||||
@@ -26,6 +27,27 @@ async function resolveLabelIds(baseUrl: string, token: string, repo: string, nam
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Shared by ticket creation and the admin retry action, so a retry embeds
|
||||||
|
* attachments exactly the same way the original attempt would have. Image
|
||||||
|
* attachments embed inline (``) so they render as a preview in the
|
||||||
|
* Gitea issue instead of just a clickable link — everything else (PDF,
|
||||||
|
* text, zip) stays a plain link since Gitea can't preview those anyway. */
|
||||||
|
export function buildGiteaIssueBody(opts: {
|
||||||
|
description: string;
|
||||||
|
userId: string;
|
||||||
|
attachments: { key: string; contentType: string }[];
|
||||||
|
}): string {
|
||||||
|
const attachmentLines = opts.attachments.map((a) => {
|
||||||
|
const url = getPublicUrl(a.key);
|
||||||
|
return a.contentType.startsWith("image/") ? `` : `- ${url}`;
|
||||||
|
});
|
||||||
|
return [
|
||||||
|
opts.description,
|
||||||
|
attachmentLines.length > 0 ? `\n**Attachments:**\n${attachmentLines.join("\n")}` : "",
|
||||||
|
`\n---\nReported via Epicure support form by user \`${opts.userId}\`.`,
|
||||||
|
].join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens an issue on the configured Gitea repo for a support ticket. Returns
|
* Opens an issue on the configured Gitea repo for a support ticket. Returns
|
||||||
* the issue's HTML URL on success, or null if Gitea isn't configured or the
|
* the issue's HTML URL on success, or null if Gitea isn't configured or the
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@epicure/web",
|
"name": "@epicure/web",
|
||||||
"version": "0.51.5",
|
"version": "0.51.6",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "epicure",
|
"name": "epicure",
|
||||||
"version": "0.51.5",
|
"version": "0.51.6",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "pnpm --filter web dev",
|
"dev": "pnpm --filter web dev",
|
||||||
|
|||||||
Reference in New Issue
Block a user