150 lines
6.1 KiB
TypeScript
150 lines
6.1 KiB
TypeScript
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 });
|
||
}
|
||
}
|