import { randomUUID } from "node:crypto"; import { NextRequest, NextResponse } from "next/server"; import { upsertSystemSetting } from "@/lib/db/queries"; import { requireAgentWorkflowAdmin } from "@/lib/middleware/admin"; import { uploadFile } from "@/lib/storage/service"; import { AGENT_PSD_FONTS_KEY, upsertAgentPsdFont, type AgentPsdFont, type AgentPsdFontListResponse, type AgentPsdFontMutationResponse, } from "@/lib/agents/psd-font-config"; import { readAgentPsdFonts } from "@/lib/agents/psd-font-settings"; export const runtime = "nodejs"; const MAX_FONT_FILE_SIZE = 20 * 1024 * 1024; const ALLOWED_FONT_EXTENSIONS = new Set(["ttf", "otf", "woff", "woff2"]); function jsonListError(status: number, error: string) { const payload: AgentPsdFontListResponse = { success: false, fonts: [], error, }; return NextResponse.json(payload, { status }); } function jsonMutationError(status: number, error: string) { const payload: AgentPsdFontMutationResponse = { success: false, error, }; return NextResponse.json(payload, { status }); } function getFileExtension(fileName: string) { return fileName.toLowerCase().split(".").pop() || ""; } function getDefaultFontName(fileName: string) { return fileName.replace(/\.[^.]+$/, "").trim() || "未命名字体"; } function getUploadFontFiles(formData: FormData) { const multipleFiles = formData.getAll("files").filter((item): item is File => item instanceof File); if (multipleFiles.length > 0) { return multipleFiles; } const singleFile = formData.get("file"); return singleFile instanceof File ? [singleFile] : []; } export async function GET() { const session = await requireAgentWorkflowAdmin().catch(() => null); if (!session?.user?.id) { return jsonListError(401, "未登录"); } try { const fonts = await readAgentPsdFonts(); return NextResponse.json({ success: true, fonts, } satisfies AgentPsdFontListResponse); } catch (error) { console.error("[PSD Fonts][Admin] list failed:", error); return jsonListError(503, "字体库读取失败"); } } export async function POST(request: NextRequest) { const session = await requireAgentWorkflowAdmin().catch(() => null); if (!session?.user?.id) { return jsonMutationError(401, "未登录"); } let formData: FormData; try { formData = await request.formData(); } catch { return jsonMutationError(400, "上传表单解析失败"); } const files = getUploadFontFiles(formData); const customName = String(formData.get("name") || "").trim(); const customFamily = String(formData.get("family") || "").trim(); const brandName = String(formData.get("brandName") || "").trim(); if (files.length === 0) { return jsonMutationError(400, "缺少字体文件"); } if (brandName.length > 64) { return jsonMutationError(400, "字体品牌不能超过 64 个字符"); } for (const file of files) { const extension = getFileExtension(file.name); if (!ALLOWED_FONT_EXTENSIONS.has(extension)) { return jsonMutationError(400, "字体文件仅支持 ttf / otf / woff / woff2"); } if (file.size <= 0) { return jsonMutationError(400, "字体文件不能为空"); } if (file.size > MAX_FONT_FILE_SIZE) { return jsonMutationError(400, "字体文件不能超过 20MB"); } } try { const now = new Date().toISOString(); const currentFonts = await readAgentPsdFonts(); const createdFonts = await Promise.all( files.map(async (file, index) => { const fileBuffer = await file.arrayBuffer(); const uploaded = await uploadFile(file.name, fileBuffer); const defaultName = getDefaultFontName(file.name); const resolvedName = files.length === 1 ? customName || defaultName : defaultName; const resolvedFamily = customFamily || (files.length === 1 ? customName || defaultName : defaultName); const nextFont: AgentPsdFont = { id: randomUUID(), name: resolvedName, family: resolvedFamily, brandName: brandName || undefined, fileName: file.name, fileUrl: uploaded.url, filePath: uploaded.pathname, contentType: uploaded.contentType || file.type || undefined, createdAt: now, updatedAt: new Date(Date.parse(now) + index).toISOString(), uploadedById: session.user.id, }; return nextFont; }) ); const fonts = createdFonts.reduce( (collection, font) => upsertAgentPsdFont(collection, font), currentFonts ); await upsertSystemSetting({ key: AGENT_PSD_FONTS_KEY, value: fonts, updatedBy: session.user.id, }); return NextResponse.json({ success: true, font: createdFonts[0], fonts: createdFonts, } satisfies AgentPsdFontMutationResponse); } catch (error) { console.error("[PSD Fonts][Admin] create failed:", error); return jsonMutationError(502, "字体上传失败"); } }