Files
breakpilot-compliance/admin-compliance/lib/sdk/einwilligungen/generator/privacy-policy.ts
Benjamin Boenisch 4435e7ea0a Initial commit: breakpilot-compliance - Compliance SDK Platform
Services: Admin-Compliance, Backend-Compliance,
AI-Compliance-SDK, Consent-SDK, Developer-Portal,
PCA-Platform, DSMS

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 23:47:28 +01:00

966 lines
33 KiB
TypeScript

/**
* Privacy Policy Generator
*
* Generiert Datenschutzerklaerungen (DSI) aus dem Datenpunktkatalog.
* Die DSI wird aus 9 Abschnitten generiert:
*
* 1. Verantwortlicher (companyInfo)
* 2. Erhobene Daten (dataPoints nach Kategorie)
* 3. Verarbeitungszwecke (dataPoints.purpose)
* 4. Rechtsgrundlagen (dataPoints.legalBasis)
* 5. Empfaenger/Dritte (dataPoints.thirdPartyRecipients)
* 6. Speicherdauer (retentionMatrix)
* 7. Betroffenenrechte (statischer Text + Links)
* 8. Cookies (cookieCategory-basiert)
* 9. Aenderungen (statischer Text + Versionierung)
*/
import {
DataPoint,
DataPointCategory,
CompanyInfo,
PrivacyPolicySection,
GeneratedPrivacyPolicy,
SupportedLanguage,
ExportFormat,
LocalizedText,
RetentionMatrixEntry,
LegalBasis,
CATEGORY_METADATA,
LEGAL_BASIS_INFO,
RETENTION_PERIOD_INFO,
ARTICLE_9_WARNING,
} from '../types'
import { RETENTION_MATRIX } from '../catalog/loader'
// =============================================================================
// KONSTANTEN - 18 Kategorien in der richtigen Reihenfolge
// =============================================================================
const ALL_CATEGORIES: DataPointCategory[] = [
'MASTER_DATA', // A
'CONTACT_DATA', // B
'AUTHENTICATION', // C
'CONSENT', // D
'COMMUNICATION', // E
'PAYMENT', // F
'USAGE_DATA', // G
'LOCATION', // H
'DEVICE_DATA', // I
'MARKETING', // J
'ANALYTICS', // K
'SOCIAL_MEDIA', // L
'HEALTH_DATA', // M - Art. 9 DSGVO
'EMPLOYEE_DATA', // N - BDSG § 26
'CONTRACT_DATA', // O
'LOG_DATA', // P
'AI_DATA', // Q - AI Act
'SECURITY', // R
]
// Alle Rechtsgrundlagen in der richtigen Reihenfolge
const ALL_LEGAL_BASES: LegalBasis[] = [
'CONTRACT',
'CONSENT',
'EXPLICIT_CONSENT',
'LEGITIMATE_INTEREST',
'LEGAL_OBLIGATION',
'VITAL_INTERESTS',
'PUBLIC_INTEREST',
]
// =============================================================================
// HELPER FUNCTIONS
// =============================================================================
/**
* Holt den lokalisierten Text
*/
function t(text: LocalizedText, language: SupportedLanguage): string {
return text[language]
}
/**
* Gruppiert Datenpunkte nach Kategorie
*/
function groupByCategory(dataPoints: DataPoint[]): Map<DataPointCategory, DataPoint[]> {
const grouped = new Map<DataPointCategory, DataPoint[]>()
for (const dp of dataPoints) {
const existing = grouped.get(dp.category) || []
grouped.set(dp.category, [...existing, dp])
}
return grouped
}
/**
* Gruppiert Datenpunkte nach Rechtsgrundlage
*/
function groupByLegalBasis(dataPoints: DataPoint[]): Map<LegalBasis, DataPoint[]> {
const grouped = new Map<LegalBasis, DataPoint[]>()
for (const dp of dataPoints) {
const existing = grouped.get(dp.legalBasis) || []
grouped.set(dp.legalBasis, [...existing, dp])
}
return grouped
}
/**
* Extrahiert alle einzigartigen Drittanbieter
*/
function extractThirdParties(dataPoints: DataPoint[]): string[] {
const thirdParties = new Set<string>()
for (const dp of dataPoints) {
for (const recipient of dp.thirdPartyRecipients) {
thirdParties.add(recipient)
}
}
return Array.from(thirdParties).sort()
}
/**
* Formatiert ein Datum fuer die Anzeige
*/
function formatDate(date: Date, language: SupportedLanguage): string {
return date.toLocaleDateString(language === 'de' ? 'de-DE' : 'en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
})
}
// =============================================================================
// SECTION GENERATORS
// =============================================================================
/**
* Abschnitt 1: Verantwortlicher
*/
function generateControllerSection(
companyInfo: CompanyInfo,
language: SupportedLanguage
): PrivacyPolicySection {
const title: LocalizedText = {
de: '1. Verantwortlicher',
en: '1. Data Controller',
}
const dpoSection = companyInfo.dpoName
? language === 'de'
? `\n\n**Datenschutzbeauftragter:**\n${companyInfo.dpoName}${companyInfo.dpoEmail ? `\nE-Mail: ${companyInfo.dpoEmail}` : ''}${companyInfo.dpoPhone ? `\nTelefon: ${companyInfo.dpoPhone}` : ''}`
: `\n\n**Data Protection Officer:**\n${companyInfo.dpoName}${companyInfo.dpoEmail ? `\nEmail: ${companyInfo.dpoEmail}` : ''}${companyInfo.dpoPhone ? `\nPhone: ${companyInfo.dpoPhone}` : ''}`
: ''
const content: LocalizedText = {
de: `Verantwortlich fuer die Datenverarbeitung auf dieser Website ist:
**${companyInfo.name}**
${companyInfo.address}
${companyInfo.postalCode} ${companyInfo.city}
${companyInfo.country}
E-Mail: ${companyInfo.email}${companyInfo.phone ? `\nTelefon: ${companyInfo.phone}` : ''}${companyInfo.website ? `\nWebsite: ${companyInfo.website}` : ''}${companyInfo.registrationNumber ? `\n\nHandelsregister: ${companyInfo.registrationNumber}` : ''}${companyInfo.vatId ? `\nUSt-IdNr.: ${companyInfo.vatId}` : ''}${dpoSection}`,
en: `The controller responsible for data processing on this website is:
**${companyInfo.name}**
${companyInfo.address}
${companyInfo.postalCode} ${companyInfo.city}
${companyInfo.country}
Email: ${companyInfo.email}${companyInfo.phone ? `\nPhone: ${companyInfo.phone}` : ''}${companyInfo.website ? `\nWebsite: ${companyInfo.website}` : ''}${companyInfo.registrationNumber ? `\n\nCommercial Register: ${companyInfo.registrationNumber}` : ''}${companyInfo.vatId ? `\nVAT ID: ${companyInfo.vatId}` : ''}${dpoSection}`,
}
return {
id: 'controller',
order: 1,
title,
content,
dataPointIds: [],
isRequired: true,
isGenerated: false,
}
}
/**
* Abschnitt 2: Erhobene Daten (18 Kategorien)
*/
function generateDataCollectionSection(
dataPoints: DataPoint[],
language: SupportedLanguage
): PrivacyPolicySection {
const title: LocalizedText = {
de: '2. Erhobene personenbezogene Daten',
en: '2. Personal Data We Collect',
}
const grouped = groupByCategory(dataPoints)
const sections: string[] = []
// Prüfe ob Art. 9 Daten enthalten sind
const hasSpecialCategoryData = dataPoints.some(dp => dp.isSpecialCategory || dp.category === 'HEALTH_DATA')
for (const category of ALL_CATEGORIES) {
const categoryData = grouped.get(category)
if (!categoryData || categoryData.length === 0) continue
const categoryMeta = CATEGORY_METADATA[category]
if (!categoryMeta) continue
const categoryTitle = t(categoryMeta.name, language)
// Spezielle Warnung für Art. 9 DSGVO Daten (Gesundheitsdaten)
let categoryNote = ''
if (category === 'HEALTH_DATA') {
categoryNote = language === 'de'
? `\n\n> **Hinweis:** Diese Daten gehoeren zu den besonderen Kategorien personenbezogener Daten gemaess Art. 9 DSGVO und erfordern eine ausdrueckliche Einwilligung.`
: `\n\n> **Note:** This data belongs to special categories of personal data under Art. 9 GDPR and requires explicit consent.`
} else if (category === 'EMPLOYEE_DATA') {
categoryNote = language === 'de'
? `\n\n> **Hinweis:** Die Verarbeitung von Beschaeftigtendaten erfolgt gemaess § 26 BDSG.`
: `\n\n> **Note:** Processing of employee data is carried out in accordance with § 26 BDSG (German Federal Data Protection Act).`
} else if (category === 'AI_DATA') {
categoryNote = language === 'de'
? `\n\n> **Hinweis:** Die Verarbeitung von KI-bezogenen Daten unterliegt den Transparenzpflichten des AI Acts.`
: `\n\n> **Note:** Processing of AI-related data is subject to AI Act transparency requirements.`
}
const dataList = categoryData
.map((dp) => {
const specialTag = dp.isSpecialCategory
? (language === 'de' ? ' *(Art. 9 DSGVO)*' : ' *(Art. 9 GDPR)*')
: ''
return `- **${t(dp.name, language)}**${specialTag}: ${t(dp.description, language)}`
})
.join('\n')
sections.push(`### ${categoryMeta.code}. ${categoryTitle}\n\n${dataList}${categoryNote}`)
}
const intro: LocalizedText = {
de: 'Wir erheben und verarbeiten die folgenden personenbezogenen Daten:',
en: 'We collect and process the following personal data:',
}
// Zusätzlicher Hinweis für Art. 9 Daten
const specialCategoryNote: LocalizedText = hasSpecialCategoryData
? {
de: '\n\n**Wichtig:** Einige der unten aufgefuehrten Daten gehoeren zu den besonderen Kategorien personenbezogener Daten nach Art. 9 DSGVO und werden nur mit Ihrer ausdruecklichen Einwilligung verarbeitet.',
en: '\n\n**Important:** Some of the data listed below belongs to special categories of personal data under Art. 9 GDPR and is only processed with your explicit consent.',
}
: { de: '', en: '' }
const content: LocalizedText = {
de: `${intro.de}${specialCategoryNote.de}\n\n${sections.join('\n\n')}`,
en: `${intro.en}${specialCategoryNote.en}\n\n${sections.join('\n\n')}`,
}
return {
id: 'data-collection',
order: 2,
title,
content,
dataPointIds: dataPoints.map((dp) => dp.id),
isRequired: true,
isGenerated: true,
}
}
/**
* Abschnitt 3: Verarbeitungszwecke
*/
function generatePurposesSection(
dataPoints: DataPoint[],
language: SupportedLanguage
): PrivacyPolicySection {
const title: LocalizedText = {
de: '3. Zwecke der Datenverarbeitung',
en: '3. Purposes of Data Processing',
}
// Gruppiere nach Zweck (unique purposes)
const purposes = new Map<string, DataPoint[]>()
for (const dp of dataPoints) {
const purpose = t(dp.purpose, language)
const existing = purposes.get(purpose) || []
purposes.set(purpose, [...existing, dp])
}
const purposeList = Array.from(purposes.entries())
.map(([purpose, dps]) => {
const dataNames = dps.map((dp) => t(dp.name, language)).join(', ')
return `- **${purpose}**\n Betroffene Daten: ${dataNames}`
})
.join('\n\n')
const content: LocalizedText = {
de: `Wir verarbeiten Ihre personenbezogenen Daten fuer folgende Zwecke:\n\n${purposeList}`,
en: `We process your personal data for the following purposes:\n\n${purposeList}`,
}
return {
id: 'purposes',
order: 3,
title,
content,
dataPointIds: dataPoints.map((dp) => dp.id),
isRequired: true,
isGenerated: true,
}
}
/**
* Abschnitt 4: Rechtsgrundlagen (alle 7 Rechtsgrundlagen)
*/
function generateLegalBasisSection(
dataPoints: DataPoint[],
language: SupportedLanguage
): PrivacyPolicySection {
const title: LocalizedText = {
de: '4. Rechtsgrundlagen der Verarbeitung',
en: '4. Legal Basis for Processing',
}
const grouped = groupByLegalBasis(dataPoints)
const sections: string[] = []
// Alle 7 Rechtsgrundlagen in der richtigen Reihenfolge
for (const basis of ALL_LEGAL_BASES) {
const basisData = grouped.get(basis)
if (!basisData || basisData.length === 0) continue
const basisInfo = LEGAL_BASIS_INFO[basis]
if (!basisInfo) continue
const basisTitle = `${t(basisInfo.name, language)} (${basisInfo.article})`
const basisDesc = t(basisInfo.description, language)
// Für Art. 9 Daten (EXPLICIT_CONSENT) zusätzliche Warnung hinzufügen
let additionalWarning = ''
if (basis === 'EXPLICIT_CONSENT') {
additionalWarning = language === 'de'
? `\n\n> **Wichtig:** Fuer die Verarbeitung dieser besonderen Kategorien personenbezogener Daten (Art. 9 DSGVO) ist eine separate, ausdrueckliche Einwilligung erforderlich, die Sie jederzeit widerrufen koennen.`
: `\n\n> **Important:** Processing of these special categories of personal data (Art. 9 GDPR) requires a separate, explicit consent that you can withdraw at any time.`
}
const dataLabel = language === 'de' ? 'Betroffene Daten' : 'Affected Data'
const dataList = basisData
.map((dp) => {
const specialTag = dp.isSpecialCategory
? (language === 'de' ? ' *(Art. 9 DSGVO)*' : ' *(Art. 9 GDPR)*')
: ''
return `- ${t(dp.name, language)}${specialTag}: ${t(dp.legalBasisJustification, language)}`
})
.join('\n')
sections.push(`### ${basisTitle}\n\n${basisDesc}${additionalWarning}\n\n**${dataLabel}:**\n${dataList}`)
}
const content: LocalizedText = {
de: `Die Verarbeitung Ihrer personenbezogenen Daten erfolgt auf Grundlage folgender Rechtsgrundlagen:\n\n${sections.join('\n\n')}`,
en: `The processing of your personal data is based on the following legal grounds:\n\n${sections.join('\n\n')}`,
}
return {
id: 'legal-basis',
order: 4,
title,
content,
dataPointIds: dataPoints.map((dp) => dp.id),
isRequired: true,
isGenerated: true,
}
}
/**
* Abschnitt 5: Empfaenger / Dritte
*/
function generateRecipientsSection(
dataPoints: DataPoint[],
language: SupportedLanguage
): PrivacyPolicySection {
const title: LocalizedText = {
de: '5. Empfaenger und Datenweitergabe',
en: '5. Recipients and Data Sharing',
}
const thirdParties = extractThirdParties(dataPoints)
if (thirdParties.length === 0) {
const content: LocalizedText = {
de: 'Wir geben Ihre personenbezogenen Daten grundsaetzlich nicht an Dritte weiter, es sei denn, dies ist zur Vertragserfuellung erforderlich oder Sie haben ausdruecklich eingewilligt.',
en: 'We generally do not share your personal data with third parties unless this is necessary for contract performance or you have expressly consented.',
}
return {
id: 'recipients',
order: 5,
title,
content,
dataPointIds: [],
isRequired: true,
isGenerated: false,
}
}
// Gruppiere nach Drittanbieter
const recipientDetails = new Map<string, DataPoint[]>()
for (const dp of dataPoints) {
for (const recipient of dp.thirdPartyRecipients) {
const existing = recipientDetails.get(recipient) || []
recipientDetails.set(recipient, [...existing, dp])
}
}
const recipientList = Array.from(recipientDetails.entries())
.map(([recipient, dps]) => {
const dataNames = dps.map((dp) => t(dp.name, language)).join(', ')
return `- **${recipient}**: ${dataNames}`
})
.join('\n')
const content: LocalizedText = {
de: `Wir uebermitteln Ihre personenbezogenen Daten an folgende Empfaenger bzw. Kategorien von Empfaengern:\n\n${recipientList}\n\nMit allen Auftragsverarbeitern haben wir Auftragsverarbeitungsvertraege nach Art. 28 DSGVO abgeschlossen.`,
en: `We share your personal data with the following recipients or categories of recipients:\n\n${recipientList}\n\nWe have concluded data processing agreements pursuant to Art. 28 GDPR with all processors.`,
}
return {
id: 'recipients',
order: 5,
title,
content,
dataPointIds: dataPoints.filter((dp) => dp.thirdPartyRecipients.length > 0).map((dp) => dp.id),
isRequired: true,
isGenerated: true,
}
}
/**
* Abschnitt 6: Speicherdauer
*/
function generateRetentionSection(
dataPoints: DataPoint[],
retentionMatrix: RetentionMatrixEntry[],
language: SupportedLanguage
): PrivacyPolicySection {
const title: LocalizedText = {
de: '6. Speicherdauer',
en: '6. Data Retention',
}
const grouped = groupByCategory(dataPoints)
const sections: string[] = []
for (const entry of retentionMatrix) {
const categoryData = grouped.get(entry.category)
if (!categoryData || categoryData.length === 0) continue
const categoryName = t(entry.categoryName, language)
const standardPeriod = t(RETENTION_PERIOD_INFO[entry.standardPeriod].label, language)
const dataRetention = categoryData
.map((dp) => {
const period = t(RETENTION_PERIOD_INFO[dp.retentionPeriod].label, language)
return `- ${t(dp.name, language)}: ${period}`
})
.join('\n')
sections.push(`### ${categoryName}\n\n**Standardfrist:** ${standardPeriod}\n\n${dataRetention}`)
}
const content: LocalizedText = {
de: `Wir speichern Ihre personenbezogenen Daten nur so lange, wie dies fuer die jeweiligen Zwecke erforderlich ist oder gesetzliche Aufbewahrungsfristen bestehen.\n\n${sections.join('\n\n')}`,
en: `We store your personal data only for as long as is necessary for the respective purposes or as required by statutory retention periods.\n\n${sections.join('\n\n')}`,
}
return {
id: 'retention',
order: 6,
title,
content,
dataPointIds: dataPoints.map((dp) => dp.id),
isRequired: true,
isGenerated: true,
}
}
/**
* Abschnitt 6a: Besondere Kategorien (Art. 9 DSGVO)
* Wird nur generiert, wenn Art. 9 Daten vorhanden sind
*/
function generateSpecialCategoriesSection(
dataPoints: DataPoint[],
language: SupportedLanguage
): PrivacyPolicySection | null {
// Filtere Art. 9 Datenpunkte
const specialCategoryDataPoints = dataPoints.filter(dp => dp.isSpecialCategory || dp.category === 'HEALTH_DATA')
if (specialCategoryDataPoints.length === 0) {
return null
}
const title: LocalizedText = {
de: '6a. Besondere Kategorien personenbezogener Daten (Art. 9 DSGVO)',
en: '6a. Special Categories of Personal Data (Art. 9 GDPR)',
}
const dataList = specialCategoryDataPoints
.map((dp) => `- **${t(dp.name, language)}**: ${t(dp.description, language)}`)
.join('\n')
const content: LocalizedText = {
de: `Gemaess Art. 9 DSGVO verarbeiten wir auch besondere Kategorien personenbezogener Daten, die einen erhoehten Schutz geniessen. Diese Daten umfassen:
${dataList}
### Ihre ausdrueckliche Einwilligung
Die Verarbeitung dieser besonderen Kategorien personenbezogener Daten erfolgt nur auf Grundlage Ihrer **ausdruecklichen Einwilligung** gemaess Art. 9 Abs. 2 lit. a DSGVO.
### Ihre Rechte bei Art. 9 Daten
- Sie koennen Ihre Einwilligung **jederzeit widerrufen**
- Der Widerruf beruehrt nicht die Rechtmaessigkeit der bisherigen Verarbeitung
- Bei Widerruf werden Ihre Daten unverzueglich geloescht
- Sie haben das Recht auf **Auskunft, Berichtigung und Loeschung**
### Besondere Schutzmassnahmen
Fuer diese sensiblen Daten haben wir besondere technische und organisatorische Massnahmen implementiert:
- Ende-zu-Ende-Verschluesselung
- Strenge Zugriffskontrolle (Need-to-Know-Prinzip)
- Audit-Logging aller Zugriffe
- Regelmaessige Datenschutz-Folgenabschaetzungen`,
en: `In accordance with Art. 9 GDPR, we also process special categories of personal data that enjoy enhanced protection. This data includes:
${dataList}
### Your Explicit Consent
Processing of these special categories of personal data only takes place on the basis of your **explicit consent** pursuant to Art. 9(2)(a) GDPR.
### Your Rights Regarding Art. 9 Data
- You can **withdraw your consent at any time**
- Withdrawal does not affect the lawfulness of previous processing
- Upon withdrawal, your data will be deleted immediately
- You have the right to **access, rectification, and erasure**
### Special Protection Measures
For this sensitive data, we have implemented special technical and organizational measures:
- End-to-end encryption
- Strict access control (need-to-know principle)
- Audit logging of all access
- Regular data protection impact assessments`,
}
return {
id: 'special-categories',
order: 6.5, // Zwischen Speicherdauer (6) und Rechte (7)
title,
content,
dataPointIds: specialCategoryDataPoints.map((dp) => dp.id),
isRequired: false,
isGenerated: true,
}
}
/**
* Abschnitt 7: Betroffenenrechte
*/
function generateRightsSection(language: SupportedLanguage): PrivacyPolicySection {
const title: LocalizedText = {
de: '7. Ihre Rechte als betroffene Person',
en: '7. Your Rights as a Data Subject',
}
const content: LocalizedText = {
de: `Sie haben gegenueber uns folgende Rechte hinsichtlich der Sie betreffenden personenbezogenen Daten:
### Auskunftsrecht (Art. 15 DSGVO)
Sie haben das Recht, Auskunft ueber die von uns verarbeiteten personenbezogenen Daten zu verlangen.
### Recht auf Berichtigung (Art. 16 DSGVO)
Sie haben das Recht, die Berichtigung unrichtiger oder die Vervollstaendigung unvollstaendiger Daten zu verlangen.
### Recht auf Loeschung (Art. 17 DSGVO)
Sie haben das Recht, die Loeschung Ihrer personenbezogenen Daten zu verlangen, sofern keine gesetzlichen Aufbewahrungspflichten entgegenstehen.
### Recht auf Einschraenkung der Verarbeitung (Art. 18 DSGVO)
Sie haben das Recht, die Einschraenkung der Verarbeitung Ihrer Daten zu verlangen.
### Recht auf Datenuebertragbarkeit (Art. 20 DSGVO)
Sie haben das Recht, Ihre Daten in einem strukturierten, gaengigen und maschinenlesbaren Format zu erhalten.
### Widerspruchsrecht (Art. 21 DSGVO)
Sie haben das Recht, der Verarbeitung Ihrer Daten jederzeit zu widersprechen, soweit die Verarbeitung auf berechtigtem Interesse beruht.
### Recht auf Widerruf der Einwilligung (Art. 7 Abs. 3 DSGVO)
Sie haben das Recht, Ihre erteilte Einwilligung jederzeit zu widerrufen. Die Rechtmaessigkeit der aufgrund der Einwilligung bis zum Widerruf erfolgten Verarbeitung wird dadurch nicht beruehrt.
### Beschwerderecht bei der Aufsichtsbehoerde (Art. 77 DSGVO)
Sie haben das Recht, sich bei einer Datenschutz-Aufsichtsbehoerde ueber die Verarbeitung Ihrer personenbezogenen Daten zu beschweren.
**Zur Ausuebung Ihrer Rechte wenden Sie sich bitte an die oben angegebenen Kontaktdaten.**`,
en: `You have the following rights regarding your personal data:
### Right of Access (Art. 15 GDPR)
You have the right to request information about the personal data we process about you.
### Right to Rectification (Art. 16 GDPR)
You have the right to request the correction of inaccurate data or the completion of incomplete data.
### Right to Erasure (Art. 17 GDPR)
You have the right to request the deletion of your personal data, unless statutory retention obligations apply.
### Right to Restriction of Processing (Art. 18 GDPR)
You have the right to request the restriction of processing of your data.
### Right to Data Portability (Art. 20 GDPR)
You have the right to receive your data in a structured, commonly used, and machine-readable format.
### Right to Object (Art. 21 GDPR)
You have the right to object to the processing of your data at any time, insofar as the processing is based on legitimate interest.
### Right to Withdraw Consent (Art. 7(3) GDPR)
You have the right to withdraw your consent at any time. The lawfulness of processing based on consent before its withdrawal is not affected.
### Right to Lodge a Complaint with a Supervisory Authority (Art. 77 GDPR)
You have the right to lodge a complaint with a data protection supervisory authority about the processing of your personal data.
**To exercise your rights, please contact us using the contact details provided above.**`,
}
return {
id: 'rights',
order: 7,
title,
content,
dataPointIds: [],
isRequired: true,
isGenerated: false,
}
}
/**
* Abschnitt 8: Cookies
*/
function generateCookiesSection(
dataPoints: DataPoint[],
language: SupportedLanguage
): PrivacyPolicySection {
const title: LocalizedText = {
de: '8. Cookies und aehnliche Technologien',
en: '8. Cookies and Similar Technologies',
}
// Filtere Datenpunkte mit Cookie-Kategorie
const cookieDataPoints = dataPoints.filter((dp) => dp.cookieCategory !== null)
if (cookieDataPoints.length === 0) {
const content: LocalizedText = {
de: 'Wir verwenden auf dieser Website keine Cookies.',
en: 'We do not use cookies on this website.',
}
return {
id: 'cookies',
order: 8,
title,
content,
dataPointIds: [],
isRequired: false,
isGenerated: false,
}
}
// Gruppiere nach Cookie-Kategorie
const essential = cookieDataPoints.filter((dp) => dp.cookieCategory === 'ESSENTIAL')
const performance = cookieDataPoints.filter((dp) => dp.cookieCategory === 'PERFORMANCE')
const personalization = cookieDataPoints.filter((dp) => dp.cookieCategory === 'PERSONALIZATION')
const externalMedia = cookieDataPoints.filter((dp) => dp.cookieCategory === 'EXTERNAL_MEDIA')
const sections: string[] = []
if (essential.length > 0) {
const list = essential.map((dp) => `- **${t(dp.name, language)}**: ${t(dp.purpose, language)}`).join('\n')
sections.push(
language === 'de'
? `### Technisch notwendige Cookies\n\nDiese Cookies sind fuer den Betrieb der Website erforderlich und koennen nicht deaktiviert werden.\n\n${list}`
: `### Essential Cookies\n\nThese cookies are required for the website to function and cannot be disabled.\n\n${list}`
)
}
if (performance.length > 0) {
const list = performance.map((dp) => `- **${t(dp.name, language)}**: ${t(dp.purpose, language)}`).join('\n')
sections.push(
language === 'de'
? `### Analyse- und Performance-Cookies\n\nDiese Cookies helfen uns, die Nutzung der Website zu verstehen und zu verbessern.\n\n${list}`
: `### Analytics and Performance Cookies\n\nThese cookies help us understand and improve website usage.\n\n${list}`
)
}
if (personalization.length > 0) {
const list = personalization.map((dp) => `- **${t(dp.name, language)}**: ${t(dp.purpose, language)}`).join('\n')
sections.push(
language === 'de'
? `### Personalisierungs-Cookies\n\nDiese Cookies ermoeglichen personalisierte Werbung und Inhalte.\n\n${list}`
: `### Personalization Cookies\n\nThese cookies enable personalized advertising and content.\n\n${list}`
)
}
if (externalMedia.length > 0) {
const list = externalMedia.map((dp) => `- **${t(dp.name, language)}**: ${t(dp.purpose, language)}`).join('\n')
sections.push(
language === 'de'
? `### Cookies fuer externe Medien\n\nDiese Cookies erlauben die Einbindung externer Medien wie Videos und Karten.\n\n${list}`
: `### External Media Cookies\n\nThese cookies allow embedding external media like videos and maps.\n\n${list}`
)
}
const intro: LocalizedText = {
de: `Wir verwenden Cookies und aehnliche Technologien, um Ihnen die bestmoegliche Nutzung unserer Website zu ermoeglichen. Sie koennen Ihre Cookie-Einstellungen jederzeit ueber unseren Cookie-Banner anpassen.`,
en: `We use cookies and similar technologies to provide you with the best possible experience on our website. You can adjust your cookie settings at any time through our cookie banner.`,
}
const content: LocalizedText = {
de: `${intro.de}\n\n${sections.join('\n\n')}`,
en: `${intro.en}\n\n${sections.join('\n\n')}`,
}
return {
id: 'cookies',
order: 8,
title,
content,
dataPointIds: cookieDataPoints.map((dp) => dp.id),
isRequired: true,
isGenerated: true,
}
}
/**
* Abschnitt 9: Aenderungen
*/
function generateChangesSection(
version: string,
date: Date,
language: SupportedLanguage
): PrivacyPolicySection {
const title: LocalizedText = {
de: '9. Aenderungen dieser Datenschutzerklaerung',
en: '9. Changes to this Privacy Policy',
}
const formattedDate = formatDate(date, language)
const content: LocalizedText = {
de: `Diese Datenschutzerklaerung ist aktuell gueltig und hat den Stand: **${formattedDate}** (Version ${version}).
Wir behalten uns vor, diese Datenschutzerklaerung anzupassen, damit sie stets den aktuellen rechtlichen Anforderungen entspricht oder um Aenderungen unserer Leistungen umzusetzen.
Fuer Ihren erneuten Besuch gilt dann die neue Datenschutzerklaerung.`,
en: `This privacy policy is currently valid and was last updated: **${formattedDate}** (Version ${version}).
We reserve the right to amend this privacy policy to ensure it always complies with current legal requirements or to implement changes to our services.
The new privacy policy will then apply for your next visit.`,
}
return {
id: 'changes',
order: 9,
title,
content,
dataPointIds: [],
isRequired: true,
isGenerated: false,
}
}
// =============================================================================
// MAIN GENERATOR FUNCTIONS
// =============================================================================
/**
* Generiert alle Abschnitte der Privacy Policy (18 Kategorien + Art. 9)
*/
export function generatePrivacyPolicySections(
dataPoints: DataPoint[],
companyInfo: CompanyInfo,
language: SupportedLanguage,
version: string = '1.0.0'
): PrivacyPolicySection[] {
const now = new Date()
const sections: PrivacyPolicySection[] = [
generateControllerSection(companyInfo, language),
generateDataCollectionSection(dataPoints, language),
generatePurposesSection(dataPoints, language),
generateLegalBasisSection(dataPoints, language),
generateRecipientsSection(dataPoints, language),
generateRetentionSection(dataPoints, RETENTION_MATRIX, language),
]
// Art. 9 DSGVO Abschnitt nur einfügen, wenn besondere Kategorien vorhanden
const specialCategoriesSection = generateSpecialCategoriesSection(dataPoints, language)
if (specialCategoriesSection) {
sections.push(specialCategoriesSection)
}
sections.push(
generateRightsSection(language),
generateCookiesSection(dataPoints, language),
generateChangesSection(version, now, language)
)
// Abschnittsnummern neu vergeben
sections.forEach((section, index) => {
section.order = index + 1
// Titel-Nummer aktualisieren
const titleDe = section.title.de
const titleEn = section.title.en
if (titleDe.match(/^\d+[a-z]?\./)) {
section.title.de = titleDe.replace(/^\d+[a-z]?\./, `${index + 1}.`)
}
if (titleEn.match(/^\d+[a-z]?\./)) {
section.title.en = titleEn.replace(/^\d+[a-z]?\./, `${index + 1}.`)
}
})
return sections
}
/**
* Generiert die vollstaendige Privacy Policy
*/
export function generatePrivacyPolicy(
tenantId: string,
dataPoints: DataPoint[],
companyInfo: CompanyInfo,
language: SupportedLanguage,
format: ExportFormat = 'HTML'
): GeneratedPrivacyPolicy {
const version = '1.0.0'
const sections = generatePrivacyPolicySections(dataPoints, companyInfo, language, version)
// Generiere den Inhalt
const content = renderPrivacyPolicy(sections, language, format)
return {
id: `privacy-policy-${tenantId}-${Date.now()}`,
tenantId,
language,
sections,
companyInfo,
generatedAt: new Date(),
version,
format,
content,
}
}
/**
* Rendert die Privacy Policy im gewuenschten Format
*/
function renderPrivacyPolicy(
sections: PrivacyPolicySection[],
language: SupportedLanguage,
format: ExportFormat
): string {
switch (format) {
case 'HTML':
return renderAsHTML(sections, language)
case 'MARKDOWN':
return renderAsMarkdown(sections, language)
default:
return renderAsMarkdown(sections, language)
}
}
/**
* Rendert als HTML
*/
function renderAsHTML(sections: PrivacyPolicySection[], language: SupportedLanguage): string {
const title = language === 'de' ? 'Datenschutzerklaerung' : 'Privacy Policy'
const sectionsHTML = sections
.map((section) => {
const content = t(section.content, language)
.replace(/\n\n/g, '</p><p>')
.replace(/\n/g, '<br>')
.replace(/### (.+)/g, '<h3>$1</h3>')
.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
.replace(/- (.+)(?:<br>|$)/g, '<li>$1</li>')
return `
<section id="${section.id}">
<h2>${t(section.title, language)}</h2>
<p>${content}</p>
</section>
`
})
.join('\n')
return `<!DOCTYPE html>
<html lang="${language}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${title}</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
line-height: 1.6;
max-width: 800px;
margin: 0 auto;
padding: 2rem;
color: #333;
}
h1 { font-size: 2rem; margin-bottom: 2rem; }
h2 { font-size: 1.5rem; margin-top: 2rem; color: #1a1a1a; }
h3 { font-size: 1.25rem; margin-top: 1.5rem; color: #333; }
p { margin: 1rem 0; }
ul, ol { margin: 1rem 0; padding-left: 2rem; }
li { margin: 0.5rem 0; }
strong { font-weight: 600; }
</style>
</head>
<body>
<h1>${title}</h1>
${sectionsHTML}
</body>
</html>`
}
/**
* Rendert als Markdown
*/
function renderAsMarkdown(sections: PrivacyPolicySection[], language: SupportedLanguage): string {
const title = language === 'de' ? 'Datenschutzerklaerung' : 'Privacy Policy'
const sectionsMarkdown = sections
.map((section) => {
return `## ${t(section.title, language)}\n\n${t(section.content, language)}`
})
.join('\n\n---\n\n')
return `# ${title}\n\n${sectionsMarkdown}`
}
// =============================================================================
// EXPORTS
// =============================================================================
export {
generateControllerSection,
generateDataCollectionSection,
generatePurposesSection,
generateLegalBasisSection,
generateRecipientsSection,
generateRetentionSection,
generateSpecialCategoriesSection,
generateRightsSection,
generateCookiesSection,
generateChangesSection,
renderAsHTML,
renderAsMarkdown,
}