127 lines
3.8 KiB
TypeScript
127 lines
3.8 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, isS3Url } from '@/lib/s3'
|
||
import { unlink } from 'fs/promises'
|
||
|
||
// Cleanup materials deleted more than 7 days ago (admin only)
|
||
export async function POST(req: NextRequest) {
|
||
try {
|
||
const session = await getServerSession(authOptions)
|
||
|
||
if (!session || (session.user as any).role !== 'ADMIN') {
|
||
return NextResponse.json(
|
||
{ error: '无权限' },
|
||
{ status: 403 }
|
||
)
|
||
}
|
||
|
||
// 计算7天前的日期
|
||
const sevenDaysAgo = new Date()
|
||
sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7)
|
||
|
||
// 查找7天前删除的素材
|
||
const deletedMaterials = await prisma.material.findMany({
|
||
where: {
|
||
deletedAt: {
|
||
not: null,
|
||
lte: sevenDaysAgo,
|
||
},
|
||
},
|
||
})
|
||
|
||
// 收集所有需要删除的文件并删除
|
||
let deletedFiles = 0
|
||
let failedFiles = 0
|
||
|
||
for (const material of deletedMaterials) {
|
||
const filesToDelete: string[] = []
|
||
|
||
// 源文件(zipFilePath 或 videoSourceUrl)
|
||
if (material.zipFilePath) filesToDelete.push(material.zipFilePath)
|
||
if ((material as any).videoSourceUrl && (material as any).videoSourceUrl !== material.zipFilePath) {
|
||
filesToDelete.push((material as any).videoSourceUrl)
|
||
}
|
||
|
||
// 预览图
|
||
if (material.previewUrl) filesToDelete.push(material.previewUrl)
|
||
|
||
// 多预览图(JSON数组)
|
||
if ((material as any).previewUrls) {
|
||
try {
|
||
const previewUrls = JSON.parse((material as any).previewUrls)
|
||
if (Array.isArray(previewUrls)) {
|
||
filesToDelete.push(...previewUrls.filter((url: string) => url && url.trim()))
|
||
}
|
||
} catch {
|
||
// 忽略解析错误
|
||
}
|
||
}
|
||
|
||
// 视频相关文件
|
||
if ((material as any).videoPreview540Url) filesToDelete.push((material as any).videoPreview540Url)
|
||
if ((material as any).videoPreviewClipUrl) filesToDelete.push((material as any).videoPreviewClipUrl)
|
||
if ((material as any).videoCoverUrl) filesToDelete.push((material as any).videoCoverUrl)
|
||
|
||
// 展示图片(JSON数组)
|
||
if (material.displayImages) {
|
||
try {
|
||
const displayImages = JSON.parse(material.displayImages)
|
||
if (Array.isArray(displayImages)) {
|
||
filesToDelete.push(...displayImages.filter((url: string) => url && url.trim()))
|
||
}
|
||
} catch {
|
||
// 忽略解析错误
|
||
}
|
||
}
|
||
|
||
// 去重并过滤空值
|
||
const uniqueFiles = Array.from(new Set(filesToDelete.filter((url) => url && url.trim())))
|
||
|
||
// 删除所有文件
|
||
for (const fileUrl of uniqueFiles) {
|
||
try {
|
||
if (isS3Url(fileUrl)) {
|
||
await deleteFromS3(fileUrl)
|
||
deletedFiles++
|
||
} else {
|
||
try {
|
||
await unlink(fileUrl)
|
||
deletedFiles++
|
||
} catch (fileError: any) {
|
||
if (fileError.code !== 'ENOENT') {
|
||
failedFiles++
|
||
}
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error(`[Cleanup] Failed to delete file: ${fileUrl}`, error)
|
||
failedFiles++
|
||
}
|
||
}
|
||
}
|
||
|
||
// 永久删除数据库记录
|
||
const deletePromises = deletedMaterials.map((material) =>
|
||
prisma.material.delete({
|
||
where: { id: material.id },
|
||
})
|
||
)
|
||
|
||
await Promise.all(deletePromises)
|
||
|
||
return NextResponse.json({
|
||
message: '清理完成',
|
||
deletedCount: deletedMaterials.length
|
||
})
|
||
} catch (error) {
|
||
console.error('[Materials] CLEANUP error:', error)
|
||
return NextResponse.json(
|
||
{ error: '清理失败' },
|
||
{ status: 500 }
|
||
)
|
||
}
|
||
}
|
||
|