docs: Qdrant und MinIO/Object-Storage Referenzen aktualisieren
All checks were successful
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-ai-compliance (push) Successful in 35s
CI / test-python-backend-compliance (push) Successful in 32s
CI / test-python-document-crawler (push) Successful in 41s
CI / test-python-dsms-gateway (push) Successful in 19s

- Qdrant: lokaler Container → qdrant-dev.breakpilot.ai (gehostet, API-Key)
- MinIO: bp-core-minio → Hetzner Object Storage (nbg1.your-objectstorage.com)
- CLAUDE.md, MkDocs, ARCHITECTURE.md, training.md, ci-cd-pipeline.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-03-06 20:18:29 +01:00
parent 6a940344c2
commit 8742cb7f5a
21 changed files with 1974 additions and 643 deletions

View File

@@ -0,0 +1,122 @@
/**
* Vendor Compliance API Proxy - Catch-all route
* Proxies all /api/sdk/v1/vendor-compliance/* requests to backend-compliance
*
* Backend routes: vendors, contracts, findings, control-instances, controls, export
* All under /api/compliance/vendor-compliance/ prefix on backend-compliance:8002
*/
import { NextRequest, NextResponse } from 'next/server'
const BACKEND_URL = process.env.BACKEND_URL || 'http://backend-compliance:8002'
async function proxyRequest(
request: NextRequest,
pathSegments: string[] | undefined,
method: string
) {
const pathStr = pathSegments?.join('/') || ''
const searchParams = request.nextUrl.searchParams.toString()
const basePath = `${BACKEND_URL}/api/compliance/vendor-compliance`
const url = pathStr
? `${basePath}/${pathStr}${searchParams ? `?${searchParams}` : ''}`
: `${basePath}${searchParams ? `?${searchParams}` : ''}`
try {
const headers: HeadersInit = {
'Content-Type': 'application/json',
}
const headerNames = ['authorization', 'x-namespace-id', 'x-tenant-slug']
for (const name of headerNames) {
const value = request.headers.get(name)
if (value) {
headers[name] = value
}
}
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
const clientUserId = request.headers.get('x-user-id')
const clientTenantId = request.headers.get('x-tenant-id')
headers['X-User-ID'] = (clientUserId && uuidRegex.test(clientUserId)) ? clientUserId : '00000000-0000-0000-0000-000000000001'
headers['X-Tenant-ID'] = (clientTenantId && uuidRegex.test(clientTenantId)) ? clientTenantId : (process.env.DEFAULT_TENANT_ID || '9282a473-5c95-4b3a-bf78-0ecc0ec71d3e')
const fetchOptions: RequestInit = {
method,
headers,
signal: AbortSignal.timeout(60000),
}
if (method === 'POST' || method === 'PUT' || method === 'PATCH') {
const body = await request.text()
if (body) {
fetchOptions.body = body
}
}
const response = await fetch(url, fetchOptions)
if (!response.ok) {
const errorText = await response.text()
let errorJson
try {
errorJson = JSON.parse(errorText)
} catch {
errorJson = { error: errorText }
}
return NextResponse.json(
{ error: `Backend Error: ${response.status}`, ...errorJson },
{ status: response.status }
)
}
const data = await response.json()
return NextResponse.json(data)
} catch (error) {
console.error('Vendor Compliance API proxy error:', error)
return NextResponse.json(
{ error: 'Verbindung zum Compliance Backend fehlgeschlagen' },
{ status: 503 }
)
}
}
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ path?: string[] }> }
) {
const { path } = await params
return proxyRequest(request, path, 'GET')
}
export async function POST(
request: NextRequest,
{ params }: { params: Promise<{ path?: string[] }> }
) {
const { path } = await params
return proxyRequest(request, path, 'POST')
}
export async function PUT(
request: NextRequest,
{ params }: { params: Promise<{ path?: string[] }> }
) {
const { path } = await params
return proxyRequest(request, path, 'PUT')
}
export async function PATCH(
request: NextRequest,
{ params }: { params: Promise<{ path?: string[] }> }
) {
const { path } = await params
return proxyRequest(request, path, 'PATCH')
}
export async function DELETE(
request: NextRequest,
{ params }: { params: Promise<{ path?: string[] }> }
) {
const { path } = await params
return proxyRequest(request, path, 'DELETE')
}

