- 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>
164 lines
9.0 KiB
TypeScript
164 lines
9.0 KiB
TypeScript
/**
|
||
* 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
|