WuYongJie d1c6efc79d
Some checks failed
CI Check / check (push) Has been cancelled
feat: NAS 部署与 Harbor 推送流程
2025-12-03 14:39:06 +08:00

129 lines
3.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<string, number> = {}
// 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<Array<{ id: string; "zipFilePath": string; "zipFileName": string }>>(`
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<Array<{ table_name: string; count: bigint }>>(`
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<string, number> = {}
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 }
)
}
}