188 lines
4.8 KiB
TypeScript
188 lines
4.8 KiB
TypeScript
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<string> {
|
|
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<string> {
|
|
const html = await appSettingService.getSetting('privacyPolicyHtml');
|
|
|
|
if (!html) {
|
|
return '<p>Keine Datenschutzerklärung hinterlegt.</p>';
|
|
}
|
|
|
|
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][^>]*>(.*?)<\/h[1-6]>/gi, '\n$1\n')
|
|
.replace(/<br\s*\/?>/gi, '\n')
|
|
.replace(/<\/p>/gi, '\n\n')
|
|
.replace(/<li[^>]*>(.*?)<\/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<Buffer> {
|
|
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();
|
|
});
|
|
}
|