refactor(admin-compliance): split 7 oversized files under 500 LOC hard cap (batch 3)
- tom-generator/export/zip.ts: extract private helpers to zip-helpers.ts (544→342 LOC) - tom-generator/export/docx.ts: extract private helpers to docx-helpers.ts (525→378 LOC) - tom-generator/export/pdf.ts: extract private helpers to pdf-helpers.ts (517→446 LOC) - tom-generator/demo-data/index.ts: extract DEMO_RISK_PROFILES + DEMO_EVIDENCE_DOCUMENTS to demo-data-part2.ts (518→360 LOC) - einwilligungen/generator/privacy-policy-sections.ts: extract sections 5-7 to part2 (559→313 LOC) - einwilligungen/export/pdf.ts: extract HTML/CSS helpers to pdf-helpers.ts (505→296 LOC) - vendor-compliance/context.tsx: extract API action hooks to context-actions.tsx (509→286 LOC) All originals re-export from sibling files — zero consumer import changes needed. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
165
admin-compliance/lib/sdk/tom-generator/export/docx-helpers.ts
Normal file
165
admin-compliance/lib/sdk/tom-generator/export/docx-helpers.ts
Normal file
@@ -0,0 +1,165 @@
|
||||
// =============================================================================
|
||||
// TOM Generator DOCX Export - Helper Functions
|
||||
// Private helpers extracted from docx.ts to stay under 500 LOC hard cap
|
||||
// =============================================================================
|
||||
|
||||
import { DerivedTOM, ControlCategory } from '../types'
|
||||
import { getControlById } from '../controls/loader'
|
||||
import type { DOCXExportOptions, DocxTableRow } from './docx'
|
||||
|
||||
// =============================================================================
|
||||
// HELPER FUNCTIONS
|
||||
// =============================================================================
|
||||
|
||||
export function groupTOMsByCategory(
|
||||
toms: DerivedTOM[],
|
||||
includeNotApplicable: boolean
|
||||
): Map<ControlCategory, DerivedTOM[]> {
|
||||
const grouped = new Map<ControlCategory, DerivedTOM[]>()
|
||||
|
||||
for (const tom of toms) {
|
||||
if (!includeNotApplicable && tom.applicability === 'NOT_APPLICABLE') {
|
||||
continue
|
||||
}
|
||||
|
||||
const control = getControlById(tom.controlId)
|
||||
if (!control) continue
|
||||
|
||||
const category = control.category
|
||||
const existing = grouped.get(category) || []
|
||||
existing.push(tom)
|
||||
grouped.set(category, existing)
|
||||
}
|
||||
|
||||
return grouped
|
||||
}
|
||||
|
||||
export function formatRole(role: string, language: 'de' | 'en'): string {
|
||||
const roles: Record<string, Record<'de' | 'en', string>> = {
|
||||
CONTROLLER: { de: 'Verantwortlicher', en: 'Controller' },
|
||||
PROCESSOR: { de: 'Auftragsverarbeiter', en: 'Processor' },
|
||||
JOINT_CONTROLLER: { de: 'Gemeinsam Verantwortlicher', en: 'Joint Controller' },
|
||||
}
|
||||
return roles[role]?.[language] || role
|
||||
}
|
||||
|
||||
export function formatProtectionLevel(level: string, language: 'de' | 'en'): string {
|
||||
const levels: Record<string, Record<'de' | 'en', string>> = {
|
||||
NORMAL: { de: 'Normal', en: 'Normal' },
|
||||
HIGH: { de: 'Hoch', en: 'High' },
|
||||
VERY_HIGH: { de: 'Sehr hoch', en: 'Very High' },
|
||||
}
|
||||
return levels[level]?.[language] || level
|
||||
}
|
||||
|
||||
export function formatType(type: string, language: 'de' | 'en'): string {
|
||||
const types: Record<string, Record<'de' | 'en', string>> = {
|
||||
TECHNICAL: { de: 'Technisch', en: 'Technical' },
|
||||
ORGANIZATIONAL: { de: 'Organisatorisch', en: 'Organizational' },
|
||||
}
|
||||
return types[type]?.[language] || type
|
||||
}
|
||||
|
||||
export function formatImplementationStatus(status: string, language: 'de' | 'en'): string {
|
||||
const statuses: Record<string, Record<'de' | 'en', string>> = {
|
||||
NOT_IMPLEMENTED: { de: 'Nicht umgesetzt', en: 'Not Implemented' },
|
||||
PARTIAL: { de: 'Teilweise umgesetzt', en: 'Partially Implemented' },
|
||||
IMPLEMENTED: { de: 'Umgesetzt', en: 'Implemented' },
|
||||
}
|
||||
return statuses[status]?.[language] || status
|
||||
}
|
||||
|
||||
export function formatApplicability(applicability: string, language: 'de' | 'en'): string {
|
||||
const apps: Record<string, Record<'de' | 'en', string>> = {
|
||||
REQUIRED: { de: 'Erforderlich', en: 'Required' },
|
||||
RECOMMENDED: { de: 'Empfohlen', en: 'Recommended' },
|
||||
OPTIONAL: { de: 'Optional', en: 'Optional' },
|
||||
NOT_APPLICABLE: { de: 'Nicht anwendbar', en: 'Not Applicable' },
|
||||
}
|
||||
return apps[applicability]?.[language] || applicability
|
||||
}
|
||||
|
||||
export function generateHTMLFromContent(
|
||||
content: Array<{ type: string; content?: string; headers?: string[]; rows?: DocxTableRow[] }>,
|
||||
options: Partial<DOCXExportOptions>
|
||||
): string {
|
||||
const DEFAULT_COLOR = '#1a56db'
|
||||
const primaryColor = options.primaryColor || DEFAULT_COLOR
|
||||
|
||||
let html = `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
body { font-family: Calibri, Arial, sans-serif; font-size: 11pt; line-height: 1.5; }
|
||||
h1 { font-size: 24pt; color: ${primaryColor}; border-bottom: 2px solid ${primaryColor}; }
|
||||
h2 { font-size: 18pt; color: ${primaryColor}; margin-top: 24pt; }
|
||||
h3 { font-size: 14pt; color: #333; margin-top: 18pt; }
|
||||
table { border-collapse: collapse; width: 100%; margin: 12pt 0; }
|
||||
th, td { border: 1px solid #ddd; padding: 8pt; text-align: left; }
|
||||
th { background-color: ${primaryColor}; color: white; }
|
||||
tr:nth-child(even) { background-color: #f9f9f9; }
|
||||
ul { margin: 6pt 0; }
|
||||
li { margin: 3pt 0; }
|
||||
.warning { color: #dc2626; font-weight: bold; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
`
|
||||
|
||||
for (const element of content) {
|
||||
if (element.type === 'table' && element.headers && element.rows) {
|
||||
html += '<table>'
|
||||
html += '<tr>'
|
||||
for (const header of element.headers) {
|
||||
html += `<th>${escapeHtml(header)}</th>`
|
||||
}
|
||||
html += '</tr>'
|
||||
for (const row of element.rows) {
|
||||
html += '<tr>'
|
||||
for (const cell of row.cells) {
|
||||
html += `<td>${escapeHtml(cell)}</td>`
|
||||
}
|
||||
html += '</tr>'
|
||||
}
|
||||
html += '</table>'
|
||||
} else {
|
||||
const tag = getHtmlTag(element.type)
|
||||
const processedContent = processContent(element.content || '')
|
||||
html += `<${tag}>${processedContent}</${tag}>\n`
|
||||
}
|
||||
}
|
||||
|
||||
html += '</body></html>'
|
||||
return html
|
||||
}
|
||||
|
||||
export function getHtmlTag(type: string): string {
|
||||
switch (type) {
|
||||
case 'heading1':
|
||||
return 'h1'
|
||||
case 'heading2':
|
||||
return 'h2'
|
||||
case 'heading3':
|
||||
return 'h3'
|
||||
case 'bullet':
|
||||
return 'li'
|
||||
default:
|
||||
return 'p'
|
||||
}
|
||||
}
|
||||
|
||||
export function escapeHtml(text: string): string {
|
||||
return text
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''')
|
||||
}
|
||||
|
||||
export function processContent(content: string): string {
|
||||
// Convert markdown-style bold to HTML
|
||||
return escapeHtml(content).replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
|
||||
}
|
||||
@@ -10,6 +10,15 @@ import {
|
||||
CONTROL_CATEGORIES,
|
||||
} from '../types'
|
||||
import { getControlById, getCategoryMetadata } from '../controls/loader'
|
||||
import {
|
||||
groupTOMsByCategory,
|
||||
formatRole,
|
||||
formatProtectionLevel,
|
||||
formatType,
|
||||
formatImplementationStatus,
|
||||
formatApplicability,
|
||||
generateHTMLFromContent,
|
||||
} from './docx-helpers'
|
||||
|
||||
// =============================================================================
|
||||
// TYPES
|
||||
@@ -320,78 +329,6 @@ export function generateDOCXContent(
|
||||
return elements
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// HELPER FUNCTIONS
|
||||
// =============================================================================
|
||||
|
||||
function groupTOMsByCategory(
|
||||
toms: DerivedTOM[],
|
||||
includeNotApplicable: boolean
|
||||
): Map<ControlCategory, DerivedTOM[]> {
|
||||
const grouped = new Map<ControlCategory, DerivedTOM[]>()
|
||||
|
||||
for (const tom of toms) {
|
||||
if (!includeNotApplicable && tom.applicability === 'NOT_APPLICABLE') {
|
||||
continue
|
||||
}
|
||||
|
||||
const control = getControlById(tom.controlId)
|
||||
if (!control) continue
|
||||
|
||||
const category = control.category
|
||||
const existing = grouped.get(category) || []
|
||||
existing.push(tom)
|
||||
grouped.set(category, existing)
|
||||
}
|
||||
|
||||
return grouped
|
||||
}
|
||||
|
||||
function formatRole(role: string, language: 'de' | 'en'): string {
|
||||
const roles: Record<string, Record<'de' | 'en', string>> = {
|
||||
CONTROLLER: { de: 'Verantwortlicher', en: 'Controller' },
|
||||
PROCESSOR: { de: 'Auftragsverarbeiter', en: 'Processor' },
|
||||
JOINT_CONTROLLER: { de: 'Gemeinsam Verantwortlicher', en: 'Joint Controller' },
|
||||
}
|
||||
return roles[role]?.[language] || role
|
||||
}
|
||||
|
||||
function formatProtectionLevel(level: string, language: 'de' | 'en'): string {
|
||||
const levels: Record<string, Record<'de' | 'en', string>> = {
|
||||
NORMAL: { de: 'Normal', en: 'Normal' },
|
||||
HIGH: { de: 'Hoch', en: 'High' },
|
||||
VERY_HIGH: { de: 'Sehr hoch', en: 'Very High' },
|
||||
}
|
||||
return levels[level]?.[language] || level
|
||||
}
|
||||
|
||||
function formatType(type: string, language: 'de' | 'en'): string {
|
||||
const types: Record<string, Record<'de' | 'en', string>> = {
|
||||
TECHNICAL: { de: 'Technisch', en: 'Technical' },
|
||||
ORGANIZATIONAL: { de: 'Organisatorisch', en: 'Organizational' },
|
||||
}
|
||||
return types[type]?.[language] || type
|
||||
}
|
||||
|
||||
function formatImplementationStatus(status: string, language: 'de' | 'en'): string {
|
||||
const statuses: Record<string, Record<'de' | 'en', string>> = {
|
||||
NOT_IMPLEMENTED: { de: 'Nicht umgesetzt', en: 'Not Implemented' },
|
||||
PARTIAL: { de: 'Teilweise umgesetzt', en: 'Partially Implemented' },
|
||||
IMPLEMENTED: { de: 'Umgesetzt', en: 'Implemented' },
|
||||
}
|
||||
return statuses[status]?.[language] || status
|
||||
}
|
||||
|
||||
function formatApplicability(applicability: string, language: 'de' | 'en'): string {
|
||||
const apps: Record<string, Record<'de' | 'en', string>> = {
|
||||
REQUIRED: { de: 'Erforderlich', en: 'Required' },
|
||||
RECOMMENDED: { de: 'Empfohlen', en: 'Recommended' },
|
||||
OPTIONAL: { de: 'Optional', en: 'Optional' },
|
||||
NOT_APPLICABLE: { de: 'Nicht anwendbar', en: 'Not Applicable' },
|
||||
}
|
||||
return apps[applicability]?.[language] || applicability
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// DOCX BLOB GENERATION
|
||||
// Uses simple XML structure compatible with docx libraries
|
||||
@@ -421,90 +358,6 @@ export async function generateDOCXBlob(
|
||||
return blob
|
||||
}
|
||||
|
||||
function generateHTMLFromContent(
|
||||
content: DocxElement[],
|
||||
options: Partial<DOCXExportOptions>
|
||||
): string {
|
||||
const opts = { ...DEFAULT_OPTIONS, ...options }
|
||||
|
||||
let html = `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
body { font-family: Calibri, Arial, sans-serif; font-size: 11pt; line-height: 1.5; }
|
||||
h1 { font-size: 24pt; color: ${opts.primaryColor}; border-bottom: 2px solid ${opts.primaryColor}; }
|
||||
h2 { font-size: 18pt; color: ${opts.primaryColor}; margin-top: 24pt; }
|
||||
h3 { font-size: 14pt; color: #333; margin-top: 18pt; }
|
||||
table { border-collapse: collapse; width: 100%; margin: 12pt 0; }
|
||||
th, td { border: 1px solid #ddd; padding: 8pt; text-align: left; }
|
||||
th { background-color: ${opts.primaryColor}; color: white; }
|
||||
tr:nth-child(even) { background-color: #f9f9f9; }
|
||||
ul { margin: 6pt 0; }
|
||||
li { margin: 3pt 0; }
|
||||
.warning { color: #dc2626; font-weight: bold; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
`
|
||||
|
||||
for (const element of content) {
|
||||
if (element.type === 'table') {
|
||||
html += '<table>'
|
||||
html += '<tr>'
|
||||
for (const header of element.headers) {
|
||||
html += `<th>${escapeHtml(header)}</th>`
|
||||
}
|
||||
html += '</tr>'
|
||||
for (const row of element.rows) {
|
||||
html += '<tr>'
|
||||
for (const cell of row.cells) {
|
||||
html += `<td>${escapeHtml(cell)}</td>`
|
||||
}
|
||||
html += '</tr>'
|
||||
}
|
||||
html += '</table>'
|
||||
} else {
|
||||
const tag = getHtmlTag(element.type)
|
||||
const processedContent = processContent(element.content)
|
||||
html += `<${tag}>${processedContent}</${tag}>\n`
|
||||
}
|
||||
}
|
||||
|
||||
html += '</body></html>'
|
||||
return html
|
||||
}
|
||||
|
||||
function getHtmlTag(type: string): string {
|
||||
switch (type) {
|
||||
case 'heading1':
|
||||
return 'h1'
|
||||
case 'heading2':
|
||||
return 'h2'
|
||||
case 'heading3':
|
||||
return 'h3'
|
||||
case 'bullet':
|
||||
return 'li'
|
||||
default:
|
||||
return 'p'
|
||||
}
|
||||
}
|
||||
|
||||
function escapeHtml(text: string): string {
|
||||
return text
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''')
|
||||
}
|
||||
|
||||
function processContent(content: string): string {
|
||||
// Convert markdown-style bold to HTML
|
||||
return escapeHtml(content).replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// FILENAME GENERATION
|
||||
// =============================================================================
|
||||
|
||||
87
admin-compliance/lib/sdk/tom-generator/export/pdf-helpers.ts
Normal file
87
admin-compliance/lib/sdk/tom-generator/export/pdf-helpers.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
// =============================================================================
|
||||
// TOM Generator PDF Export - Helper Functions
|
||||
// Private helpers extracted from pdf.ts to stay under 500 LOC hard cap
|
||||
// =============================================================================
|
||||
|
||||
import { DerivedTOM, CONTROL_CATEGORIES } from '../types'
|
||||
import { getControlById } from '../controls/loader'
|
||||
import type { PDFExportOptions } from './pdf'
|
||||
|
||||
// =============================================================================
|
||||
// HELPER FUNCTIONS
|
||||
// =============================================================================
|
||||
|
||||
export function generateCategorySummary(
|
||||
toms: DerivedTOM[],
|
||||
opts: PDFExportOptions
|
||||
): string[][] {
|
||||
const summary: string[][] = []
|
||||
|
||||
for (const category of CONTROL_CATEGORIES) {
|
||||
const categoryTOMs = toms.filter((tom) => {
|
||||
const control = getControlById(tom.controlId)
|
||||
return control?.category === category.id
|
||||
})
|
||||
|
||||
if (categoryTOMs.length === 0) continue
|
||||
|
||||
const required = categoryTOMs.filter((t) => t.applicability === 'REQUIRED').length
|
||||
const implemented = categoryTOMs.filter((t) => t.implementationStatus === 'IMPLEMENTED').length
|
||||
|
||||
summary.push([
|
||||
category.name[opts.language],
|
||||
String(categoryTOMs.length),
|
||||
String(required),
|
||||
String(implemented),
|
||||
])
|
||||
}
|
||||
|
||||
return summary
|
||||
}
|
||||
|
||||
export function formatProtectionLevel(level: string, language: 'de' | 'en'): string {
|
||||
const levels: Record<string, Record<'de' | 'en', string>> = {
|
||||
NORMAL: { de: 'Normal', en: 'Normal' },
|
||||
HIGH: { de: 'Hoch', en: 'High' },
|
||||
VERY_HIGH: { de: 'Sehr hoch', en: 'Very High' },
|
||||
}
|
||||
return levels[level]?.[language] || level
|
||||
}
|
||||
|
||||
export function formatType(type: string, language: 'de' | 'en'): string {
|
||||
const types: Record<string, Record<'de' | 'en', string>> = {
|
||||
TECHNICAL: { de: 'Technisch', en: 'Technical' },
|
||||
ORGANIZATIONAL: { de: 'Organisatorisch', en: 'Organizational' },
|
||||
}
|
||||
return types[type]?.[language] || type
|
||||
}
|
||||
|
||||
export function formatImplementationStatus(status: string, language: 'de' | 'en'): string {
|
||||
const statuses: Record<string, Record<'de' | 'en', string>> = {
|
||||
NOT_IMPLEMENTED: { de: 'Nicht umgesetzt', en: 'Not Implemented' },
|
||||
PARTIAL: { de: 'Teilweise', en: 'Partial' },
|
||||
IMPLEMENTED: { de: 'Umgesetzt', en: 'Implemented' },
|
||||
}
|
||||
return statuses[status]?.[language] || status
|
||||
}
|
||||
|
||||
export function formatApplicability(applicability: string, language: 'de' | 'en'): string {
|
||||
const apps: Record<string, Record<'de' | 'en', string>> = {
|
||||
REQUIRED: { de: 'Erforderlich', en: 'Required' },
|
||||
RECOMMENDED: { de: 'Empfohlen', en: 'Recommended' },
|
||||
OPTIONAL: { de: 'Optional', en: 'Optional' },
|
||||
NOT_APPLICABLE: { de: 'N/A', en: 'N/A' },
|
||||
}
|
||||
return apps[applicability]?.[language] || applicability
|
||||
}
|
||||
|
||||
export function getCIAMeaning(rating: number, language: 'de' | 'en'): string {
|
||||
const meanings: Record<number, Record<'de' | 'en', string>> = {
|
||||
1: { de: 'Sehr gering', en: 'Very Low' },
|
||||
2: { de: 'Gering', en: 'Low' },
|
||||
3: { de: 'Mittel', en: 'Medium' },
|
||||
4: { de: 'Hoch', en: 'High' },
|
||||
5: { de: 'Sehr hoch', en: 'Very High' },
|
||||
}
|
||||
return meanings[rating]?.[language] || String(rating)
|
||||
}
|
||||
@@ -9,6 +9,14 @@ import {
|
||||
CONTROL_CATEGORIES,
|
||||
} from '../types'
|
||||
import { getControlById } from '../controls/loader'
|
||||
import {
|
||||
generateCategorySummary,
|
||||
formatProtectionLevel,
|
||||
formatType,
|
||||
formatImplementationStatus,
|
||||
formatApplicability,
|
||||
getCIAMeaning,
|
||||
} from './pdf-helpers'
|
||||
|
||||
// =============================================================================
|
||||
// TYPES
|
||||
@@ -369,85 +377,6 @@ export function generatePDFContent(
|
||||
return sections
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// HELPER FUNCTIONS
|
||||
// =============================================================================
|
||||
|
||||
function generateCategorySummary(
|
||||
toms: DerivedTOM[],
|
||||
opts: PDFExportOptions
|
||||
): string[][] {
|
||||
const summary: string[][] = []
|
||||
|
||||
for (const category of CONTROL_CATEGORIES) {
|
||||
const categoryTOMs = toms.filter((tom) => {
|
||||
const control = getControlById(tom.controlId)
|
||||
return control?.category === category.id
|
||||
})
|
||||
|
||||
if (categoryTOMs.length === 0) continue
|
||||
|
||||
const required = categoryTOMs.filter((t) => t.applicability === 'REQUIRED').length
|
||||
const implemented = categoryTOMs.filter((t) => t.implementationStatus === 'IMPLEMENTED').length
|
||||
|
||||
summary.push([
|
||||
category.name[opts.language],
|
||||
String(categoryTOMs.length),
|
||||
String(required),
|
||||
String(implemented),
|
||||
])
|
||||
}
|
||||
|
||||
return summary
|
||||
}
|
||||
|
||||
function formatProtectionLevel(level: string, language: 'de' | 'en'): string {
|
||||
const levels: Record<string, Record<'de' | 'en', string>> = {
|
||||
NORMAL: { de: 'Normal', en: 'Normal' },
|
||||
HIGH: { de: 'Hoch', en: 'High' },
|
||||
VERY_HIGH: { de: 'Sehr hoch', en: 'Very High' },
|
||||
}
|
||||
return levels[level]?.[language] || level
|
||||
}
|
||||
|
||||
function formatType(type: string, language: 'de' | 'en'): string {
|
||||
const types: Record<string, Record<'de' | 'en', string>> = {
|
||||
TECHNICAL: { de: 'Technisch', en: 'Technical' },
|
||||
ORGANIZATIONAL: { de: 'Organisatorisch', en: 'Organizational' },
|
||||
}
|
||||
return types[type]?.[language] || type
|
||||
}
|
||||
|
||||
function formatImplementationStatus(status: string, language: 'de' | 'en'): string {
|
||||
const statuses: Record<string, Record<'de' | 'en', string>> = {
|
||||
NOT_IMPLEMENTED: { de: 'Nicht umgesetzt', en: 'Not Implemented' },
|
||||
PARTIAL: { de: 'Teilweise', en: 'Partial' },
|
||||
IMPLEMENTED: { de: 'Umgesetzt', en: 'Implemented' },
|
||||
}
|
||||
return statuses[status]?.[language] || status
|
||||
}
|
||||
|
||||
function formatApplicability(applicability: string, language: 'de' | 'en'): string {
|
||||
const apps: Record<string, Record<'de' | 'en', string>> = {
|
||||
REQUIRED: { de: 'Erforderlich', en: 'Required' },
|
||||
RECOMMENDED: { de: 'Empfohlen', en: 'Recommended' },
|
||||
OPTIONAL: { de: 'Optional', en: 'Optional' },
|
||||
NOT_APPLICABLE: { de: 'N/A', en: 'N/A' },
|
||||
}
|
||||
return apps[applicability]?.[language] || applicability
|
||||
}
|
||||
|
||||
function getCIAMeaning(rating: number, language: 'de' | 'en'): string {
|
||||
const meanings: Record<number, Record<'de' | 'en', string>> = {
|
||||
1: { de: 'Sehr gering', en: 'Very Low' },
|
||||
2: { de: 'Gering', en: 'Low' },
|
||||
3: { de: 'Mittel', en: 'Medium' },
|
||||
4: { de: 'Hoch', en: 'High' },
|
||||
5: { de: 'Sehr hoch', en: 'Very High' },
|
||||
}
|
||||
return meanings[rating]?.[language] || String(rating)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// PDF BLOB GENERATION
|
||||
// Note: For production, use jspdf or pdfmake library
|
||||
|
||||
219
admin-compliance/lib/sdk/tom-generator/export/zip-helpers.ts
Normal file
219
admin-compliance/lib/sdk/tom-generator/export/zip-helpers.ts
Normal file
@@ -0,0 +1,219 @@
|
||||
// =============================================================================
|
||||
// TOM Generator ZIP Export - Helper Functions
|
||||
// Private helpers extracted from zip.ts to stay under 500 LOC hard cap
|
||||
// =============================================================================
|
||||
|
||||
import { TOMGeneratorState, DerivedTOM } from '../types'
|
||||
import { getControlById } from '../controls/loader'
|
||||
import type { ZIPExportOptions } from './zip'
|
||||
|
||||
// =============================================================================
|
||||
// HELPER FUNCTIONS
|
||||
// =============================================================================
|
||||
|
||||
export function generateReadme(
|
||||
state: TOMGeneratorState,
|
||||
opts: ZIPExportOptions
|
||||
): string {
|
||||
const date = new Date().toISOString().split('T')[0]
|
||||
const lang = opts.language
|
||||
|
||||
return `# TOM Export Package
|
||||
|
||||
${lang === 'de' ? 'Exportiert am' : 'Exported on'}: ${date}
|
||||
${lang === 'de' ? 'Unternehmen' : 'Company'}: ${state.companyProfile?.name || 'N/A'}
|
||||
|
||||
## ${lang === 'de' ? 'Inhalt' : 'Contents'}
|
||||
|
||||
### /data
|
||||
- **profiles/** - ${lang === 'de' ? 'Profilinformationen (Unternehmen, Daten, Architektur, Sicherheit, Risiko)' : 'Profile information (company, data, architecture, security, risk)'}
|
||||
- **toms/** - ${lang === 'de' ? 'Abgeleitete TOMs und Zusammenfassungen' : 'Derived TOMs and summaries'}
|
||||
- **evidence/** - ${lang === 'de' ? 'Nachweisdokumente und Zuordnungen' : 'Evidence documents and mappings'}
|
||||
- **gap-analysis/** - ${lang === 'de' ? 'Lückenanalyse und Empfehlungen' : 'Gap analysis and recommendations'}
|
||||
|
||||
### /reference
|
||||
- **control-library/** - ${lang === 'de' ? 'Kontrollbibliothek mit allen 60+ Kontrollen' : 'Control library with all 60+ controls'}
|
||||
|
||||
### /documents
|
||||
- **tom-summary.md** - ${lang === 'de' ? 'Zusammenfassung als Markdown' : 'Summary as Markdown'}
|
||||
- **toms.csv** - ${lang === 'de' ? 'CSV für Tabellenimport' : 'CSV for spreadsheet import'}
|
||||
|
||||
## ${lang === 'de' ? 'Statistiken' : 'Statistics'}
|
||||
|
||||
- ${lang === 'de' ? 'Gesamtzahl TOMs' : 'Total TOMs'}: ${state.derivedTOMs.length}
|
||||
- ${lang === 'de' ? 'Erforderlich' : 'Required'}: ${state.derivedTOMs.filter((t) => t.applicability === 'REQUIRED').length}
|
||||
- ${lang === 'de' ? 'Umgesetzt' : 'Implemented'}: ${state.derivedTOMs.filter((t) => t.implementationStatus === 'IMPLEMENTED').length}
|
||||
- ${lang === 'de' ? 'Schutzbedarf' : 'Protection Level'}: ${state.riskProfile?.protectionLevel || 'N/A'}
|
||||
${state.gapAnalysis ? `- ${lang === 'de' ? 'Compliance Score' : 'Compliance Score'}: ${state.gapAnalysis.overallScore}%` : ''}
|
||||
|
||||
---
|
||||
|
||||
${lang === 'de' ? 'Generiert mit dem TOM Generator' : 'Generated with TOM Generator'}
|
||||
`
|
||||
}
|
||||
|
||||
export function groupTOMsByCategory(
|
||||
toms: DerivedTOM[]
|
||||
): Map<string, DerivedTOM[]> {
|
||||
const grouped = new Map<string, DerivedTOM[]>()
|
||||
|
||||
for (const tom of toms) {
|
||||
const control = getControlById(tom.controlId)
|
||||
if (!control) continue
|
||||
|
||||
const category = control.category
|
||||
const existing: DerivedTOM[] = grouped.get(category) || []
|
||||
existing.push(tom)
|
||||
grouped.set(category, existing)
|
||||
}
|
||||
|
||||
return grouped
|
||||
}
|
||||
|
||||
export function generateImplementationSummary(
|
||||
toms: Array<{ implementationStatus: string; applicability: string }>
|
||||
): Record<string, number> {
|
||||
return {
|
||||
total: toms.length,
|
||||
required: toms.filter((t) => t.applicability === 'REQUIRED').length,
|
||||
recommended: toms.filter((t) => t.applicability === 'RECOMMENDED').length,
|
||||
optional: toms.filter((t) => t.applicability === 'OPTIONAL').length,
|
||||
notApplicable: toms.filter((t) => t.applicability === 'NOT_APPLICABLE').length,
|
||||
implemented: toms.filter((t) => t.implementationStatus === 'IMPLEMENTED').length,
|
||||
partial: toms.filter((t) => t.implementationStatus === 'PARTIAL').length,
|
||||
notImplemented: toms.filter((t) => t.implementationStatus === 'NOT_IMPLEMENTED').length,
|
||||
}
|
||||
}
|
||||
|
||||
export function groupEvidenceByControl(
|
||||
documents: Array<{ id: string; linkedControlIds: string[] }>
|
||||
): Map<string, string[]> {
|
||||
const grouped = new Map<string, string[]>()
|
||||
|
||||
for (const doc of documents) {
|
||||
for (const controlId of doc.linkedControlIds) {
|
||||
const existing = grouped.get(controlId) || []
|
||||
existing.push(doc.id)
|
||||
grouped.set(controlId, existing)
|
||||
}
|
||||
}
|
||||
|
||||
return grouped
|
||||
}
|
||||
|
||||
export function generateRecommendationsMarkdown(
|
||||
recommendations: string[],
|
||||
language: 'de' | 'en'
|
||||
): string {
|
||||
const title = language === 'de' ? 'Empfehlungen' : 'Recommendations'
|
||||
|
||||
return `# ${title}
|
||||
|
||||
${recommendations.map((rec, i) => `${i + 1}. ${rec}`).join('\n\n')}
|
||||
|
||||
---
|
||||
|
||||
${language === 'de' ? 'Generiert am' : 'Generated on'} ${new Date().toISOString().split('T')[0]}
|
||||
`
|
||||
}
|
||||
|
||||
export function generateMarkdownSummary(
|
||||
state: TOMGeneratorState,
|
||||
opts: ZIPExportOptions
|
||||
): string {
|
||||
const lang = opts.language
|
||||
const date = new Date().toLocaleDateString(lang === 'de' ? 'de-DE' : 'en-US')
|
||||
|
||||
let md = `# ${lang === 'de' ? 'Technische und Organisatorische Maßnahmen' : 'Technical and Organizational Measures'}
|
||||
|
||||
**${lang === 'de' ? 'Unternehmen' : 'Company'}:** ${state.companyProfile?.name || 'N/A'}
|
||||
**${lang === 'de' ? 'Stand' : 'Date'}:** ${date}
|
||||
**${lang === 'de' ? 'Schutzbedarf' : 'Protection Level'}:** ${state.riskProfile?.protectionLevel || 'N/A'}
|
||||
|
||||
## ${lang === 'de' ? 'Zusammenfassung' : 'Summary'}
|
||||
|
||||
| ${lang === 'de' ? 'Metrik' : 'Metric'} | ${lang === 'de' ? 'Wert' : 'Value'} |
|
||||
|--------|-------|
|
||||
| ${lang === 'de' ? 'Gesamtzahl TOMs' : 'Total TOMs'} | ${state.derivedTOMs.length} |
|
||||
| ${lang === 'de' ? 'Erforderlich' : 'Required'} | ${state.derivedTOMs.filter((t) => t.applicability === 'REQUIRED').length} |
|
||||
| ${lang === 'de' ? 'Umgesetzt' : 'Implemented'} | ${state.derivedTOMs.filter((t) => t.implementationStatus === 'IMPLEMENTED').length} |
|
||||
| ${lang === 'de' ? 'Teilweise umgesetzt' : 'Partially Implemented'} | ${state.derivedTOMs.filter((t) => t.implementationStatus === 'PARTIAL').length} |
|
||||
| ${lang === 'de' ? 'Nicht umgesetzt' : 'Not Implemented'} | ${state.derivedTOMs.filter((t) => t.implementationStatus === 'NOT_IMPLEMENTED').length} |
|
||||
|
||||
`
|
||||
|
||||
if (state.gapAnalysis) {
|
||||
md += `
|
||||
## ${lang === 'de' ? 'Compliance Score' : 'Compliance Score'}
|
||||
|
||||
**${state.gapAnalysis.overallScore}%**
|
||||
|
||||
`
|
||||
}
|
||||
|
||||
// Add required TOMs table
|
||||
const requiredTOMs = state.derivedTOMs.filter(
|
||||
(t) => t.applicability === 'REQUIRED'
|
||||
)
|
||||
|
||||
if (requiredTOMs.length > 0) {
|
||||
md += `
|
||||
## ${lang === 'de' ? 'Erforderliche Maßnahmen' : 'Required Measures'}
|
||||
|
||||
| ID | ${lang === 'de' ? 'Maßnahme' : 'Measure'} | Status |
|
||||
|----|----------|--------|
|
||||
${requiredTOMs.map((tom) => `| ${tom.controlId} | ${tom.name} | ${formatStatus(tom.implementationStatus, lang)} |`).join('\n')}
|
||||
|
||||
`
|
||||
}
|
||||
|
||||
return md
|
||||
}
|
||||
|
||||
export function generateCSV(
|
||||
toms: Array<{
|
||||
controlId: string
|
||||
name: string
|
||||
description: string
|
||||
applicability: string
|
||||
implementationStatus: string
|
||||
responsiblePerson: string | null
|
||||
}>,
|
||||
opts: ZIPExportOptions
|
||||
): string {
|
||||
const lang = opts.language
|
||||
|
||||
const headers = lang === 'de'
|
||||
? ['ID', 'Name', 'Beschreibung', 'Anwendbarkeit', 'Status', 'Verantwortlich']
|
||||
: ['ID', 'Name', 'Description', 'Applicability', 'Status', 'Responsible']
|
||||
|
||||
const rows = toms.map((tom) => [
|
||||
tom.controlId,
|
||||
escapeCSV(tom.name),
|
||||
escapeCSV(tom.description),
|
||||
tom.applicability,
|
||||
tom.implementationStatus,
|
||||
tom.responsiblePerson || '',
|
||||
])
|
||||
|
||||
return [
|
||||
headers.join(','),
|
||||
...rows.map((row) => row.join(',')),
|
||||
].join('\n')
|
||||
}
|
||||
|
||||
export function escapeCSV(value: string): string {
|
||||
if (value.includes(',') || value.includes('"') || value.includes('\n')) {
|
||||
return `"${value.replace(/"/g, '""')}"`
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
export function formatStatus(status: string, lang: 'de' | 'en'): string {
|
||||
const statuses: Record<string, Record<'de' | 'en', string>> = {
|
||||
NOT_IMPLEMENTED: { de: 'Nicht umgesetzt', en: 'Not Implemented' },
|
||||
PARTIAL: { de: 'Teilweise', en: 'Partial' },
|
||||
IMPLEMENTED: { de: 'Umgesetzt', en: 'Implemented' },
|
||||
}
|
||||
return statuses[status]?.[lang] || status
|
||||
}
|
||||
@@ -6,7 +6,16 @@
|
||||
import { TOMGeneratorState, DerivedTOM, EvidenceDocument } from '../types'
|
||||
import { generateDOCXContent, DOCXExportOptions } from './docx'
|
||||
import { generatePDFContent, PDFExportOptions } from './pdf'
|
||||
import { getControlById, getAllControls, getLibraryMetadata } from '../controls/loader'
|
||||
import { getAllControls, getLibraryMetadata } from '../controls/loader'
|
||||
import {
|
||||
generateReadme,
|
||||
groupTOMsByCategory,
|
||||
generateImplementationSummary,
|
||||
groupEvidenceByControl,
|
||||
generateRecommendationsMarkdown,
|
||||
generateMarkdownSummary,
|
||||
generateCSV,
|
||||
} from './zip-helpers'
|
||||
|
||||
// =============================================================================
|
||||
// TYPES
|
||||
@@ -267,217 +276,6 @@ export function generateZIPFiles(
|
||||
return files
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// HELPER FUNCTIONS
|
||||
// =============================================================================
|
||||
|
||||
function generateReadme(
|
||||
state: TOMGeneratorState,
|
||||
opts: ZIPExportOptions
|
||||
): string {
|
||||
const date = new Date().toISOString().split('T')[0]
|
||||
const lang = opts.language
|
||||
|
||||
return `# TOM Export Package
|
||||
|
||||
${lang === 'de' ? 'Exportiert am' : 'Exported on'}: ${date}
|
||||
${lang === 'de' ? 'Unternehmen' : 'Company'}: ${state.companyProfile?.name || 'N/A'}
|
||||
|
||||
## ${lang === 'de' ? 'Inhalt' : 'Contents'}
|
||||
|
||||
### /data
|
||||
- **profiles/** - ${lang === 'de' ? 'Profilinformationen (Unternehmen, Daten, Architektur, Sicherheit, Risiko)' : 'Profile information (company, data, architecture, security, risk)'}
|
||||
- **toms/** - ${lang === 'de' ? 'Abgeleitete TOMs und Zusammenfassungen' : 'Derived TOMs and summaries'}
|
||||
- **evidence/** - ${lang === 'de' ? 'Nachweisdokumente und Zuordnungen' : 'Evidence documents and mappings'}
|
||||
- **gap-analysis/** - ${lang === 'de' ? 'Lückenanalyse und Empfehlungen' : 'Gap analysis and recommendations'}
|
||||
|
||||
### /reference
|
||||
- **control-library/** - ${lang === 'de' ? 'Kontrollbibliothek mit allen 60+ Kontrollen' : 'Control library with all 60+ controls'}
|
||||
|
||||
### /documents
|
||||
- **tom-summary.md** - ${lang === 'de' ? 'Zusammenfassung als Markdown' : 'Summary as Markdown'}
|
||||
- **toms.csv** - ${lang === 'de' ? 'CSV für Tabellenimport' : 'CSV for spreadsheet import'}
|
||||
|
||||
## ${lang === 'de' ? 'Statistiken' : 'Statistics'}
|
||||
|
||||
- ${lang === 'de' ? 'Gesamtzahl TOMs' : 'Total TOMs'}: ${state.derivedTOMs.length}
|
||||
- ${lang === 'de' ? 'Erforderlich' : 'Required'}: ${state.derivedTOMs.filter((t) => t.applicability === 'REQUIRED').length}
|
||||
- ${lang === 'de' ? 'Umgesetzt' : 'Implemented'}: ${state.derivedTOMs.filter((t) => t.implementationStatus === 'IMPLEMENTED').length}
|
||||
- ${lang === 'de' ? 'Schutzbedarf' : 'Protection Level'}: ${state.riskProfile?.protectionLevel || 'N/A'}
|
||||
${state.gapAnalysis ? `- ${lang === 'de' ? 'Compliance Score' : 'Compliance Score'}: ${state.gapAnalysis.overallScore}%` : ''}
|
||||
|
||||
---
|
||||
|
||||
${lang === 'de' ? 'Generiert mit dem TOM Generator' : 'Generated with TOM Generator'}
|
||||
`
|
||||
}
|
||||
|
||||
function groupTOMsByCategory(
|
||||
toms: DerivedTOM[]
|
||||
): Map<string, DerivedTOM[]> {
|
||||
const grouped = new Map<string, DerivedTOM[]>()
|
||||
|
||||
for (const tom of toms) {
|
||||
const control = getControlById(tom.controlId)
|
||||
if (!control) continue
|
||||
|
||||
const category = control.category
|
||||
const existing: DerivedTOM[] = grouped.get(category) || []
|
||||
existing.push(tom)
|
||||
grouped.set(category, existing)
|
||||
}
|
||||
|
||||
return grouped
|
||||
}
|
||||
|
||||
function generateImplementationSummary(
|
||||
toms: Array<{ implementationStatus: string; applicability: string }>
|
||||
): Record<string, number> {
|
||||
return {
|
||||
total: toms.length,
|
||||
required: toms.filter((t) => t.applicability === 'REQUIRED').length,
|
||||
recommended: toms.filter((t) => t.applicability === 'RECOMMENDED').length,
|
||||
optional: toms.filter((t) => t.applicability === 'OPTIONAL').length,
|
||||
notApplicable: toms.filter((t) => t.applicability === 'NOT_APPLICABLE').length,
|
||||
implemented: toms.filter((t) => t.implementationStatus === 'IMPLEMENTED').length,
|
||||
partial: toms.filter((t) => t.implementationStatus === 'PARTIAL').length,
|
||||
notImplemented: toms.filter((t) => t.implementationStatus === 'NOT_IMPLEMENTED').length,
|
||||
}
|
||||
}
|
||||
|
||||
function groupEvidenceByControl(
|
||||
documents: Array<{ id: string; linkedControlIds: string[] }>
|
||||
): Map<string, string[]> {
|
||||
const grouped = new Map<string, string[]>()
|
||||
|
||||
for (const doc of documents) {
|
||||
for (const controlId of doc.linkedControlIds) {
|
||||
const existing = grouped.get(controlId) || []
|
||||
existing.push(doc.id)
|
||||
grouped.set(controlId, existing)
|
||||
}
|
||||
}
|
||||
|
||||
return grouped
|
||||
}
|
||||
|
||||
function generateRecommendationsMarkdown(
|
||||
recommendations: string[],
|
||||
language: 'de' | 'en'
|
||||
): string {
|
||||
const title = language === 'de' ? 'Empfehlungen' : 'Recommendations'
|
||||
|
||||
return `# ${title}
|
||||
|
||||
${recommendations.map((rec, i) => `${i + 1}. ${rec}`).join('\n\n')}
|
||||
|
||||
---
|
||||
|
||||
${language === 'de' ? 'Generiert am' : 'Generated on'} ${new Date().toISOString().split('T')[0]}
|
||||
`
|
||||
}
|
||||
|
||||
function generateMarkdownSummary(
|
||||
state: TOMGeneratorState,
|
||||
opts: ZIPExportOptions
|
||||
): string {
|
||||
const lang = opts.language
|
||||
const date = new Date().toLocaleDateString(lang === 'de' ? 'de-DE' : 'en-US')
|
||||
|
||||
let md = `# ${lang === 'de' ? 'Technische und Organisatorische Maßnahmen' : 'Technical and Organizational Measures'}
|
||||
|
||||
**${lang === 'de' ? 'Unternehmen' : 'Company'}:** ${state.companyProfile?.name || 'N/A'}
|
||||
**${lang === 'de' ? 'Stand' : 'Date'}:** ${date}
|
||||
**${lang === 'de' ? 'Schutzbedarf' : 'Protection Level'}:** ${state.riskProfile?.protectionLevel || 'N/A'}
|
||||
|
||||
## ${lang === 'de' ? 'Zusammenfassung' : 'Summary'}
|
||||
|
||||
| ${lang === 'de' ? 'Metrik' : 'Metric'} | ${lang === 'de' ? 'Wert' : 'Value'} |
|
||||
|--------|-------|
|
||||
| ${lang === 'de' ? 'Gesamtzahl TOMs' : 'Total TOMs'} | ${state.derivedTOMs.length} |
|
||||
| ${lang === 'de' ? 'Erforderlich' : 'Required'} | ${state.derivedTOMs.filter((t) => t.applicability === 'REQUIRED').length} |
|
||||
| ${lang === 'de' ? 'Umgesetzt' : 'Implemented'} | ${state.derivedTOMs.filter((t) => t.implementationStatus === 'IMPLEMENTED').length} |
|
||||
| ${lang === 'de' ? 'Teilweise umgesetzt' : 'Partially Implemented'} | ${state.derivedTOMs.filter((t) => t.implementationStatus === 'PARTIAL').length} |
|
||||
| ${lang === 'de' ? 'Nicht umgesetzt' : 'Not Implemented'} | ${state.derivedTOMs.filter((t) => t.implementationStatus === 'NOT_IMPLEMENTED').length} |
|
||||
|
||||
`
|
||||
|
||||
if (state.gapAnalysis) {
|
||||
md += `
|
||||
## ${lang === 'de' ? 'Compliance Score' : 'Compliance Score'}
|
||||
|
||||
**${state.gapAnalysis.overallScore}%**
|
||||
|
||||
`
|
||||
}
|
||||
|
||||
// Add required TOMs table
|
||||
const requiredTOMs = state.derivedTOMs.filter(
|
||||
(t) => t.applicability === 'REQUIRED'
|
||||
)
|
||||
|
||||
if (requiredTOMs.length > 0) {
|
||||
md += `
|
||||
## ${lang === 'de' ? 'Erforderliche Maßnahmen' : 'Required Measures'}
|
||||
|
||||
| ID | ${lang === 'de' ? 'Maßnahme' : 'Measure'} | Status |
|
||||
|----|----------|--------|
|
||||
${requiredTOMs.map((tom) => `| ${tom.controlId} | ${tom.name} | ${formatStatus(tom.implementationStatus, lang)} |`).join('\n')}
|
||||
|
||||
`
|
||||
}
|
||||
|
||||
return md
|
||||
}
|
||||
|
||||
function generateCSV(
|
||||
toms: Array<{
|
||||
controlId: string
|
||||
name: string
|
||||
description: string
|
||||
applicability: string
|
||||
implementationStatus: string
|
||||
responsiblePerson: string | null
|
||||
}>,
|
||||
opts: ZIPExportOptions
|
||||
): string {
|
||||
const lang = opts.language
|
||||
|
||||
const headers = lang === 'de'
|
||||
? ['ID', 'Name', 'Beschreibung', 'Anwendbarkeit', 'Status', 'Verantwortlich']
|
||||
: ['ID', 'Name', 'Description', 'Applicability', 'Status', 'Responsible']
|
||||
|
||||
const rows = toms.map((tom) => [
|
||||
tom.controlId,
|
||||
escapeCSV(tom.name),
|
||||
escapeCSV(tom.description),
|
||||
tom.applicability,
|
||||
tom.implementationStatus,
|
||||
tom.responsiblePerson || '',
|
||||
])
|
||||
|
||||
return [
|
||||
headers.join(','),
|
||||
...rows.map((row) => row.join(',')),
|
||||
].join('\n')
|
||||
}
|
||||
|
||||
function escapeCSV(value: string): string {
|
||||
if (value.includes(',') || value.includes('"') || value.includes('\n')) {
|
||||
return `"${value.replace(/"/g, '""')}"`
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
function formatStatus(status: string, lang: 'de' | 'en'): string {
|
||||
const statuses: Record<string, Record<'de' | 'en', string>> = {
|
||||
NOT_IMPLEMENTED: { de: 'Nicht umgesetzt', en: 'Not Implemented' },
|
||||
PARTIAL: { de: 'Teilweise', en: 'Partial' },
|
||||
IMPLEMENTED: { de: 'Umgesetzt', en: 'Implemented' },
|
||||
}
|
||||
return statuses[status]?.[lang] || status
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ZIP BLOB GENERATION
|
||||
// Note: For production, use jszip library
|
||||
|
||||
Reference in New Issue
Block a user