144 lines
4.5 KiB
TypeScript
144 lines
4.5 KiB
TypeScript
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<ReturnType<typeof getProductPlanningToken>>) {
|
|
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, "模板删除失败");
|
|
}
|
|
}
|