import { ConsentType, ConsentStatus } from '@prisma/client'; import crypto from 'crypto'; import prisma from '../lib/prisma.js'; import * as consentService from './consent.service.js'; import * as appSettingService from './appSetting.service.js'; import PDFDocument from 'pdfkit'; /** * Kunden-Lookup per consentHash */ export async function getCustomerByConsentHash(hash: string) { const customer = await prisma.customer.findUnique({ where: { consentHash: hash }, select: { id: true, firstName: true, lastName: true, customerNumber: true, salutation: true, email: true, }, }); if (!customer) return null; const consents = await consentService.getCustomerConsents(customer.id); return { customer, consents }; } /** * Alle 4 Einwilligungen über den öffentlichen Link erteilen */ export async function grantAllConsentsPublic(hash: string, ipAddress: string) { const customer = await prisma.customer.findUnique({ where: { consentHash: hash }, select: { id: true, firstName: true, lastName: true }, }); if (!customer) { throw new Error('Ungültiger Link'); } const results = []; for (const type of Object.values(ConsentType)) { const result = await consentService.updateConsent(customer.id, type, { status: ConsentStatus.GRANTED, source: 'public-link', ipAddress, createdBy: `${customer.firstName} ${customer.lastName} (Public-Link)`, }); results.push(result); } return results; } /** * consentHash generieren falls nicht vorhanden */ export async function ensureConsentHash(customerId: number): Promise { const customer = await prisma.customer.findUnique({ where: { id: customerId }, select: { consentHash: true }, }); if (!customer) { throw new Error('Kunde nicht gefunden'); } if (customer.consentHash) { return customer.consentHash; } const hash = crypto.randomUUID(); await prisma.customer.update({ where: { id: customerId }, data: { consentHash: hash }, }); return hash; } /** * Platzhalter in Text ersetzen */ function replacePlaceholders(html: string, customer: { firstName: string; lastName: string; customerNumber: string; salutation?: string | null; email?: string | null; }): string { return html .replace(/\{\{vorname\}\}/gi, customer.firstName || '') .replace(/\{\{nachname\}\}/gi, customer.lastName || '') .replace(/\{\{kundennummer\}\}/gi, customer.customerNumber || '') .replace(/\{\{anrede\}\}/gi, customer.salutation || '') .replace(/\{\{email\}\}/gi, customer.email || '') .replace(/\{\{datum\}\}/gi, new Date().toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric', })); } /** * Datenschutzerklärung als HTML abrufen (mit Platzhaltern ersetzt) */ export async function getPrivacyPolicyHtml(customerId?: number): Promise { const html = await appSettingService.getSetting('privacyPolicyHtml'); if (!html) { return '

Keine Datenschutzerklärung hinterlegt.

'; } if (!customerId) return html; const customer = await prisma.customer.findUnique({ where: { id: customerId }, select: { firstName: true, lastName: true, customerNumber: true, salutation: true, email: true, }, }); if (!customer) return html; return replacePlaceholders(html, customer); } /** * HTML zu Plain-Text konvertieren (für PDF) */ function htmlToText(html: string): string { return html .replace(/]*>(.*?)<\/h[1-6]>/gi, '\n$1\n') .replace(//gi, '\n') .replace(/<\/p>/gi, '\n\n') .replace(/]*>(.*?)<\/li>/gi, ' • $1\n') .replace(/<[^>]+>/g, '') .replace(/&/g, '&') .replace(/</g, '<') .replace(/>/g, '>') .replace(/"/g, '"') .replace(/'/g, "'") .replace(/ /g, ' ') .replace(/\n{3,}/g, '\n\n') .trim(); } /** * Datenschutzerklärung als PDF generieren */ export async function generateConsentPdf(customerId: number): Promise { const html = await getPrivacyPolicyHtml(customerId); const text = htmlToText(html); return new Promise((resolve, reject) => { const doc = new PDFDocument({ size: 'A4', margin: 50 }); const chunks: Buffer[] = []; doc.on('data', (chunk: Buffer) => chunks.push(chunk)); doc.on('end', () => resolve(Buffer.concat(chunks))); doc.on('error', reject); // Titel doc.fontSize(18).font('Helvetica-Bold').text('Datenschutzerklärung', { align: 'center' }); doc.moveDown(1); // Datum doc.fontSize(10).font('Helvetica') .text(`Erstellt am: ${new Date().toLocaleDateString('de-DE')}`, { align: 'right' }); doc.moveDown(1); // Inhalt doc.fontSize(11).font('Helvetica').text(text, { align: 'left', lineGap: 4, }); doc.end(); }); }