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>
This commit is contained in:
Benjamin Boenisch
2026-02-11 23:47:28 +01:00
commit 4435e7ea0a
734 changed files with 251369 additions and 0 deletions

View File

@@ -0,0 +1,121 @@
import { NextRequest, NextResponse } from 'next/server'
import { TOMDocumentAnalyzer } from '@/lib/sdk/tom-generator/ai/document-analyzer'
import { evidenceStore } from '@/lib/sdk/tom-generator/evidence-store'
/**
* TOM Generator Evidence Analysis API
*
* POST /api/sdk/v1/tom-generator/evidence/[id]/analyze - Analyze evidence document with AI
*
* Request body:
* {
* tenantId: string
* documentText?: string (if already extracted)
* }
*/
export async function POST(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const { id } = await params
const body = await request.json()
const { tenantId, documentText } = body
if (!tenantId) {
return NextResponse.json(
{ success: false, error: 'tenantId is required' },
{ status: 400 }
)
}
// Get the document
const document = await evidenceStore.getById(tenantId, id)
if (!document) {
return NextResponse.json(
{ success: false, error: `Document not found: ${id}` },
{ status: 404 }
)
}
// Check if already analyzed
if (document.aiAnalysis && document.status === 'ANALYZED') {
return NextResponse.json({
success: true,
data: document.aiAnalysis,
meta: {
alreadyAnalyzed: true,
analyzedAt: document.aiAnalysis.analyzedAt,
},
})
}
// Get document text (in production, this would be extracted from the file)
const text = documentText || `[Document content from ${document.originalName}]`
// Initialize analyzer
const analyzer = new TOMDocumentAnalyzer()
// Analyze the document
const analysisResult = await analyzer.analyzeDocument(
document,
text,
'de'
)
// Check if analysis was successful
if (!analysisResult.success || !analysisResult.analysis) {
return NextResponse.json(
{ success: false, error: analysisResult.error || 'Analysis failed' },
{ status: 500 }
)
}
const analysis = analysisResult.analysis
// Update the document with analysis results
const updatedDocument = await evidenceStore.update(tenantId, id, {
aiAnalysis: analysis,
status: 'ANALYZED',
linkedControlIds: [
...new Set([
...document.linkedControlIds,
...analysis.applicableControls,
]),
],
})
return NextResponse.json({
success: true,
data: {
analysis,
document: updatedDocument,
},
meta: {
documentId: id,
analyzedAt: analysis.analyzedAt,
confidence: analysis.confidence,
applicableControlsCount: analysis.applicableControls.length,
gapsCount: analysis.gaps.length,
},
})
} catch (error) {
console.error('Failed to analyze evidence:', error)
return NextResponse.json(
{ success: false, error: 'Failed to analyze evidence' },
{ status: 500 }
)
}
}
export async function OPTIONS() {
return NextResponse.json(
{ status: 'ok' },
{
headers: {
Allow: 'POST, OPTIONS',
},
}
)
}

View File

@@ -0,0 +1,153 @@
import { NextRequest, NextResponse } from 'next/server'
import { DocumentType } from '@/lib/sdk/tom-generator/types'
import { evidenceStore } from '@/lib/sdk/tom-generator/evidence-store'
/**
* TOM Generator Evidence API
*
* GET /api/sdk/v1/tom-generator/evidence?tenantId=xxx - List all evidence documents
* DELETE /api/sdk/v1/tom-generator/evidence?tenantId=xxx&id=xxx - Delete evidence
*/
// =============================================================================
// HANDLERS
// =============================================================================
export async function GET(request: NextRequest) {
try {
const { searchParams } = new URL(request.url)
const tenantId = searchParams.get('tenantId')
const documentType = searchParams.get('type') as DocumentType | null
const status = searchParams.get('status')
const id = searchParams.get('id')
if (!tenantId) {
return NextResponse.json(
{ success: false, error: 'tenantId is required' },
{ status: 400 }
)
}
// Get single document
if (id) {
const document = await evidenceStore.getById(tenantId, id)
if (!document) {
return NextResponse.json(
{ success: false, error: `Document not found: ${id}` },
{ status: 404 }
)
}
return NextResponse.json({
success: true,
data: document,
})
}
// Filter by type
if (documentType) {
const documents = await evidenceStore.getByType(tenantId, documentType)
return NextResponse.json({
success: true,
data: documents,
meta: {
count: documents.length,
filter: { type: documentType },
},
})
}
// Filter by status
if (status) {
const documents = await evidenceStore.getByStatus(tenantId, status)
return NextResponse.json({
success: true,
data: documents,
meta: {
count: documents.length,
filter: { status },
},
})
}
// Get all documents
const documents = await evidenceStore.getAll(tenantId)
// Group by type for summary
const byType: Record<string, number> = {}
const byStatus: Record<string, number> = {}
documents.forEach((doc) => {
byType[doc.documentType] = (byType[doc.documentType] || 0) + 1
byStatus[doc.status] = (byStatus[doc.status] || 0) + 1
})
return NextResponse.json({
success: true,
data: documents,
meta: {
count: documents.length,
byType,
byStatus,
},
})
} catch (error) {
console.error('Failed to fetch evidence:', error)
return NextResponse.json(
{ success: false, error: 'Failed to fetch evidence' },
{ status: 500 }
)
}
}
export async function DELETE(request: NextRequest) {
try {
const { searchParams } = new URL(request.url)
const tenantId = searchParams.get('tenantId')
const id = searchParams.get('id')
if (!tenantId) {
return NextResponse.json(
{ success: false, error: 'tenantId is required' },
{ status: 400 }
)
}
if (!id) {
return NextResponse.json(
{ success: false, error: 'id is required' },
{ status: 400 }
)
}
const deleted = await evidenceStore.delete(tenantId, id)
if (!deleted) {
return NextResponse.json(
{ success: false, error: `Document not found: ${id}` },
{ status: 404 }
)
}
return NextResponse.json({
success: true,
id,
deletedAt: new Date().toISOString(),
})
} catch (error) {
console.error('Failed to delete evidence:', error)
return NextResponse.json(
{ success: false, error: 'Failed to delete evidence' },
{ status: 500 }
)
}
}
export async function OPTIONS() {
return NextResponse.json(
{ status: 'ok' },
{
headers: {
Allow: 'GET, DELETE, OPTIONS',
},
}
)
}

