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, access } from 'fs/promises' export const dynamic = 'force-dynamic' 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 } ) } 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", "zipFileName" FROM materials `) let deletedFiles = 0 let failedFiles = 0 // 删除存储文件(优先S3,其次本地兼容) for (const material of materials) { if (material.zipFilePath && material.zipFilePath !== '') { const filePath = material.zipFilePath if (isS3Url(filePath)) { try { await deleteFromS3(filePath) deletedFiles++ } catch (error) { console.error('[Cleanup] Failed to delete S3 object:', filePath, error) failedFiles++ } continue } try { await access(filePath) await unlink(filePath) deletedFiles++ } catch (fileError: any) { if (fileError.code !== 'ENOENT') { failedFiles++ } } } } // 删除所有素材记录 const deletedMaterials = await prisma.$executeRawUnsafe(` DELETE FROM materials `) results.materials = Number(deletedMaterials) results.materialFilesDeleted = deletedFiles 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 } ) } }