All files / web/scripts cleanupVisionRecordings.ts

0% Statements 0/116
0% Branches 0/1
0% Functions 0/1
0% Lines 0/116

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117                                                                                                                                                                                                                                         
#!/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)
})