relv-space3 / src /config.ts
relv-dev's picture
Preserve Supabase bucket routing for legacy storage paths
23dd5ee verified
Raw
History Blame Contribute Delete
4.09 kB
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
storageProvider: process.env.STORAGE_PROVIDER === 'r2' ? 'r2' : 'supabase',
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',
// Plain storage paths were created before R2 and must always resolve
// against the original Supabase bucket names after the R2 switch.
supabaseInputBucket:
process.env.SUPABASE_INPUT_BUCKET || 'turnitin-inputs',
supabaseReportBucket:
process.env.SUPABASE_REPORT_BUCKET || 'turnitin-reports',
supabaseSessionBucket:
process.env.SUPABASE_SESSION_BUCKET || 'turnitin-sessions',
r2AccountId: process.env.R2_ACCOUNT_ID || '',
r2AccessKeyId: process.env.R2_ACCESS_KEY_ID || '',
r2SecretAccessKey: process.env.R2_SECRET_ACCESS_KEY || '',
r2Endpoint: process.env.R2_ENDPOINT || '',
r2Region: process.env.R2_REGION || 'auto',
// 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 (config.storageProvider === 'r2') {
const r2Required: (keyof typeof config)[] = [
'r2AccountId',
'r2AccessKeyId',
'r2SecretAccessKey',
'r2Endpoint',
];
missing.push(...r2Required.filter((key) => !config[key]));
}
if (missing.length > 0) {
throw new Error(`Missing required environment variables: ${missing.join(', ')}`);
}
}