CognxSafeTrack
Claude Sonnet 4.6
refactor(debt): resolve all 10 technical debt items from audit
a966957 Download apps/api/src/services/cleanup.ts from safetrack/edtech: direct link, hf CLI and curl.
- Browser
- Download file 3.93 kB
-
https://huggingface.co/spaces/safetrack/edtech/resolve/main/apps/api/src/services/cleanup.ts
- Command line
-
hf download hf://spaces/safetrack/edtech/apps/api/src/services/cleanup.ts
-
curl -L -o cleanup.ts https://huggingface.co/spaces/safetrack/edtech/resolve/main/apps/api/src/services/cleanup.ts
3.93 kB
| import { logger } from '../logger'; | |
| import fs from 'fs/promises'; | |
| import path from 'path'; | |
| import cron from 'node-cron'; | |
| import { redis } from '../lib/redis'; | |
| const TEMP_DIR = '/tmp'; | |
| const GLOBAL_MAX_AGE = 30 * 60 * 1000; // 30 minutes | |
| const PER_TENANT_QUOTA_MB = 100; // 100MB per tenant limit | |
| const CLEANUP_LOCK_KEY = 'lock:cleanup:temp_files'; | |
| const LOCK_TTL = 1800; // 30 minutes in seconds | |
| /** | |
| * Cleanup Service | |
| * | |
| * Manages /tmp storage by enforcing age limits and per-tenant quotas. | |
| * Uses a Redis lock to ensure only one instance runs maintenance at a time. | |
| */ | |
| export async function cleanTempFiles() { | |
| // 0. Acquire distributed lock | |
| const lock = await redis.set(CLEANUP_LOCK_KEY, 'locked', 'EX', LOCK_TTL, 'NX'); | |
| if (!lock) { | |
| logger.info('[CLEANUP] Maintenance already in progress on another instance. Skipping.'); | |
| return; | |
| } | |
| const now = Date.now(); | |
| try { | |
| const folders = ['', 'audio', 'images', 'documents']; | |
| const tenantUsage: Record<string, number> = {}; | |
| const tenantFiles: Record<string, { path: string, size: number, mtime: number }[]> = {}; | |
| for (const folder of folders) { | |
| const dirPath = path.join(TEMP_DIR, folder); | |
| try { | |
| await fs.access(dirPath); | |
| } catch { | |
| continue; | |
| } | |
| const files = await fs.readdir(dirPath); | |
| for (const file of files) { | |
| const filePath = path.join(dirPath, file); | |
| const stats = await fs.stat(filePath); | |
| if (stats.isDirectory()) continue; | |
| // 1. Check Age (Global cleanup) | |
| const age = now - stats.mtimeMs; | |
| if (age > GLOBAL_MAX_AGE) { | |
| await fs.unlink(filePath); | |
| logger.info(`[CLEANUP] Deleted old file (Age): ${filePath}`); | |
| continue; | |
| } | |
| // 2. Track usage by tenant (Assumes files are named orgId_filename) | |
| const orgId = file.split('_')[0]; | |
| if (orgId && orgId.length > 10) { // Basic orgId check | |
| tenantUsage[orgId] = (tenantUsage[orgId] || 0) + stats.size; | |
| if (!tenantFiles[orgId]) tenantFiles[orgId] = []; | |
| tenantFiles[orgId].push({ path: filePath, size: stats.size, mtime: stats.mtimeMs }); | |
| } | |
| } | |
| } | |
| // 3. Enforce Quotas | |
| for (const orgId in tenantUsage) { | |
| const usageMB = tenantUsage[orgId] / (1024 * 1024); | |
| if (usageMB > PER_TENANT_QUOTA_MB) { | |
| logger.warn(`[CLEANUP] Quota exceeded for Org ${orgId}: ${usageMB.toFixed(2)}MB. Cleaning oldest files...`); | |
| // Sort by mtime (oldest first) | |
| const files = tenantFiles[orgId].sort((a, b) => a.mtime - b.mtime); | |
| let freed = 0; | |
| const targetToFree = (usageMB - (PER_TENANT_QUOTA_MB * 0.8)) * 1024 * 1024; // Free up to 80% of quota | |
| for (const file of files) { | |
| await fs.unlink(file.path); | |
| freed += file.size; | |
| if (freed >= targetToFree) break; | |
| } | |
| logger.info(`[CLEANUP] Freed ${(freed / (1024 * 1024)).toFixed(2)}MB for Org ${orgId}`); | |
| } | |
| } | |
| } catch (err: any) { | |
| logger.error({ err }, '[CLEANUP] Error during maintenance'); | |
| } finally { | |
| // 4. Release lock | |
| await redis.del(CLEANUP_LOCK_KEY); | |
| } | |
| } | |
| /** | |
| * Starts a cron job that runs every 30 minutes. | |
| */ | |
| export function startCleanupCron() { | |
| cron.schedule('*/30 * * * *', async () => { | |
| logger.info('[CLEANUP] 🧹 Starting scheduled maintenance...'); | |
| await cleanTempFiles(); | |
| }); | |
| // Also run once at startup | |
| cleanTempFiles(); | |
| } | |