/** * Zentrale, typisierte Betreiber- und Rechtskonfiguration für Lageplan. * * WARUM: Rechtliche Angaben (Betreiber, Kontakt, Serverstandort, Dokumentversionen) * dürfen NICHT an mehreren Stellen widersprüchlich hartcodiert werden. Alle Legal-Seiten, * das Impressum, die Datenschutzerklärung, der Consent-Flow und die Export-Footer lesen * ihre Angaben aus dieser einen Quelle. * * PLATZHALTER: Felder, die der Betreiber rechtlich verbindlich festlegen muss, tragen als * Default einen deutlich sichtbaren Platzhalter im Format `[NAME]`. Diese Platzhalter dürfen * NICHT stillschweigend durch erfundene Werte ersetzt werden. Setze die echten Werte über * Umgebungsvariablen (siehe unten) oder trage sie hier direkt ein, sobald sie feststehen. * * VALIDIERUNG: `assertLegalConfig()` warnt in Development und beim Build, wenn Pflichtangaben * noch Platzhalter sind. In Produktion werden Platzhalter in der UI als solche kenntlich * gemacht (siehe `isPlaceholder()`), damit nie ein erfundener Wert als echt erscheint. */ /** Präfix, an dem ein noch nicht ausgefüllter Pflichtwert erkannt wird. */ const PLACEHOLDER_PREFIX = '[' /** True, wenn der Wert noch ein unausgefüllter Platzhalter ist. */ export function isPlaceholder(value: string | null | undefined): boolean { return !value || (value.trim().startsWith(PLACEHOLDER_PREFIX) && value.trim().endsWith(']')) } /** Liest eine Umgebungsvariable, fällt sonst auf den Platzhalter/Default zurück. */ function env(name: string, fallback: string): string { const v = process.env[name] return v && v.trim().length > 0 ? v.trim() : fallback } export interface ExternalProcessor { /** Anzeigename des Dienstleisters */ name: string /** Zweck der Bearbeitung */ purpose: string /** Verarbeitete Datenarten (grob) */ data: string /** Sitz / relevante Verarbeitungsregion, soweit bekannt; sonst Platzhalter */ region: string /** true, wenn der Dienst nur bei Bedarf/optionaler Konfiguration aktiv ist */ optional?: boolean } export interface LegalDocumentMeta { /** Interne, bewusst gesetzte Versionsangabe (nicht automatisch aus Dateiänderung ableiten!) */ version: string /** Veröffentlichungsdatum ISO (YYYY-MM-DD) */ publishedAt: string } export const legal = { // ─── Betreiber ───────────────────────────────────────────── /** Anzeigename des Projekts */ projectName: 'Lageplan', /** * Rechtlich verbindlicher Name des Betreibers. Im Repository wird das Projekt öffentlich * "Pepe Ziberi / Purepixel" zugeschrieben; die rechtsverbindliche Bezeichnung inkl. * Rechtsform muss der Betreiber bestätigen. */ operatorName: env('LEGAL_OPERATOR_NAME', 'Perparim Ziberi'), /** Rechtsform */ operatorLegalForm: env('LEGAL_OPERATOR_FORM', 'Privatperson'), /** Vollständige Postadresse des Betreibers */ postalAddress: env('LEGAL_POSTAL_ADDRESS', 'Anglikerstrasse 56, 5612 Villmergen'), // ─── Kontakt ─────────────────────────────────────────────── /** Allgemeine Support-/Kontaktadresse (im Repo öffentlich verwendet) */ supportEmail: env('LEGAL_SUPPORT_EMAIL', 'app@lageplan.ch'), /** * Datenschutz-Kontakt. Default = im Repository bereits verwendete Adresse * (nicht erfunden). Der Betreiber muss bestätigen, dass dieses Postfach aktiv überwacht wird. */ privacyContactEmail: env('LEGAL_PRIVACY_EMAIL', 'datenschutz@lageplan.ch'), // ─── Domains ─────────────────────────────────────────────── siteDomain: env('LEGAL_SITE_DOMAIN', 'lageplan.ch'), appDomain: env('LEGAL_APP_DOMAIN', 'app.lageplan.ch'), // ─── Recht & Gerichtsstand ──────────────────────────────── country: 'Schweiz', governingLaw: 'Schweizer Recht', /** Gerichtsstand: gesetzlicher Standard am Wohnsitz des Betreibers; zwingende Gerichtsstände vorbehalten. */ jurisdiction: env('LEGAL_JURISDICTION', 'Wohnsitz des Betreibers in der Schweiz'), // ─── Infrastruktur ──────────────────────────────────────── /** Hostinganbieter: Eigenbetrieb (selbst gehosteter Server beim Betreiber). */ hostingProvider: env('LEGAL_HOSTING_PROVIDER', 'Eigenbetrieb (selbst gehosteter Server beim Betreiber)'), /** Serverstandort der primären Daten (DB/Objektspeicher). */ serverLocation: env('LEGAL_SERVER_LOCATION', 'Schweiz (beim Betreiber, Villmergen AG)'), /** Aufbewahrungsfrist von Sicherungskopien. */ backupRetention: env('LEGAL_BACKUP_RETENTION', 'bis zu 30 Tage'), // ─── Externe Dienstleister (Ist-Zustand aus dem Code abgeleitet) ── // Nichts erfinden: nur tatsächlich im Code kontaktierte Dienste. Sitz/Region, die nicht // sicher bekannt ist, bleibt Platzhalter. processors: [ { name: 'OpenStreetMap (Tiles)', purpose: 'Basiskarte', data: 'Kachel-Koordinaten (Kartenausschnitt)', region: '[REGION – OSM Foundation, EU/UK]' }, { name: 'Esri ArcGIS World Imagery', purpose: 'Satellitenkarte', data: 'Kachel-Koordinaten', region: '[REGION]' }, { name: 'swisstopo (geo.admin.ch)', purpose: 'Schweizer Landeskarte', data: 'Kachel-Koordinaten', region: 'Schweiz' }, { name: 'Nominatim (OpenStreetMap)', purpose: 'Adress-/Reverse-Geocoding', data: 'Adress-/Ortssuche, GPS-Koordinaten', region: '[REGION – OSM Foundation]' }, { name: 'Open-Meteo', purpose: 'Wind- und Höhendaten', data: 'Koordinaten', region: '[REGION – EU]' }, { name: 'Open-Elevation', purpose: 'Höhendaten (Fallback)', data: 'Koordinaten', region: '[REGION]' }, { name: 'Stripe', purpose: 'Zahlungsabwicklung freiwilliger Unterstützungsbeiträge', data: 'Betrag, freiwilliger Name/Nachricht, Zahlungsdaten (direkt bei Stripe)', region: '[REGION – u.a. USA/EU; Auslandbearbeitung möglich]', optional: true }, { name: 'SMTP-/E-Mail-Anbieter', purpose: 'System-E-Mails (Verifizierung, Reset, Kontakt, Einladungen)', data: 'E-Mail-Adresse, Nachrichteninhalt', region: '[REGION – abhängig vom konfigurierten Anbieter]', optional: true }, { name: 'MinIO (Objektspeicher, Eigenbetrieb)', purpose: 'Datei-/Bild-Uploads (Pläne, Logos, Symbole)', data: 'hochgeladene Dateien', region: 'Schweiz (beim Betreiber)' }, { name: 'PostgreSQL (Datenbank, Eigenbetrieb)', purpose: 'Primäre Datenhaltung', data: 'Konto-, Organisations-, Plan- und Journaldaten', region: 'Schweiz (beim Betreiber)' }, ] as ExternalProcessor[], // ─── Rechtsdokumente: bewusst gesetzte Versionen ────────── // WICHTIG: Version manuell erhöhen, wenn sich der Inhalt WESENTLICH ändert (löst Re-Consent aus). // Nicht automatisch aus minimalen Dateiänderungen ableiten. documents: { terms: { version: '1.0.0', publishedAt: '2026-07-23' }, privacy: { version: '1.0.0', publishedAt: '2026-07-23' }, organizationDeclaration: { version: '1.0.0', publishedAt: '2026-07-23' }, responsibleUse: { version: '1.0.0', publishedAt: '2026-07-23' }, dataProcessingAgreement: { version: '1.0.0', publishedAt: '2026-07-23' }, } as Record, /** Dezenter Standard-Hinweis auf allen Rechtsseiten (kein Entwurfshinweis). */ draftNotice: 'Diese Unterlagen wurden nach bestem Wissen und Gewissen erstellt und werden laufend gepflegt.', } as const /** Pflichtfelder, die vor Produktivbetrieb ausgefüllt sein müssen. */ const REQUIRED_FIELDS: { key: string; value: string }[] = [ { key: 'operatorName', value: legal.operatorName }, { key: 'operatorLegalForm', value: legal.operatorLegalForm }, { key: 'postalAddress', value: legal.postalAddress }, { key: 'privacyContactEmail', value: legal.privacyContactEmail }, { key: 'hostingProvider', value: legal.hostingProvider }, { key: 'serverLocation', value: legal.serverLocation }, { key: 'backupRetention', value: legal.backupRetention }, ] /** Liste der noch offenen Pflichtangaben (Platzhalter). */ export function missingLegalFields(): string[] { return REQUIRED_FIELDS.filter(f => isPlaceholder(f.value)).map(f => f.key) } /** * Warnt beim Build/Start, wenn Pflichtangaben noch Platzhalter sind. * Bricht den Build NICHT ab (damit Entwicklung möglich bleibt), macht das Risiko aber sichtbar. */ export function assertLegalConfig(): void { const missing = missingLegalFields() if (missing.length > 0) { const msg = `⚠️ [legal.ts] Offene rechtliche Pflichtangaben (noch Platzhalter): ${missing.join(', ')}. ` + `Bitte in src/config/legal.ts bzw. via Umgebungsvariablen (LEGAL_*) setzen, bevor produktiv gegangen wird.` // In Produktion als Fehler-Log, in Dev als Warnung — nie stillschweigend. if (process.env.NODE_ENV === 'production') console.error(msg) else console.warn(msg) } } // Beim Laden der Konfiguration einmalig prüfen (serverseitig). if (typeof window === 'undefined') { assertLegalConfig() } export type LegalConfig = typeof legal