feat: file/screenshot attachments on support tickets (v0.49.1)
Support form now accepts up to 5 attachments (images, PDF, text, JSON, zip; 10MB each) via the existing S3/MinIO presigned-upload flow. New support_ticket_attachments table; attachment links get folded into the Gitea issue body and shown as thumbnails in both the user's ticket history and the admin support view. New presign endpoint (/api/v1/support/attachments/presign, rate-limited 20/hr) scoped to the uploading user rather than a ticket id, since the ticket doesn't exist yet at upload time. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,26 +1,18 @@
|
||||
import type { Metadata } from "next";
|
||||
import { db, users, supportTickets, eq, desc } from "@epicure/db";
|
||||
import { db, supportTickets, desc } from "@epicure/db";
|
||||
import { AdminSupportManager } from "@/components/admin/admin-support-manager";
|
||||
import { getPublicUrl } from "@/lib/storage";
|
||||
|
||||
export const metadata: Metadata = {};
|
||||
|
||||
export default async function AdminSupportPage() {
|
||||
const rows = await db
|
||||
.select({
|
||||
id: supportTickets.id,
|
||||
userEmail: users.email,
|
||||
username: users.username,
|
||||
type: supportTickets.type,
|
||||
title: supportTickets.title,
|
||||
description: supportTickets.description,
|
||||
status: supportTickets.status,
|
||||
giteaIssueUrl: supportTickets.giteaIssueUrl,
|
||||
giteaError: supportTickets.giteaError,
|
||||
createdAt: supportTickets.createdAt,
|
||||
})
|
||||
.from(supportTickets)
|
||||
.innerJoin(users, eq(supportTickets.userId, users.id))
|
||||
.orderBy(desc(supportTickets.createdAt));
|
||||
const rows = await db.query.supportTickets.findMany({
|
||||
orderBy: desc(supportTickets.createdAt),
|
||||
with: {
|
||||
attachments: true,
|
||||
user: { columns: { email: true, username: true } },
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
@@ -31,7 +23,23 @@ export default async function AdminSupportPage() {
|
||||
</p>
|
||||
</div>
|
||||
<AdminSupportManager
|
||||
initialTickets={rows.map((r) => ({ ...r, createdAt: r.createdAt.toISOString() }))}
|
||||
initialTickets={rows.map((r) => ({
|
||||
id: r.id,
|
||||
userEmail: r.user.email,
|
||||
username: r.user.username,
|
||||
type: r.type,
|
||||
title: r.title,
|
||||
description: r.description,
|
||||
status: r.status,
|
||||
giteaIssueUrl: r.giteaIssueUrl,
|
||||
giteaError: r.giteaError,
|
||||
createdAt: r.createdAt.toISOString(),
|
||||
attachments: r.attachments.map((a) => ({
|
||||
id: a.id,
|
||||
contentType: a.contentType,
|
||||
url: getPublicUrl(a.storageKey),
|
||||
})),
|
||||
}))}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user