/** * Privacy Policy Renderers & Main Generator * * Cookies section, changes section, rendering (HTML/Markdown), * and the main generatePrivacyPolicy entry point. */ import { DataPoint, CompanyInfo, PrivacyPolicySection, GeneratedPrivacyPolicy, SupportedLanguage, ExportFormat, LocalizedText, } from '../types' import { RETENTION_MATRIX } from '../catalog/loader' import { formatDate, generateControllerSection, generateDataCollectionSection, generatePurposesSection, generateLegalBasisSection, generateRecipientsSection, generateRetentionSection, generateSpecialCategoriesSection, generateRightsSection, } from './privacy-policy-sections' // ============================================================================= // HELPER // ============================================================================= function t(text: LocalizedText, language: SupportedLanguage): string { return text[language] } // ============================================================================= // SECTION GENERATORS (cookies + changes) // ============================================================================= export function generateCookiesSection( dataPoints: DataPoint[], language: SupportedLanguage ): PrivacyPolicySection { const title: LocalizedText = { de: '8. Cookies und aehnliche Technologien', en: '8. Cookies and Similar Technologies', } 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, } } 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, } } export 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 // ============================================================================= 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), ] const specialCategoriesSection = generateSpecialCategoriesSection(dataPoints, language) if (specialCategoriesSection) { sections.push(specialCategoriesSection) } sections.push( generateRightsSection(language), generateCookiesSection(dataPoints, language), generateChangesSection(version, now, language) ) sections.forEach((section, index) => { section.order = index + 1 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 } 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) const content = renderPrivacyPolicy(sections, language, format) return { id: `privacy-policy-${tenantId}-${Date.now()}`, tenantId, language, sections, companyInfo, generatedAt: new Date(), version, format, content, } } // ============================================================================= // RENDERERS // ============================================================================= 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) } } export 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, '
')
.replace(/\n/g, '
')
.replace(/### (.+)/g, '
${content}