const fs = require('fs'); const path = require('path'); const LOGS_DIR = path.join(__dirname, 'logs'); const LOG_FILE = path.join(LOGS_DIR, 'cleanup.log'); const CONFIG = { PDF_DIR: path.join(__dirname, 'data/pdfs'), ARCHIVE_DIR: path.join(__dirname, 'data/archive'), ERROR_DIR: path.join(__dirname, 'data/error'), PDF_RETENTION_DAYS: 7, ARCHIVE_RETENTION_DAYS: 45, LOG_COMPRESS_DAYS: 7, LOG_DELETE_DAYS: 30, DISK_USAGE_THRESHOLD: 80 }; function logInfo(message, data = null) { const timestamp = new Date().toISOString(); const logEntry = `[${timestamp}] [INFO] ${message}${data ? ' ' + JSON.stringify(data) : ''}\n`; fs.appendFileSync(LOG_FILE, logEntry); console.log(`[INFO] ${message}`, data || ''); } function logWarn(message, data = null) { const timestamp = new Date().toISOString(); const logEntry = `[${timestamp}] [WARN] ${message}${data ? ' ' + JSON.stringify(data) : ''}\n`; fs.appendFileSync(LOG_FILE, logEntry); console.warn(`[WARN] ${message}`, data || ''); } function formatBytes(bytes) { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i]; } async function runCleanup(dryRun = false) { logInfo('Cleanup started', { dryRun }); const startTime = Date.now(); try { await archiveOldPDFs(dryRun); await deleteOldArchives(dryRun); await rotateLogs(dryRun); const diskInfo = await checkDiskSpace(); const elapsed = ((Date.now() - startTime) / 1000).toFixed(2); logInfo('Cleanup completed', { elapsedSeconds: elapsed, diskInfo }); console.log('\nCleanup completed in', elapsed + 's'); console.log('Disk space:', diskInfo.total, 'total,', diskInfo.used, 'used,', diskInfo.free, 'free'); console.log('Disk usage:', diskInfo.percentage + '%'); if (diskInfo.percentage > CONFIG.DISK_USAGE_THRESHOLD) { logWarn('Disk usage above threshold', { current: diskInfo.percentage, threshold: CONFIG.DISK_USAGE_THRESHOLD }); console.warn('WARNING: Disk usage above', CONFIG.DISK_USAGE_THRESHOLD + '%'); } } catch (error) { logError('Cleanup failed', error); console.error('Cleanup failed:', error.message); process.exit(1); } } async function archiveOldPDFs(dryRun) { logInfo('Archiving old PDFs...'); const files = fs.readdirSync(CONFIG.PDF_DIR); const now = Date.now(); const oneDayMs = 24 * 60 * 60 * 1000; const sevenDaysMs = 7 * oneDayMs; let archivedCount = 0; files.forEach(file => { if (!file.endsWith('.pdf')) return; const filePath = path.join(CONFIG.PDF_DIR, file); const stats = fs.statSync(filePath); const age = now - stats.mtimeMs; if (age > sevenDaysMs) { const month = new Date(stats.mtimeMs).toISOString().slice(0, 7); const archivePath = path.join(CONFIG.ARCHIVE_DIR, month); if (!fs.existsSync(archivePath)) { fs.mkdirSync(archivePath, { recursive: true }); } if (!dryRun) { fs.renameSync(filePath, path.join(archivePath, file)); archivedCount++; logInfo('Archived PDF', { file, month }); } else { logInfo('[DRY-RUN] Would archive', { file, month }); } } }); logInfo('Archived PDFs', { count: archivedCount, dryRun }); } async function deleteOldArchives(dryRun) { logInfo('Deleting old archives...'); const now = Date.now(); const fortyFiveDaysMs = 45 * 24 * 60 * 60 * 1000; const months = fs.readdirSync(CONFIG.ARCHIVE_DIR); let deletedCount = 0; months.forEach(month => { const monthPath = path.join(CONFIG.ARCHIVE_DIR, month); const stats = fs.statSync(monthPath); const age = now - stats.mtimeMs; if (age > fortyFiveDaysMs) { if (!dryRun) { fs.rmSync(monthPath, { recursive: true, force: true }); deletedCount++; logInfo('Deleted old archive', { month }); } else { logInfo('[DRY-RUN] Would delete archive', { month }); } } }); logInfo('Deleted old archives', { count: deletedCount, dryRun }); } async function rotateLogs(dryRun) { logInfo('Rotating logs...'); const files = fs.readdirSync(LOGS_DIR); const now = Date.now(); const sevenDaysMs = 7 * 24 * 60 * 60 * 1000; const thirtyDaysMs = 30 * 24 * 60 * 60 * 1000; let compressedCount = 0; let deletedCount = 0; files.forEach(file => { const filePath = path.join(LOGS_DIR, file); const stats = fs.statSync(filePath); const age = now - stats.mtimeMs; if (age > sevenDaysMs && !file.endsWith('.gz')) { if (!dryRun) { try { fs.copyFileSync(filePath, filePath + '.gz'); fs.unlinkSync(filePath); compressedCount++; logInfo('Compressed log', { file }); } catch (error) { logWarn('Failed to compress log', { file, error: error.message }); } } else { logInfo('[DRY-RUN] Would compress', { file }); } } if (age > thirtyDaysMs) { if (!dryRun) { fs.unlinkSync(filePath); deletedCount++; logInfo('Deleted old log', { file }); } else { logInfo('[DRY-RUN] Would delete', { file }); } } }); logInfo('Rotated logs', { compressed: compressedCount, deleted: deletedCount, dryRun }); } async function checkDiskSpace() { try { const stats = fs.statfsSync(CONFIG.PDF_DIR); const total = stats.bavail * stats.frsize; const free = stats.bfree * stats.frsize; const used = total - free; const usedPercent = (used / total) * 100; return { total: formatBytes(total), used: formatBytes(used), free: formatBytes(free), percentage: usedPercent.toFixed(1) }; } catch (error) { logError('Failed to check disk space', error); return { total: 'Unknown', used: 'Unknown', free: 'Unknown', percentage: 0 }; } } const dryRun = process.argv.includes('--dry-run'); runCleanup(dryRun);