View File

@@ -1,88 +0,0 @@
import { NextRequest, NextResponse } from 'next/server'
import { v4 as uuidv4 } from 'uuid'
import { ContractDocument } from '@/lib/sdk/vendor-compliance'
// In-memory storage for demo purposes
const contracts: Map<string, ContractDocument> = new Map()
export async function GET(request: NextRequest) {
try {
const contractList = Array.from(contracts.values())
return NextResponse.json({
success: true,
data: contractList,
timestamp: new Date().toISOString(),
})
} catch (error) {
console.error('Error fetching contracts:', error)
return NextResponse.json(
{ success: false, error: 'Failed to fetch contracts' },
{ status: 500 }
)
}
}
export async function POST(request: NextRequest) {
try {
// Handle multipart form data for file upload
const formData = await request.formData()
const file = formData.get('file') as File | null
const vendorId = formData.get('vendorId') as string
const metadataStr = formData.get('metadata') as string
if (!file || !vendorId) {
return NextResponse.json(
{ success: false, error: 'File and vendorId are required' },
{ status: 400 }
)
}
const metadata = metadataStr ? JSON.parse(metadataStr) : {}
const id = uuidv4()
// In production, upload file to storage (MinIO, S3, etc.)
const storagePath = `contracts/${id}/${file.name}`
const contract: ContractDocument = {
id,
tenantId: 'default',
vendorId,
fileName: `${id}-${file.name}`,
originalName: file.name,
mimeType: file.type,
fileSize: file.size,
storagePath,
documentType: metadata.documentType || 'OTHER',
version: metadata.version || '1.0',
previousVersionId: metadata.previousVersionId,
parties: metadata.parties,
effectiveDate: metadata.effectiveDate ? new Date(metadata.effectiveDate) : undefined,
expirationDate: metadata.expirationDate ? new Date(metadata.expirationDate) : undefined,
autoRenewal: metadata.autoRenewal,
renewalNoticePeriod: metadata.renewalNoticePeriod,
terminationNoticePeriod: metadata.terminationNoticePeriod,
reviewStatus: 'PENDING',
status: 'DRAFT',
createdAt: new Date(),
updatedAt: new Date(),
}
contracts.set(id, contract)
return NextResponse.json(
{
success: true,
data: contract,
timestamp: new Date().toISOString(),
},
{ status: 201 }
)
} catch (error) {
console.error('Error uploading contract:', error)
return NextResponse.json(
{ success: false, error: 'Failed to upload contract' },
{ status: 500 }
)
}
}

View File

@@ -1,28 +0,0 @@
import { NextRequest, NextResponse } from 'next/server'
import { CONTROLS_LIBRARY } from '@/lib/sdk/vendor-compliance'
export async function GET(request: NextRequest) {
try {
const searchParams = request.nextUrl.searchParams
const domain = searchParams.get('domain')
let controls = [...CONTROLS_LIBRARY]
// Filter by domain if provided
if (domain) {
controls = controls.filter((c) => c.domain === domain)
}
return NextResponse.json({
success: true,
data: controls,
timestamp: new Date().toISOString(),
})
} catch (error) {
console.error('Error fetching controls:', error)
return NextResponse.json(
{ success: false, error: 'Failed to fetch controls' },
{ status: 500 }
)
}
}

View File

