import { NextRequest, NextResponse } from "next/server"; import { getDefaultStorageConfig, getSystemSetting, upsertSystemSetting } from "@/lib/db/queries"; import { PRODUCT_PLANNING_TEMPLATES_KEY, parseProductPlanningTemplates, } from "@/lib/product-planning/templates"; import { isProductPlanningEnabled, PRODUCT_PLANNING_DISABLED_MESSAGE, } from "@/lib/product-planning/server-feature"; import { getProductPlanningToken } from "@/lib/product-planning/server-auth"; import type { ProductPlanningTemplateMutationResponse } from "@/lib/product-planning/types"; export const runtime = "nodejs"; function isAdminToken(token: Awaited>) { const type = String(token?.type || "").trim(); return type === "admin" || type === "agent_admin"; } function jsonError(status: number, error: string) { const payload: ProductPlanningTemplateMutationResponse = { success: false, error, }; return NextResponse.json(payload, { status }); } function encodeAttachmentFileName(fileName: string) { const fallback = fileName.replace(/[^\x20-\x7E]+/g, "_") || "template.xlsx"; return `attachment; filename="${fallback}"; filename*=UTF-8''${encodeURIComponent(fileName)}`; } async function readTemplates() { const setting = await getSystemSetting({ key: PRODUCT_PLANNING_TEMPLATES_KEY }); return parseProductPlanningTemplates(setting?.value); } async function tryDeleteTemplateFile(filePath?: string) { if (!filePath) return; try { const storageConfig = await getDefaultStorageConfig().catch(() => undefined); if (!storageConfig || storageConfig.type !== "s3") return; const { deleteS3ObjectByPath, toDbS3UploadConfig } = await import("@/lib/storage/service"); const uploadConfig = toDbS3UploadConfig(storageConfig); if (!uploadConfig) return; await deleteS3ObjectByPath(filePath, uploadConfig); } catch (error) { console.warn("[ProductPlanning][Templates] delete file failed:", error); } } export async function GET( request: NextRequest, context: { params: Promise<{ id: string }> } ) { const token = await getProductPlanningToken(request); if (!token?.sub) { return jsonError(401, "未登录"); } if (!(await isProductPlanningEnabled())) { return jsonError(403, PRODUCT_PLANNING_DISABLED_MESSAGE); } try { const { id } = await context.params; const templates = await readTemplates(); const template = templates.find((entry) => entry.id === id); if (!template) { return jsonError(404, "未找到模板"); } const response = await fetch(template.fileUrl, { headers: { Accept: template.contentType || "*/*", }, cache: "no-store", signal: AbortSignal.timeout(20000), }); if (!response.ok) { return jsonError(502, "模板文件下载失败"); } const buffer = await response.arrayBuffer(); return new NextResponse(buffer, { status: 200, headers: { "Content-Type": template.contentType || response.headers.get("content-type") || "application/octet-stream", "Content-Length": String(buffer.byteLength), "Content-Disposition": encodeAttachmentFileName(template.fileName), "Cache-Control": "no-store", }, }); } catch (error) { console.error("[ProductPlanning][Templates] download failed:", error); return jsonError(502, "模板文件下载失败"); } } export async function DELETE( request: NextRequest, context: { params: Promise<{ id: string }> } ) { const token = await getProductPlanningToken(request); if (!token?.sub) { return jsonError(401, "未登录"); } if (!(await isProductPlanningEnabled())) { return jsonError(403, PRODUCT_PLANNING_DISABLED_MESSAGE); } if (!isAdminToken(token)) { return jsonError(403, "需要管理员权限"); } try { const { id } = await context.params; const templates = await readTemplates(); const template = templates.find((entry) => entry.id === id); if (!template) { return jsonError(404, "未找到模板"); } await upsertSystemSetting({ key: PRODUCT_PLANNING_TEMPLATES_KEY, value: templates.filter((entry) => entry.id !== id), updatedBy: String(token.sub), }); await tryDeleteTemplateFile(template.filePath); return NextResponse.json({ success: true, } satisfies ProductPlanningTemplateMutationResponse); } catch (error) { console.error("[ProductPlanning][Templates] delete failed:", error); return jsonError(502, "模板删除失败"); } }