117 lines
3.1 KiB
TypeScript
117 lines
3.1 KiB
TypeScript
#!/usr/bin/env npx tsx
|
|
/**
|
|
* Cleanup script for expired vision recordings
|
|
*
|
|
* Deletes recordings that have passed their expiresAt timestamp.
|
|
* Should be run periodically via cron job (e.g., daily).
|
|
*
|
|
* Usage:
|
|
* npx tsx scripts/cleanupVisionRecordings.ts
|
|
*
|
|
* Options:
|
|
* --dry-run Show what would be deleted without actually deleting
|
|
* --verbose Show detailed output
|
|
*/
|
|
|
|
import { eq, lt } from 'drizzle-orm'
|
|
import { rm } from 'fs/promises'
|
|
import path from 'path'
|
|
import { db } from '../src/db'
|
|
import { visionRecordings } from '../src/db/schema/vision-recordings'
|
|
|
|
async function main() {
|
|
const args = process.argv.slice(2)
|
|
const dryRun = args.includes('--dry-run')
|
|
const verbose = args.includes('--verbose')
|
|
|
|
console.log('='.repeat(60))
|
|
console.log('Vision Recording Cleanup')
|
|
console.log('='.repeat(60))
|
|
console.log(`Mode: ${dryRun ? 'DRY RUN' : 'LIVE'}`)
|
|
console.log(`Time: ${new Date().toISOString()}`)
|
|
console.log('')
|
|
|
|
const now = new Date()
|
|
|
|
// Find expired recordings
|
|
const expiredRecordings = await db.query.visionRecordings.findMany({
|
|
where: lt(visionRecordings.expiresAt, now),
|
|
})
|
|
|
|
console.log(`Found ${expiredRecordings.length} expired recording(s)`)
|
|
|
|
if (expiredRecordings.length === 0) {
|
|
console.log('Nothing to clean up.')
|
|
return
|
|
}
|
|
|
|
let deletedCount = 0
|
|
let errorCount = 0
|
|
let totalSizeBytes = 0
|
|
|
|
for (const recording of expiredRecordings) {
|
|
const recordingDir = path.join(
|
|
process.cwd(),
|
|
'data',
|
|
'uploads',
|
|
'vision-recordings',
|
|
recording.playerId,
|
|
recording.id
|
|
)
|
|
|
|
if (verbose) {
|
|
console.log(`\nProcessing: ${recording.id}`)
|
|
console.log(` Session: ${recording.sessionId}`)
|
|
console.log(` Player: ${recording.playerId}`)
|
|
console.log(` Expired: ${recording.expiresAt.toISOString()}`)
|
|
console.log(
|
|
` Size: ${recording.fileSize ? `${(recording.fileSize / 1024 / 1024).toFixed(2)} MB` : 'unknown'}`
|
|
)
|
|
console.log(` Path: ${recordingDir}`)
|
|
}
|
|
|
|
if (recording.fileSize) {
|
|
totalSizeBytes += recording.fileSize
|
|
}
|
|
|
|
if (dryRun) {
|
|
console.log(`[DRY RUN] Would delete: ${recording.id}`)
|
|
deletedCount++
|
|
continue
|
|
}
|
|
|
|
try {
|
|
// Delete files from disk
|
|
await rm(recordingDir, { recursive: true, force: true })
|
|
|
|
// Delete database record
|
|
await db.delete(visionRecordings).where(eq(visionRecordings.id, recording.id))
|
|
|
|
deletedCount++
|
|
console.log(`Deleted: ${recording.id}`)
|
|
} catch (error) {
|
|
errorCount++
|
|
console.error(`Error deleting ${recording.id}:`, error)
|
|
}
|
|
}
|
|
|
|
console.log('')
|
|
console.log('-'.repeat(60))
|
|
console.log('Summary:')
|
|
console.log(` Total expired: ${expiredRecordings.length}`)
|
|
console.log(` Deleted: ${deletedCount}`)
|
|
console.log(` Errors: ${errorCount}`)
|
|
console.log(` Space recovered: ${(totalSizeBytes / 1024 / 1024).toFixed(2)} MB`)
|
|
console.log('')
|
|
|
|
if (dryRun) {
|
|
console.log('NOTE: This was a dry run. No files were actually deleted.')
|
|
console.log('Run without --dry-run to perform actual cleanup.')
|
|
}
|
|
}
|
|
|
|
main().catch((error) => {
|
|
console.error('Fatal error:', error)
|
|
process.exit(1)
|
|
})
|