import 'dart:io'; import 'alpaca/alpaca_env.dart'; import 'market_history_env.dart'; import 'package:dotenv/dotenv.dart'; class ServerEnv { ServerEnv._({ required this.databaseUrl, required this.port, required this.firebaseWebApiKey, required this.questionWorkerEnabled, required this.questionWorkerIntervalSeconds, required this.questionPipelineTestMode, required this.tradingEnabled, required this.tradingWorkerIngestEnabled, required this.tradingWorkerEvalEnabled, required this.tradingDevEndpointsEnabled, required this.adminPortalEnabled, required this.adminFirebaseUids, required this.marketHistory, required this.alpaca, }); final String databaseUrl; final int port; final String firebaseWebApiKey; final bool questionWorkerEnabled; final int questionWorkerIntervalSeconds; final bool questionPipelineTestMode; final bool tradingEnabled; final bool tradingWorkerIngestEnabled; final bool tradingWorkerEvalEnabled; /// Mounts dev-only endpoints under `/v1/me/trading/dev/*` (e.g. `force-fire`). /// Default false — never enable in production. final bool tradingDevEndpointsEnabled; final bool adminPortalEnabled; final Set adminFirebaseUids; /// Rolling market-history sync, retention, and guess-rule settings. final MarketHistoryEnv marketHistory; /// Shorthand for [MarketHistoryEnv.syncEnabled]. bool get marketHistorySyncEnabled => marketHistory.syncEnabled; final AlpacaEnv alpaca; /// Validates cross-flag constraints after [load]. void assertConsistent() { marketHistory.assertConsistent(tradingEnabled: tradingEnabled); } static ServerEnv load() { final DotEnv env = DotEnv(includePlatformEnvironment: true) ..load(['.env']); // Build a sanitized snapshot of the Alpaca-relevant keys for AlpacaEnv. const List alpacaKeys = [ 'ALPACA_API_KEY_ID', 'ALPACA_API_SECRET_KEY', 'ALPACA_TRADING_BASE_URL', 'ALPACA_DATA_BASE_URL', 'ALPACA_DATA_FEED', 'ALPACA_ALLOW_LIVE', ]; final Map envMap = { for (final String key in alpacaKeys) if (env[key] != null && env[key]!.isNotEmpty) key: env[key]!, }; final String? databaseUrl = env['DATABASE_URL']; if (databaseUrl == null || databaseUrl.isEmpty) { stderr.writeln('DATABASE_URL is required in server/.env'); exit(1); } final String? apiKey = env['FIREBASE_WEB_API_KEY']; if (apiKey == null || apiKey.isEmpty) { stderr.writeln('FIREBASE_WEB_API_KEY is required in server/.env'); exit(1); } final int port = int.tryParse(env['PORT'] ?? '3000') ?? 3000; final bool workerEnabled = (env['QUESTION_WORKER_ENABLED'] ?? 'true').toLowerCase() != 'false'; final int workerIntervalSeconds = int.tryParse(env['QUESTION_WORKER_INTERVAL_SECONDS'] ?? '60') ?? 60; final bool pipelineTestMode = (env['QUESTION_PIPELINE_TEST_MODE'] ?? 'false').toLowerCase() == 'true'; final bool tradingEnabled = (env['TRADING_ENABLED'] ?? 'false').toLowerCase() == 'true'; final bool tradingWorkerIngestEnabled = (env['TRADING_WORKER_INGEST_ENABLED'] ?? 'true').toLowerCase() != 'false'; final bool tradingWorkerEvalEnabled = (env['TRADING_WORKER_EVAL_ENABLED'] ?? 'true').toLowerCase() != 'false'; final bool tradingDevEndpointsEnabled = (env['TRADING_DEV_ENDPOINTS_ENABLED'] ?? 'false').toLowerCase() == 'true'; final bool adminPortalEnabled = (env['ADMIN_PORTAL_ENABLED'] ?? 'false').toLowerCase() == 'true'; final Set adminFirebaseUids = (env['ADMIN_FIREBASE_UIDS'] ?? '') .split(',') .map((String value) => value.trim()) .where((String value) => value.isNotEmpty) .toSet(); final Map stringEnv = Map.from(Platform.environment); const List marketHistoryKeys = [ 'MARKET_HISTORY_SYNC_ENABLED', 'MARKET_HISTORY_WINDOW_DAYS', 'MARKET_HISTORY_RETENTION_DAYS', 'MARKET_HISTORY_ARCHIVE_ENABLED', 'MARKET_UNIVERSE_REFRESH_HOURS', 'MARKET_HISTORY_SYNC_HOURS', 'MARKET_HISTORY_CLEANUP_HOURS', 'MARKET_HISTORY_SYNC_HOUR_UTC', 'HISTORY_SYNC_BATCH_SIZE', 'HISTORY_SYNC_MAX_SYMBOLS', 'MIN_BARS_FOR_GUESS', 'GUESS_COOLDOWN_HOURS', ]; for (final String key in marketHistoryKeys) { final String? value = env[key]; if (value != null && value.isNotEmpty) { stringEnv[key] = value; } } final MarketHistoryEnv marketHistory = MarketHistoryEnv.fromMap(stringEnv); final AlpacaEnv alpaca = AlpacaEnv.fromMap(envMap)..assertPaperOnly(); final ServerEnv loaded = ServerEnv._( databaseUrl: databaseUrl, port: port, firebaseWebApiKey: apiKey, questionWorkerEnabled: workerEnabled, questionWorkerIntervalSeconds: workerIntervalSeconds, questionPipelineTestMode: pipelineTestMode, tradingEnabled: tradingEnabled, tradingWorkerIngestEnabled: tradingWorkerIngestEnabled, tradingWorkerEvalEnabled: tradingWorkerEvalEnabled, tradingDevEndpointsEnabled: tradingDevEndpointsEnabled, adminPortalEnabled: adminPortalEnabled, adminFirebaseUids: adminFirebaseUids, marketHistory: marketHistory, alpaca: alpaca, ); loaded.assertConsistent(); return loaded; } }