386 lines
9.8 KiB
TypeScript

/**
* 素材资产管理接口
* GET - 获取资产列表
* POST - 添加资产
* DELETE - 删除资产
*/
import { NextRequest, NextResponse } from 'next/server';
import { getServerSession } from 'next-auth';
import { authOptions } from '@/lib/auth';
import { prisma } from '@/lib/prisma';
import { getAssetsByMaterialId, addAsset } from '@/lib/product-package';
import { deleteFileIfUnreferenced } from '@/lib/file-cleanup';
import { createMaterialLog } from '@/lib/material-log';
import { normalizeDisplayImages } from '@/lib/material-cleanup';
import {
canUserViewMaterial,
isSuperAdmin,
isSystemAdmin,
} from '@/lib/department-permission';
type AssetRouteSessionUser = {
id: string;
role?: string | null;
department?: string | null;
managedDepartment?: string | null;
isSuperAdmin?: boolean | null;
};
async function canManageMaterial(user: AssetRouteSessionUser, materialUserId: string) {
if (materialUserId === user.id || isSystemAdmin(user.role) || user.isSuperAdmin === true) {
return true;
}
return isSuperAdmin(user.id);
}
async function canViewMaterial(
user: AssetRouteSessionUser,
material: { userId: string; materialType: string },
) {
if (material.userId === user.id) {
return true;
}
return canUserViewMaterial(
user.department,
user.role || 'USER',
user.managedDepartment,
material.materialType,
user.id,
);
}
/**
* GET - 获取资产列表
*/
export async function GET(
request: NextRequest,
{ params }: { params: { id: string } }
) {
try {
const session = await getServerSession(authOptions);
if (!session?.user || !(session.user as any).id) {
return NextResponse.json(
{ success: false, error: '未登录' },
{ status: 401 }
);
}
const materialId = params.id;
// 验证素材是否存在
const material = await prisma.material.findUnique({
where: { id: materialId, deletedAt: null },
select: {
id: true,
userId: true,
materialType: true,
previewUrl: true,
zipFilePath: true,
zipFileName: true,
zipFileSize: true,
displayImages: true,
},
});
if (!material) {
return NextResponse.json(
{ success: false, error: '素材不存在' },
{ status: 404 }
);
}
if (!(await canViewMaterial(session.user as AssetRouteSessionUser, material))) {
return NextResponse.json(
{ success: false, error: '无权限查看此素材' },
{ status: 403 }
);
}
// 获取资产列表
const assets = await getAssetsByMaterialId(materialId);
return NextResponse.json({
success: true,
data: assets,
});
} catch (error) {
console.error('[materials/assets] GET error:', error);
return NextResponse.json(
{ success: false, error: '服务器错误' },
{ status: 500 }
);
}
}
/**
* POST - 添加资产
*/
export async function POST(
request: NextRequest,
{ params }: { params: { id: string } }
) {
try {
const session = await getServerSession(authOptions);
if (!session?.user || !(session.user as any).id) {
return NextResponse.json(
{ success: false, error: '未登录' },
{ status: 401 }
);
}
const userId = (session.user as any).id;
const materialId = params.id;
const body = await request.json();
const { assetType, fileUrl, fileName, fileSize, sortOrder } = body;
// 验证必填字段
if (!assetType || !fileUrl) {
return NextResponse.json(
{ success: false, error: '缺少必填参数' },
{ status: 400 }
);
}
// 验证素材是否存在且属于当前用户
const material = await prisma.material.findUnique({
where: { id: materialId, deletedAt: null },
select: { id: true, userId: true },
});
if (!material) {
return NextResponse.json(
{ success: false, error: '素材不存在' },
{ status: 404 }
);
}
if (!(await canManageMaterial(session.user as AssetRouteSessionUser, material.userId))) {
return NextResponse.json(
{ success: false, error: '无权限操作此素材' },
{ status: 403 }
);
}
// 添加资产
const asset = await addAsset({
materialId,
assetType,
fileUrl,
fileName,
fileSize: fileSize ? BigInt(fileSize) : undefined,
sortOrder,
});
await createMaterialLog({
materialId,
userId,
action: 'UPDATE',
changes: {
assetOperation: {
oldValue: null,
newValue: 'ADD',
},
assetType: {
oldValue: null,
newValue: asset.assetType,
},
assetFileName: {
oldValue: null,
newValue: asset.fileName || null,
},
assetFileUrl: {
oldValue: null,
newValue: asset.fileUrl,
},
},
});
return NextResponse.json({
success: true,
data: asset,
});
} catch (error) {
console.error('[materials/assets] POST error:', error);
return NextResponse.json(
{ success: false, error: '服务器错误' },
{ status: 500 }
);
}
}
/**
* DELETE - 删除资产
*/
export async function DELETE(
request: NextRequest,
{ params }: { params: { id: string } }
) {
try {
const session = await getServerSession(authOptions);
if (!session?.user || !(session.user as any).id) {
return NextResponse.json(
{ success: false, error: '未登录' },
{ status: 401 }
);
}
const userId = (session.user as any).id;
const materialId = params.id;
const { searchParams } = new URL(request.url);
const assetId = searchParams.get('assetId');
if (!assetId) {
return NextResponse.json(
{ success: false, error: '缺少 assetId 参数' },
{ status: 400 }
);
}
// 验证素材是否存在且属于当前用户
const material = await prisma.material.findUnique({
where: { id: materialId, deletedAt: null },
select: {
id: true,
userId: true,
previewUrl: true,
zipFilePath: true,
zipFileName: true,
zipFileSize: true,
displayImages: true,
},
});
if (!material) {
return NextResponse.json(
{ success: false, error: '素材不存在' },
{ status: 404 }
);
}
if (!(await canManageMaterial(session.user as AssetRouteSessionUser, material.userId))) {
return NextResponse.json(
{ success: false, error: '无权限操作此素材' },
{ status: 403 }
);
}
// 验证资产是否属于该素材
const asset = await prisma.materialAsset.findUnique({
where: { id: assetId },
select: {
id: true,
materialId: true,
assetType: true,
fileUrl: true,
fileName: true,
},
});
if (!asset || asset.materialId !== materialId) {
return NextResponse.json(
{ success: false, error: '资产不存在或不属于该素材' },
{ status: 404 }
);
}
const previewAssetTypes = ['preview', 'main-preview', 'sku-preview', 'detail-preview'];
const currentDisplayImages = normalizeDisplayImages((material as any).displayImages);
const displayImages = currentDisplayImages.filter((url) => url !== asset.fileUrl);
const materialUpdateData: Record<string, unknown> = {};
if ((material as any).previewUrl === asset.fileUrl) {
const nextPreviewAsset = await prisma.materialAsset.findFirst({
where: {
materialId,
id: { not: assetId },
assetType: { in: previewAssetTypes },
},
orderBy: [
{ sortOrder: 'asc' },
{ createdAt: 'asc' },
],
select: { fileUrl: true },
});
materialUpdateData.previewUrl = nextPreviewAsset?.fileUrl || '';
}
if ((material as any).zipFilePath === asset.fileUrl) {
materialUpdateData.zipFilePath = '';
materialUpdateData.zipFileName = '';
materialUpdateData.zipFileSize = BigInt(0);
}
if (displayImages.length !== currentDisplayImages.length) {
materialUpdateData.displayImages = displayImages;
}
await prisma.$transaction(async (tx) => {
await tx.materialAsset.delete({
where: { id: assetId },
});
if (Object.keys(materialUpdateData).length > 0) {
await tx.material.update({
where: { id: materialId },
data: materialUpdateData as any,
});
}
});
const cleanupResult = await deleteFileIfUnreferenced(asset.fileUrl, {
excludeAssetIds: [assetId],
});
await createMaterialLog({
materialId,
userId,
action: 'UPDATE',
changes: {
assetOperation: {
oldValue: null,
newValue: 'DELETE',
},
fileCleanup: {
oldValue: null,
newValue: cleanupResult.skipped ? 'skipped' : cleanupResult.success ? 'deleted' : 'failed',
},
assetId: {
oldValue: asset.id,
newValue: asset.id,
},
assetType: {
oldValue: asset.assetType,
newValue: asset.assetType,
},
assetFileName: {
oldValue: asset.fileName || null,
newValue: null,
},
assetFileUrl: {
oldValue: asset.fileUrl,
newValue: null,
},
},
});
return NextResponse.json({
success: true,
message: '删除成功',
details: {
fileDeleted: !cleanupResult.skipped && cleanupResult.success,
fileSkipped: !!cleanupResult.skipped,
fileCleanupError: cleanupResult.success ? null : cleanupResult.error || null,
},
});
} catch (error) {
console.error('[materials/assets] DELETE error:', error);
return NextResponse.json(
{ success: false, error: '服务器错误' },
{ status: 500 }
);
}
}