import { PrismaClient } from "../../apps/web/prisma/generated/client"; const prisma = new PrismaClient(); const TARGET_PREFIX = process.env.GITHUB_ISSUE_PREFIX || "GitHub Issue:"; const BATCH_SIZE = Math.max(parseInt(process.env.PURGE_BATCH_SIZE || "100", 10), 1); const args = new Set(process.argv.slice(2)); const isDryRun = args.has("--dry-run"); const forceRun = args.has("--force"); function chunk(items: T[], size: number): T[][] { const output: T[][] = []; for (let index = 0; index < items.length; index += size) { output.push(items.slice(index, index + size)); } return output; } function normalizeJudgeCount(value: unknown): number { return typeof value === "number" ? value : 0; } async function main() { if (!isDryRun && !forceRun) { console.error( "Refuse to run without explicit mode. Use --dry-run 先看影響筆數,或加上 --force 正式刪除。" ); process.exit(1); } const targetTasks = await prisma.task.findMany({ where: { title: { startsWith: TARGET_PREFIX, }, }, select: { id: true }, }); const targetTaskIds = targetTasks.map((task) => task.id); if (targetTaskIds.length === 0) { console.log(`No task with prefix "${TARGET_PREFIX}" found. done.`); return; } const submissionIdsForAll = ( await prisma.submission.findMany({ where: { task_id: { in: targetTaskIds } }, select: { id: true }, }) ).map((row) => row.id); const [taskCount, submissionCount, claimCount, judgeCount, ledgerCount, auditCount, scoutDraftEvents] = await Promise.all([ prisma.task.count({ where: { id: { in: targetTaskIds } } }), prisma.submission.count({ where: { task_id: { in: targetTaskIds } } }), prisma.claim.count({ where: { task_id: { in: targetTaskIds } } }), submissionIdsForAll.length ? prisma.judgeResult.count({ where: { submission_id: { in: submissionIdsForAll } } }) : Promise.resolve(0), prisma.ledgerEntry.count({ where: { task_id: { in: targetTaskIds } } }), prisma.auditEvent.count({ where: { entityType: "TASK", entityId: { in: targetTaskIds } } }), prisma.auditEvent.count({ where: { action: { startsWith: "SCOUT_DRAFT_" } } }), ]); console.log("=== Purge Impact Preview ==="); console.log(`Task count: ${normalizeJudgeCount(taskCount)}`); console.log(`Submission count: ${normalizeJudgeCount(submissionCount)}`); console.log(`Claim count: ${normalizeJudgeCount(claimCount)}`); console.log(`JudgeResult count: ${normalizeJudgeCount(judgeCount)}`); console.log(`LedgerEntry count: ${normalizeJudgeCount(ledgerCount)}`); console.log(`AuditEvent (entityType TASK) count: ${normalizeJudgeCount(auditCount)}`); console.log(`AuditEvent (SCOUT_DRAFT_*) count: ${normalizeJudgeCount(scoutDraftEvents)}`); console.log(`Target batch size: ${BATCH_SIZE}`); if (isDryRun) { console.log("Dry-run complete. No data was modified."); return; } for (const idBatch of chunk(targetTaskIds, BATCH_SIZE)) { await prisma.$transaction(async (tx) => { const batchSubmissionIds = ( await tx.submission.findMany({ where: { task_id: { in: idBatch } }, select: { id: true }, }) ).map((row) => row.id); if (batchSubmissionIds.length > 0) { await tx.judgeResult.deleteMany({ where: { submission_id: { in: batchSubmissionIds } }, }); } await tx.submission.deleteMany({ where: { task_id: { in: idBatch } }, }); await tx.claim.deleteMany({ where: { task_id: { in: idBatch } }, }); await tx.ledgerEntry.deleteMany({ where: { task_id: { in: idBatch } }, }); await tx.auditEvent.deleteMany({ where: { OR: [ { entityType: "TASK", entityId: { in: idBatch }, }, { action: { startsWith: "SCOUT_DRAFT_" }, entityId: { in: idBatch }, }, ], }, }); await tx.task.deleteMany({ where: { id: { in: idBatch } }, }); }); } console.log(`Deleted ${taskCount} GitHub Issue task entries.`); } main() .catch((error) => { console.error("Purge script failed:", error); process.exit(1); }) .finally(async () => { await prisma.$disconnect(); });