'use strict'; /* ── Environment configuration & startup validation ─────────────────────── Require this module BEFORE anything else that reads process.env. Fails fast with a clear message if required vars are missing or weak. ──────────────────────────────────────────────────────────────────────── */ require('dotenv').config({ path: require('path').join(__dirname, '../.env') }); const path = require('path'); const errors = []; function _require(key, { minLen, notValue } = {}) { const val = process.env[key]; if (!val) { errors.push(`${key} is required`); return undefined; } if (minLen && val.length < minLen) errors.push(`${key} must be at least ${minLen} chars (got ${val.length})`); if (notValue && val === notValue) errors.push(`${key} must not be the default placeholder value`); return val; } function _optional(key, defaultVal = undefined) { return process.env[key] || defaultVal; } /* ── Required vars ────────────────────────────────────────────────────── */ const JWT_SECRET = _require('JWT_SECRET', { minLen: 32, notValue: 'change_this_to_a_long_random_string', }); /* ── Optional vars with defaults ─────────────────────────────────────── */ const NODE_ENV = _optional('NODE_ENV', 'development'); if (!['development', 'production', 'test'].includes(NODE_ENV)) { errors.push(`NODE_ENV must be development | production | test (got: "${NODE_ENV}")`); } const PORT_RAW = _optional('PORT', '3000'); const PORT = Number(PORT_RAW); if (!Number.isInteger(PORT) || PORT < 1 || PORT > 65535) { errors.push(`PORT must be an integer between 1 and 65535 (got: "${PORT_RAW}")`); } /* ── Fail fast ───────────────────────────────────────────────────────── */ if (errors.length) { process.stderr.write('\n[config] FATAL: invalid environment configuration:\n'); errors.forEach(e => process.stderr.write(` ✗ ${e}\n`)); process.stderr.write('\nFix these issues in your .env file and restart.\n\n'); process.exit(1); } module.exports = Object.freeze({ /* env */ JWT_SECRET, PORT, NODE_ENV, isProd: NODE_ENV === 'production', LOG_LEVEL: _optional('LOG_LEVEL', NODE_ENV === 'production' ? 'info' : 'debug'), CLIENT_ORIGIN: _optional('CLIENT_ORIGIN'), /* paths */ DB_PATH: _optional('DB_PATH', path.join(__dirname, '../data/learnspace.db')), UPLOADS_DIR: _optional('UPLOADS_DIR', path.join(__dirname, '../uploads')), /* WebRTC ICE servers — TURN required for clients behind symmetric NAT/CGNAT (school networks) */ TURN_URL: _optional('TURN_URL'), // e.g. turn:turn.example.com:3478 TURN_USER: _optional('TURN_USER'), TURN_PASS: _optional('TURN_PASS'), /* constants */ BCRYPT_ROUNDS: 12, MAX_FILE_SIZE: 50 * 1024 * 1024, });