Shuffle/app/lib/core/config/app_config.dart
cschlaefke e5df79ee0a feat(app): add Flutter scaffold with config, logging, routing and l10n
Refs #31

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-02 20:17:24 +01:00

103 lines
3.7 KiB
Dart

/// Laufzeitkonfiguration der App.
///
/// Die Werte kommen zur Build-Zeit aus `--dart-define-from-file=env/<stage>.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<String, String> 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<String> 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/<stage>.json '
'(see app/env/dev.example.json).',
);
}
}
}