View File

@@ -0,0 +1,155 @@
import { NextRequest, NextResponse } from 'next/server'
import { EvidenceDocument, DocumentType } from '@/lib/sdk/tom-generator/types'
import { evidenceStore } from '@/lib/sdk/tom-generator/evidence-store'
import crypto from 'crypto'
/**
* TOM Generator Evidence Upload API
*
* POST /api/sdk/v1/tom-generator/evidence/upload - Upload evidence document
*
* Request: multipart/form-data
* - file: File
* - tenantId: string
* - documentType: DocumentType
* - validFrom?: string (ISO date)
* - validUntil?: string (ISO date)
* - linkedControlIds?: string (comma-separated)
*/
// Document type detection based on filename patterns
function detectDocumentType(filename: string, mimeType: string): DocumentType {
const lower = filename.toLowerCase()
if (lower.includes('avv') || lower.includes('auftragsverarbeitung')) {
return 'AVV'
}
if (lower.includes('dpa') || lower.includes('data processing')) {
return 'DPA'
}
if (lower.includes('sla') || lower.includes('service level')) {
return 'SLA'
}
if (lower.includes('nda') || lower.includes('vertraulichkeit') || lower.includes('geheimhaltung')) {
return 'NDA'
}
if (lower.includes('policy') || lower.includes('richtlinie')) {
return 'POLICY'
}
if (lower.includes('cert') || lower.includes('zertifikat') || lower.includes('iso')) {
return 'CERTIFICATE'
}
if (lower.includes('audit') || lower.includes('prüf') || lower.includes('bericht')) {
return 'AUDIT_REPORT'
}
return 'OTHER'
}
export async function POST(request: NextRequest) {
try {
const formData = await request.formData()
const file = formData.get('file') as File | null
const tenantId = formData.get('tenantId') as string | null
const documentType = formData.get('documentType') as DocumentType | null
const validFrom = formData.get('validFrom') as string | null
const validUntil = formData.get('validUntil') as string | null
const linkedControlIdsStr = formData.get('linkedControlIds') as string | null
const uploadedBy = formData.get('uploadedBy') as string | null
if (!file) {
return NextResponse.json(
{ success: false, error: 'file is required' },
{ status: 400 }
)
}
if (!tenantId) {
return NextResponse.json(
{ success: false, error: 'tenantId is required' },
{ status: 400 }
)
}
// Read file data
const arrayBuffer = await file.arrayBuffer()
const buffer = Buffer.from(arrayBuffer)
// Generate hash for deduplication
const hash = crypto.createHash('sha256').update(buffer).digest('hex')
// Generate unique filename
const id = crypto.randomUUID()
const ext = file.name.split('.').pop() || 'bin'
const filename = `${id}.${ext}`
// Detect document type if not provided
const detectedType = detectDocumentType(file.name, file.type)
const finalDocumentType = documentType || detectedType
// Parse linked control IDs
const linkedControlIds = linkedControlIdsStr
? linkedControlIdsStr.split(',').map((s) => s.trim()).filter(Boolean)
: []
// Create evidence document
const document: EvidenceDocument = {
id,
filename,
originalName: file.name,
mimeType: file.type,
size: file.size,
uploadedAt: new Date(),
uploadedBy: uploadedBy || 'unknown',
documentType: finalDocumentType,
detectedType,
hash,
validFrom: validFrom ? new Date(validFrom) : null,
validUntil: validUntil ? new Date(validUntil) : null,
linkedControlIds,
aiAnalysis: null,
status: 'PENDING',
}
// Store the document metadata
// Note: In production, the actual file would be stored in MinIO/S3
await evidenceStore.add(tenantId, document)
return NextResponse.json({
success: true,
data: {
id: document.id,
filename: document.filename,
originalName: document.originalName,
mimeType: document.mimeType,
size: document.size,
documentType: document.documentType,
detectedType: document.detectedType,
status: document.status,
uploadedAt: document.uploadedAt.toISOString(),
},
meta: {
hash,
needsAnalysis: true,
analyzeUrl: `/api/sdk/v1/tom-generator/evidence/${id}/analyze`,
},
})
} catch (error) {
console.error('Failed to upload evidence:', error)
return NextResponse.json(
{ success: false, error: 'Failed to upload evidence' },
{ status: 500 }
)
}
}
export async function OPTIONS() {
return NextResponse.json(
{ status: 'ok' },
{
headers: {
Allow: 'POST, OPTIONS',
},
}
)
}