import 'dotenv/config'; export const config = { // Supabase supabaseUrl: process.env.SUPABASE_URL || '', supabaseServiceRoleKey: process.env.SUPABASE_SERVICE_ROLE_KEY || '', // Worker workerId: process.env.WORKER_ID || `worker-${process.pid}`, maxWorkers: parseInt(process.env.MAX_WORKERS || '2', 10), pollIntervalMs: parseInt(process.env.POLL_INTERVAL_MS || '3000', 10), // Turnitin turnitinSharedPassword: process.env.TURNITIN_SHARED_PASSWORD || '', credentialEncryptionKey: process.env.TURNITIN_CREDENTIAL_ENCRYPTION_KEY || '', turnitinTargetUrl: process.env.TARGET_URL || 'https://www.turnitin.com/login_page.asp?lang=en_us', turnitinClassTitle: process.env.CLASS_TITLE || 'Summer Reading 2026', turnitinAssignmentTitle: process.env.ASSIGNMENT_TITLE || 'HTLLP notes', // Playwright headless: process.env.PLAYWRIGHT_HEADLESS !== 'false', // Storage buckets inputBucket: process.env.INPUT_BUCKET || 'turnitin-inputs', reportBucket: process.env.REPORT_BUCKET || 'turnitin-reports', diagnosticsBucket: process.env.DIAGNOSTICS_BUCKET || 'turnitin-diagnostics', sessionBucket: process.env.SESSION_BUCKET || 'turnitin-sessions', // Cron intervals (minutes) quotaCheckInterval: parseInt(process.env.QUOTA_CHECK_INTERVAL || '30', 10), quotaCheckAccountDelayMs: parseInt( process.env.QUOTA_CHECK_ACCOUNT_DELAY_MS || String(5 * 60 * 1000), 10, ), quotaCheckOrder: process.env.QUOTA_CHECK_ORDER === 'newest' ? 'newest' : 'oldest', cleanupInterval: parseInt(process.env.CLEANUP_INTERVAL || '15', 10), staleRecoveryInterval: parseInt(process.env.STALE_RECOVERY_INTERVAL || '5', 10), // Report reportRetentionHours: parseInt(process.env.REPORT_RETENTION_HOURS || '24', 10), // Server port: parseInt(process.env.PORT || '7860', 10), internalSecret: process.env.INTERNAL_SECRET || '', allowedOrigins: (process.env.ALLOWED_ORIGINS || process.env.FRONTEND_ORIGIN || '') .split(',') .map((origin) => origin.trim()) .filter(Boolean), enableWorker: process.env.ENABLE_WORKER !== 'false', enableCron: process.env.ENABLE_CRON !== 'false', // Timeouts similarityTimeoutMs: parseInt(process.env.SIMILARITY_TIMEOUT_MS || '180000', 10), similarityPollMs: parseInt(process.env.SIMILARITY_POLL_MS || '5000', 10), similarityRefreshAfterMs: parseInt(process.env.SIMILARITY_REFRESH_AFTER_MS || '90000', 10), // BUG-10 FIX: leaseMinutes was hardcoded to 20 min — shorter than the combined // time for login + navigate + similarity wait (up to 30+ min). Stale recovery // could reset an account that is still being actively used. leaseMinutes: parseInt(process.env.LEASE_MINUTES || '35', 10), } as const; /** Validate required environment variables at startup */ export function validateConfig(): void { const required: (keyof typeof config)[] = [ 'supabaseUrl', 'supabaseServiceRoleKey', 'turnitinSharedPassword', ]; const missing = required.filter((key) => !config[key]); if (missing.length > 0) { throw new Error(`Missing required environment variables: ${missing.join(', ')}`); } }