@@ -1,75 +0,0 @@
import { NextRequest, NextResponse } from 'next/server'
/**
* GET /api/sdk/v1/vendor-compliance/export/[reportId]/download
*
* Download a generated report file.
* In production, this would redirect to a signed MinIO/S3 URL or stream the file.
*/
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ reportId: string }> }
) {
const { reportId } = await params
// TODO: Implement actual file download
// This would typically:
// 1. Verify report exists and user has access
// 2. Generate signed URL for MinIO/S3
// 3. Redirect to signed URL or stream file
// For now, return a placeholder PDF
const placeholderContent = `
%PDF-1.4
1 0 obj
<< /Type /Catalog /Pages 2 0 R >>
endobj
2 0 obj
<< /Type /Pages /Kids [3 0 R] /Count 1 >>
endobj
3 0 obj
<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents 4 0 R /Resources << /Font << /F1 5 0 R >> >> >>
endobj
4 0 obj
<< /Length 200 >>
stream
BT
/F1 24 Tf
100 700 Td
(Vendor Compliance Report) Tj
/F1 12 Tf
100 650 Td
(Report ID: ${reportId}) Tj
100 620 Td
(Generated: ${new Date().toISOString()}) Tj
100 580 Td
(This is a placeholder. Implement actual report generation.) Tj
ET
endstream
endobj
5 0 obj
<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>
endobj
xref
0 6
0000000000 65535 f
0000000009 00000 n
0000000058 00000 n
0000000115 00000 n
0000000266 00000 n
0000000519 00000 n
trailer
<< /Size 6 /Root 1 0 R >>
startxref
598
%%EOF
`.trim()
// Return as PDF
return new NextResponse(placeholderContent, {
headers: {
'Content-Type': 'application/pdf',
'Content-Disposition': `attachment; filename="Report_${reportId.slice(0, 8)}.pdf"`,
},
})
}

View File

@@ -1,44 +0,0 @@
import { NextRequest, NextResponse } from 'next/server'
/**
* GET /api/sdk/v1/vendor-compliance/export/[reportId]
*
* Get report metadata by ID.
*/
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ reportId: string }> }
) {
const { reportId } = await params
// TODO: Fetch report metadata from database
// For now, return mock data
return NextResponse.json({
id: reportId,
status: 'completed',
filename: `Report_${reportId.slice(0, 8)}.pdf`,
generatedAt: new Date().toISOString(),
expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(), // 24h
})
}
/**
* DELETE /api/sdk/v1/vendor-compliance/export/[reportId]
*
* Delete a generated report.
*/
export async function DELETE(
request: NextRequest,
{ params }: { params: Promise<{ reportId: string }> }
) {
const { reportId } = await params
// TODO: Delete report from storage and database
console.log('Deleting report:', reportId)
return NextResponse.json({
success: true,
deletedId: reportId,
})
}

View File

@@ -1,118 +0,0 @@
import { NextRequest, NextResponse } from 'next/server'
import { v4 as uuidv4 } from 'uuid'
/**
* POST /api/sdk/v1/vendor-compliance/export
*
* Generate and export reports in various formats.
* Currently returns mock data - integrate with actual report generation service.
*/
interface ExportConfig {
reportType: 'VVT_EXPORT' | 'VENDOR_AUDIT' | 'ROPA' | 'MANAGEMENT_SUMMARY' | 'DPIA_INPUT'
format: 'PDF' | 'DOCX' | 'XLSX' | 'JSON'
scope: {
vendorIds: string[]
processingActivityIds: string[]
includeFindings: boolean
includeControls: boolean
includeRiskAssessment: boolean
dateRange?: {
from: string
to: string
}
}
}
const REPORT_TYPE_NAMES: Record<ExportConfig['reportType'], string> = {
VVT_EXPORT: 'Verarbeitungsverzeichnis',
VENDOR_AUDIT: 'Vendor-Audit-Pack',
ROPA: 'RoPA',
MANAGEMENT_SUMMARY: 'Management-Summary',
DPIA_INPUT: 'DSFA-Input',
}
const FORMAT_EXTENSIONS: Record<ExportConfig['format'], string> = {
PDF: 'pdf',
DOCX: 'docx',
XLSX: 'xlsx',
JSON: 'json',
}
export async function POST(request: NextRequest) {
try {
const config = (await request.json()) as ExportConfig
// Validate request
if (!config.reportType || !config.format) {
return NextResponse.json(
{ error: 'reportType and format are required' },
{ status: 400 }
)
}
// Generate report ID and filename
const reportId = uuidv4()
const timestamp = new Date().toISOString().slice(0, 10).replace(/-/g, '')
const filename = `${REPORT_TYPE_NAMES[config.reportType]}_${timestamp}.${FORMAT_EXTENSIONS[config.format]}`
// TODO: Implement actual report generation
// This would typically:
// 1. Fetch data from database based on scope
// 2. Generate report using template engine (e.g., docx-templates, pdfkit)
// 3. Store in MinIO/S3
// 4. Return download URL
// Mock implementation - simulate processing time
await new Promise((resolve) => setTimeout(resolve, 500))
// In production, this would be a signed URL to MinIO/S3
const downloadUrl = `/api/sdk/v1/vendor-compliance/export/${reportId}/download`
// Log export for audit trail
console.log('Export generated:', {
reportId,
reportType: config.reportType,
format: config.format,
scope: config.scope,
filename,
generatedAt: new Date().toISOString(),
})
return NextResponse.json({
id: reportId,
reportType: config.reportType,
format: config.format,
filename,
downloadUrl,
generatedAt: new Date().toISOString(),
scope: {
vendorCount: config.scope.vendorIds?.length || 0,
activityCount: config.scope.processingActivityIds?.length || 0,
includesFindings: config.scope.includeFindings,
includesControls: config.scope.includeControls,
includesRiskAssessment: config.scope.includeRiskAssessment,
},
})
} catch (error) {
console.error('Export error:', error)
return NextResponse.json(
{ error: 'Failed to generate export' },
{ status: 500 }
)
}
}
/**
* GET /api/sdk/v1/vendor-compliance/export
*
* List recent exports for the current tenant.
*/
export async function GET() {
// TODO: Implement fetching recent exports from database
// For now, return empty list
return NextResponse.json({
exports: [],
totalCount: 0,
})
}

