import { NextRequest, NextResponse } from 'next/server' import { getServerSession } from 'next-auth' import { authOptions } from '@/lib/auth' import { prisma } from '@/lib/prisma' import { isCurrentSuperAdmin } from '@/lib/admin-permissions' import { collectMaterialStorageUrls, deleteMaterialStorageFiles, } from '@/lib/material-storage-cleanup' export const dynamic = 'force-dynamic' export async function POST(req: NextRequest) { try { const session = await getServerSession(authOptions) if (!session || !(await isCurrentSuperAdmin(session))) { return NextResponse.json( { error: '仅最高权限账号可执行数据库清理' }, { status: 403 } ) } const results: Record = {} // 1. 清理过期的邀请码(已使用且过期超过30天) const expiredInvitations = await prisma.$executeRawUnsafe(` DELETE FROM invitations WHERE "usedAt" IS NOT NULL AND "expiresAt" IS NOT NULL AND "expiresAt" < NOW() - INTERVAL '30 days' `) results.expiredInvitations = Number(expiredInvitations) // 2. 清理旧的弹幕数据(超过90天) const oldDanmakus = await prisma.$executeRawUnsafe(` DELETE FROM danmakus WHERE "createdAt" < NOW() - INTERVAL '90 days' `) results.oldDanmakus = Number(oldDanmakus) // 3. 清理所有素材数据和记录 // 先获取所有素材记录以便删除文件 const materials = await prisma.$queryRawUnsafe>(` SELECT id, "zipFilePath", "previewUrl", "previewUrls", "displayImages", "videoSourceUrl", "videoPreview540Url", "videoPreviewClipUrl", "videoCoverUrl" FROM materials `) let deletedFiles = 0 let skippedFiles = 0 let failedFiles = 0 const failedMaterials: Array<{ id: string failedFiles: number failures: Array<{ fileUrl: string; reason: string }> }> = [] // 删除存储文件。文件删除失败时保留数据库记录,避免清库后无法追踪残留对象。 for (const material of materials) { const cleanupResult = await deleteMaterialStorageFiles(collectMaterialStorageUrls(material), { logPrefix: `[Cleanup] Material ${material.id}`, }) deletedFiles += cleanupResult.deletedFiles skippedFiles += cleanupResult.skippedFiles failedFiles += cleanupResult.failedFiles if (cleanupResult.failedFiles > 0) { failedMaterials.push({ id: material.id, failedFiles: cleanupResult.failedFiles, failures: cleanupResult.failures.slice(0, 10), }) } } if (failedMaterials.length > 0) { return NextResponse.json( { error: '素材文件删除失败,数据库记录已保留,请处理存储连接后重试', deleted: { ...results, materialFilesDeleted: deletedFiles, materialFilesSkipped: skippedFiles, materialFilesFailed: failedFiles, }, failedMaterials, }, { status: 500 } ) } // 删除所有素材记录 const deletedMaterials = await prisma.$executeRawUnsafe(` DELETE FROM materials `) results.materials = Number(deletedMaterials) results.materialFilesDeleted = deletedFiles results.materialFilesSkipped = skippedFiles results.materialFilesFailed = failedFiles // 4. 获取清理后的统计信息 const stats = await prisma.$queryRawUnsafe>(` SELECT 'invitations' as table_name, COUNT(*)::bigint as count FROM invitations UNION ALL SELECT 'danmakus' as table_name, COUNT(*)::bigint as count FROM danmakus UNION ALL SELECT 'materials' as table_name, COUNT(*)::bigint as count FROM materials UNION ALL SELECT 'users' as table_name, COUNT(*)::bigint as count FROM users `) const statsObj: Record = {} stats.forEach((stat) => { statsObj[stat.table_name] = Number(stat.count) }) return NextResponse.json({ message: '数据库清理成功', success: true, deleted: results, remaining: statsObj, }) } catch (error) { console.error('[Cleanup] Error:', error) return NextResponse.json( { error: '数据库清理失败', details: error instanceof Error ? error.message : '未知错误', }, { status: 500 } ) } }