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:
@@ -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',
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
153
admin-compliance/app/api/sdk/v1/tom-generator/evidence/route.ts
Normal file
153
admin-compliance/app/api/sdk/v1/tom-generator/evidence/route.ts
Normal 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',
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -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',
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user