File size: 3,108 Bytes
d5c9d40
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
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(', ')}`);
  }
}