150 lines
6.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { NextRequest, NextResponse } from 'next/server';
import { getServerSession } from 'next-auth';
import { authOptions } from '@/lib/auth';
import { prisma } from '@/lib/prisma';
import { deleteFromS3, uploadToS3 } from '@/lib/s3';
import { compressImage, sanitizeFileName } from '@/lib/image-processor';
import { requiresCategoryAndScene, requiresRenderer, requiresProductCategoryName, getDefaultRenderer } from '@/lib/material-validation';
// PUT /api/materials/update - 更新素材(包括预览图)
export async function PUT(request: NextRequest) {
try {
const session = await getServerSession(authOptions);
if (!session?.user) {
return NextResponse.json({ error: '请先登录' }, { status: 401 });
}
const formData = await request.formData();
const id = formData.get('id') as string;
const productModel = formData.get('productModel') as string;
const categoryName = formData.get('categoryName') as string;
const sceneName = formData.get('sceneName') as string;
const platformName = formData.get('platformName') as string | null;
const productCategoryName = formData.get('productCategoryName') as string | null;
const materialType = (formData.get('materialType') as string) || '3D场景';
const renderer = formData.get('renderer') as string | null;
const authorName = formData.get('authorName') as string;
const previewImage = formData.get('previewImage') as File | null;
// 使用统一的验证逻辑
// 基础必填字段验证
if (!id || !productModel || !materialType || !authorName) {
return NextResponse.json({ error: '缺少必填字段' }, { status: 400 });
}
if (requiresCategoryAndScene(materialType) && (!categoryName || !sceneName)) {
return NextResponse.json({ error: '请填写所有必填字段(店铺、场景)' }, { status: 400 });
}
// 标准模型必须选择品类
if (requiresProductCategoryName(materialType) && !productCategoryName) {
return NextResponse.json({ error: '标准模型类型必须选择品类' }, { status: 400 });
}
// renderer 只在3D场景类型时必填
if (requiresRenderer(materialType) && !renderer) {
return NextResponse.json({ error: '3D场景类型必须指定渲染器' }, { status: 400 });
}
// 检查素材是否存在(排除已删除的素材)
const material = await prisma.material.findFirst({
where: { id, deletedAt: null },
});
if (!material) {
return NextResponse.json({ error: '素材不存在或已被删除' }, { status: 404 });
}
// 检查权限:素材所有者、系统管理员或超级管理员都可以编辑
const user = session.user as any;
const userId = user.id;
const userRole = user.role;
// 检查是否是超级管理员或系统管理员
const { isSuperAdmin, isSystemAdmin } = await import('@/lib/department-permission');
const isUserSuperAdmin = await isSuperAdmin(userId);
const isUserSystemAdmin = isSystemAdmin(userRole);
if (material.userId !== userId && !isUserSystemAdmin && !isUserSuperAdmin) {
return NextResponse.json({ error: '您没有权限编辑此素材' }, { status: 403 });
}
let previewUrl = material.previewUrl;
// 如果上传了新的预览图
if (previewImage && previewImage.size > 0) {
// 删除旧的预览图
try {
await deleteFromS3(material.previewUrl);
} catch (error) {
console.error('Error deleting old preview:', error);
}
// 压缩并上传新的预览图
const imageBuffer = Buffer.from(await previewImage.arrayBuffer());
const compressedBuffer = await compressImage(imageBuffer, {
maxSizeKB: 300,
maxWidth: 600,
format: 'webp',
});
const sanitizedName = sanitizeFileName(previewImage.name);
const fileName = `preview-${Date.now()}-${sanitizedName}`;
previewUrl = await uploadToS3(compressedBuffer, fileName, 'image/webp');
}
// 更新素材(包含所有字段,确保数据不丢失)
const updateData: any = {
previewUrl,
productModel,
// 使用统一的验证逻辑判断字段是否需要更新
categoryName: requiresCategoryAndScene(materialType) ? (categoryName || material.categoryName) : (categoryName !== undefined && categoryName !== null ? categoryName : material.categoryName),
sceneName: requiresCategoryAndScene(materialType) ? (sceneName || material.sceneName) : (sceneName !== undefined && sceneName !== null ? sceneName : material.sceneName),
platformName: platformName !== undefined && platformName !== null ? platformName : (material.platformName || ''),
materialType,
authorName,
};
// 标准模型必须更新 productCategoryName
if (requiresProductCategoryName(materialType)) {
updateData.productCategoryName = productCategoryName || material.productCategoryName;
} else {
updateData.productCategoryName = productCategoryName !== undefined && productCategoryName !== null ? productCategoryName : material.productCategoryName;
}
// 使用统一的验证逻辑更新renderer
if (requiresRenderer(materialType)) {
// 3D场景类型:必须提供renderer,如果没有提供则使用默认值或保留原值
updateData.renderer = renderer || material.renderer || getDefaultRenderer(materialType);
} else {
// 非3D场景类型,保持原renderer值(如果有)
updateData.renderer = material.renderer;
}
const updatedMaterial = await prisma.material.update({
where: { id },
data: updateData,
include: {
user: {
select: {
username: true,
},
},
},
});
const response = {
...updatedMaterial,
zipFileSize: updatedMaterial.zipFileSize.toString(),
};
return NextResponse.json(
{ message: '更新成功', material: response },
{ status: 200 }
);
} catch (error) {
console.error('Update error:', error);
return NextResponse.json({ error: '更新失败,请稍后再试' }, { status: 500 });
}
}