View File

@@ -1,43 +0,0 @@
import { NextRequest, NextResponse } from 'next/server'
import { Finding } from '@/lib/sdk/vendor-compliance'
// In-memory storage for demo purposes
const findings: Map<string, Finding> = new Map()
export async function GET(request: NextRequest) {
try {
const searchParams = request.nextUrl.searchParams
const vendorId = searchParams.get('vendorId')
const contractId = searchParams.get('contractId')
const status = searchParams.get('status')
let findingsList = Array.from(findings.values())
// Filter by vendor
if (vendorId) {
findingsList = findingsList.filter((f) => f.vendorId === vendorId)
}
// Filter by contract
if (contractId) {
findingsList = findingsList.filter((f) => f.contractId === contractId)
}
// Filter by status
if (status) {
findingsList = findingsList.filter((f) => f.status === status)
}
return NextResponse.json({
success: true,
data: findingsList,
timestamp: new Date().toISOString(),
})
} catch (error) {
console.error('Error fetching findings:', error)
return NextResponse.json(
{ success: false, error: 'Failed to fetch findings' },
{ status: 500 }
)
}
}

View File

@@ -1,70 +0,0 @@
import { NextRequest, NextResponse } from 'next/server'
// This would reference the same storage as the main route
// In production, this would be database calls
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const { id } = await params
// In production, fetch from database
return NextResponse.json({
success: true,
data: null, // Would return the activity
timestamp: new Date().toISOString(),
})
} catch (error) {
console.error('Error fetching processing activity:', error)
return NextResponse.json(
{ success: false, error: 'Failed to fetch processing activity' },
{ status: 500 }
)
}
}
export async function PUT(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const { id } = await params
const body = await request.json()
// In production, update in database
return NextResponse.json({
success: true,
data: { id, ...body, updatedAt: new Date() },
timestamp: new Date().toISOString(),
})
} catch (error) {
console.error('Error updating processing activity:', error)
return NextResponse.json(
{ success: false, error: 'Failed to update processing activity' },
{ status: 500 }
)
}
}
export async function DELETE(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const { id } = await params
// In production, delete from database
return NextResponse.json({
success: true,
timestamp: new Date().toISOString(),
})
} catch (error) {
console.error('Error deleting processing activity:', error)
return NextResponse.json(
{ success: false, error: 'Failed to delete processing activity' },
{ status: 500 }
)
}
}

View File

