/// Laufzeitkonfiguration der App. /// /// Die Werte kommen zur Build-Zeit aus `--dart-define-from-file=env/.json` /// (siehe `app/env/*.example.json`). Secrets gehören nicht ins Repo. class AppConfig { const AppConfig({ required this.environment, required this.supabaseUrl, required this.supabaseAnonKey, required this.siteUrl, required this.legalPrivacyUrl, required this.legalTermsUrl, required this.termsVersion, }); /// Liest die Konfiguration aus den `--dart-define`-Werten. /// /// `String.fromEnvironment` muss mit konstanten Namen aufgerufen werden; /// deshalb steht jeder Schlüssel hier einzeln. factory AppConfig.fromEnvironment() => const AppConfig( environment: String.fromEnvironment( keyAppEnv, defaultValue: defaultEnvironment, ), supabaseUrl: String.fromEnvironment(keySupabaseUrl), supabaseAnonKey: String.fromEnvironment(keySupabaseAnonKey), siteUrl: String.fromEnvironment(keySiteUrl, defaultValue: defaultSiteUrl), legalPrivacyUrl: String.fromEnvironment( keyLegalPrivacyUrl, defaultValue: defaultLegalPrivacyUrl, ), legalTermsUrl: String.fromEnvironment( keyLegalTermsUrl, defaultValue: defaultLegalTermsUrl, ), termsVersion: String.fromEnvironment( keyTermsVersion, defaultValue: defaultTermsVersion, ), ); /// Baut die Konfiguration aus einer Map (Tests, Tooling). Fehlende /// optionale Werte erhalten dieselben Platzhalter wie [fromEnvironment]. factory AppConfig.fromMap(Map values) => AppConfig( environment: values[keyAppEnv] ?? defaultEnvironment, supabaseUrl: values[keySupabaseUrl] ?? '', supabaseAnonKey: values[keySupabaseAnonKey] ?? '', siteUrl: values[keySiteUrl] ?? defaultSiteUrl, legalPrivacyUrl: values[keyLegalPrivacyUrl] ?? defaultLegalPrivacyUrl, legalTermsUrl: values[keyLegalTermsUrl] ?? defaultLegalTermsUrl, termsVersion: values[keyTermsVersion] ?? defaultTermsVersion, ); static const keyAppEnv = 'APP_ENV'; static const keySupabaseUrl = 'SUPABASE_URL'; static const keySupabaseAnonKey = 'SUPABASE_ANON_KEY'; static const keySiteUrl = 'SITE_URL'; static const keyLegalPrivacyUrl = 'LEGAL_PRIVACY_URL'; static const keyLegalTermsUrl = 'LEGAL_TERMS_URL'; static const keyTermsVersion = 'TERMS_VERSION'; // Platzhalter (Spec #1 § 5.1), bis Chris die echten Werte festlegt. static const defaultEnvironment = 'dev'; static const defaultSiteUrl = 'https://shuffle.cshomelabs.work'; static const defaultLegalPrivacyUrl = 'https://shuffle.cshomelabs.work/legal/privacy'; static const defaultLegalTermsUrl = 'https://shuffle.cshomelabs.work/legal/terms'; static const defaultTermsVersion = '2026-11-draft'; /// Name der Stage (`dev`, `ci`, `staging`, `prod`). final String environment; final String supabaseUrl; final String supabaseAnonKey; /// Öffentliche Basis-URL der Web-App (Ziel der Mail-Links). final String siteUrl; final String legalPrivacyUrl; final String legalTermsUrl; /// Version der Rechtstexte, die bei der Registrierung gespeichert wird. final String termsVersion; /// Namen der Pflichtwerte, die fehlen oder leer sind. List get missingKeys => [ if (supabaseUrl.isEmpty) keySupabaseUrl, if (supabaseAnonKey.isEmpty) keySupabaseAnonKey, ]; bool get isValid => missingKeys.isEmpty; /// Wirft einen [StateError], wenn Pflichtwerte fehlen. void validate() { final missing = missingKeys; if (missing.isNotEmpty) { throw StateError( 'Missing app configuration: ${missing.join(', ')}. ' 'Start the app with --dart-define-from-file=env/.json ' '(see app/env/dev.example.json).', ); } } }