Files
Lageplan/src/config/legal.ts
Pepe Ziberi 836b231c42 feat(legal): echte Betreiberangaben eingesetzt, Impressum als Privatperson (v1.7.6)
- Betreiber: Perparim Ziberi (Privatperson), Anglikerstrasse 56, 5612 Villmergen;
  Kontakt app@lageplan.ch, Datenschutz datenschutz@lageplan.ch
- Serverstandort/Hosting: Eigenbetrieb, Schweiz (beim Betreiber) — MinIO/PostgreSQL Region = Schweiz
- Backup-Aufbewahrung: bis zu 30 Tage · Gerichtsstand: Wohnsitz des Betreibers (Schweiz)
- Impressum: Firma "Kingstickers Ziberi" + UID entfernt (auf Wunsch Privatperson), Urheberrecht
  und Copyright auf Perparim Ziberi umgestellt; Home-Footer-Copyright angeglichen
- Alle Legal-Platzhalter damit gefüllt (keine Build-Warnung mehr)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 12:47:04 +02:00

164 lines
9.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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<string, LegalDocumentMeta>,
/** 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