@@ -1,84 +0,0 @@
import { NextRequest, NextResponse } from 'next/server'
import { v4 as uuidv4 } from 'uuid'
import { ProcessingActivity, generateVVTId } from '@/lib/sdk/vendor-compliance'
// In-memory storage for demo purposes
// In production, this would be replaced with database calls
const processingActivities: Map<string, ProcessingActivity> = new Map()
export async function GET(request: NextRequest) {
try {
const activities = Array.from(processingActivities.values())
return NextResponse.json({
success: true,
data: activities,
timestamp: new Date().toISOString(),
})
} catch (error) {
console.error('Error fetching processing activities:', error)
return NextResponse.json(
{ success: false, error: 'Failed to fetch processing activities' },
{ status: 500 }
)
}
}
export async function POST(request: NextRequest) {
try {
const body = await request.json()
// Generate IDs
const id = uuidv4()
const existingIds = Array.from(processingActivities.values()).map((a) => a.vvtId)
const vvtId = body.vvtId || generateVVTId(existingIds)
const activity: ProcessingActivity = {
id,
tenantId: 'default', // Would come from auth context
vvtId,
name: body.name,
responsible: body.responsible,
dpoContact: body.dpoContact,
purposes: body.purposes || [],
dataSubjectCategories: body.dataSubjectCategories || [],
personalDataCategories: body.personalDataCategories || [],
recipientCategories: body.recipientCategories || [],
thirdCountryTransfers: body.thirdCountryTransfers || [],
retentionPeriod: body.retentionPeriod || { description: { de: '', en: '' } },
technicalMeasures: body.technicalMeasures || [],
legalBasis: body.legalBasis || [],
dataSources: body.dataSources || [],
systems: body.systems || [],
dataFlows: body.dataFlows || [],
protectionLevel: body.protectionLevel || 'MEDIUM',
dpiaRequired: body.dpiaRequired || false,
dpiaJustification: body.dpiaJustification,
subProcessors: body.subProcessors || [],
legalRetentionBasis: body.legalRetentionBasis,
status: body.status || 'DRAFT',
owner: body.owner || '',
lastReviewDate: body.lastReviewDate,
nextReviewDate: body.nextReviewDate,
createdAt: new Date(),
updatedAt: new Date(),
}
processingActivities.set(id, activity)
return NextResponse.json(
{
success: true,
data: activity,
timestamp: new Date().toISOString(),
},
{ status: 201 }
)
} catch (error) {
console.error('Error creating processing activity:', error)
return NextResponse.json(
{ success: false, error: 'Failed to create processing activity' },
{ status: 500 }
)
}
}

View File

@@ -1,82 +0,0 @@
import { NextRequest, NextResponse } from 'next/server'
import { v4 as uuidv4 } from 'uuid'
import { Vendor } from '@/lib/sdk/vendor-compliance'
// In-memory storage for demo purposes
const vendors: Map<string, Vendor> = new Map()
export async function GET(request: NextRequest) {
try {
const vendorList = Array.from(vendors.values())
return NextResponse.json({
success: true,
data: vendorList,
timestamp: new Date().toISOString(),
})
} catch (error) {
console.error('Error fetching vendors:', error)
return NextResponse.json(
{ success: false, error: 'Failed to fetch vendors' },
{ status: 500 }
)
}
}
export async function POST(request: NextRequest) {
try {
const body = await request.json()
const id = uuidv4()
const vendor: Vendor = {
id,
tenantId: 'default',
name: body.name,
legalForm: body.legalForm,
country: body.country,
address: body.address,
website: body.website,
role: body.role,
serviceDescription: body.serviceDescription,
serviceCategory: body.serviceCategory,
dataAccessLevel: body.dataAccessLevel || 'NONE',
processingLocations: body.processingLocations || [],
transferMechanisms: body.transferMechanisms || [],
certifications: body.certifications || [],
primaryContact: body.primaryContact,
dpoContact: body.dpoContact,
securityContact: body.securityContact,
contractTypes: body.contractTypes || [],
contracts: body.contracts || [],
inherentRiskScore: body.inherentRiskScore || 50,
residualRiskScore: body.residualRiskScore || 50,
manualRiskAdjustment: body.manualRiskAdjustment,
riskJustification: body.riskJustification,
reviewFrequency: body.reviewFrequency || 'ANNUAL',
lastReviewDate: body.lastReviewDate,
nextReviewDate: body.nextReviewDate,
status: body.status || 'ACTIVE',
processingActivityIds: body.processingActivityIds || [],
notes: body.notes,
createdAt: new Date(),
updatedAt: new Date(),
}
vendors.set(id, vendor)
return NextResponse.json(
{
success: true,
data: vendor,
timestamp: new Date().toISOString(),
},
{ status: 201 }
)
} catch (error) {
console.error('Error creating vendor:', error)
return NextResponse.json(
{ success: false, error: 'Failed to create vendor' },
{ status: 500 }
)
}
}