fix(admin-v2): Restore complete admin-v2 application
The admin-v2 application was incomplete in the repository. This commit restores all missing components: - Admin pages (76 pages): dashboard, ai, compliance, dsgvo, education, infrastructure, communication, development, onboarding, rbac - SDK pages (45 pages): tom, dsfa, vvt, loeschfristen, einwilligungen, vendor-compliance, tom-generator, dsr, and more - Developer portal (25 pages): API docs, SDK guides, frameworks - All components, lib files, hooks, and types - Updated package.json with all dependencies The issue was caused by incomplete initial repository state - the full admin-v2 codebase existed in backend/admin-v2 and docs-src/admin-v2 but was never fully synced to the main admin-v2 directory. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
205
admin-v2/app/api/sdk/v1/tom-generator/gap-analysis/route.ts
Normal file
205
admin-v2/app/api/sdk/v1/tom-generator/gap-analysis/route.ts
Normal file
@@ -0,0 +1,205 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { TOMRulesEngine } from '@/lib/sdk/tom-generator/rules-engine'
|
||||
import { TOMGeneratorState, GapAnalysisResult } from '@/lib/sdk/tom-generator/types'
|
||||
|
||||
/**
|
||||
* TOM Generator Gap Analysis API
|
||||
*
|
||||
* POST /api/sdk/v1/tom-generator/gap-analysis - Perform gap analysis
|
||||
*
|
||||
* Request body:
|
||||
* {
|
||||
* tenantId: string
|
||||
* state: TOMGeneratorState
|
||||
* }
|
||||
*
|
||||
* Response:
|
||||
* {
|
||||
* gapAnalysis: GapAnalysisResult
|
||||
* }
|
||||
*/
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const body = await request.json()
|
||||
const { tenantId, state } = body
|
||||
|
||||
if (!tenantId) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: 'tenantId is required' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
if (!state) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: 'state is required in request body' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
// Parse dates in state
|
||||
const parsedState: TOMGeneratorState = {
|
||||
...state,
|
||||
createdAt: new Date(state.createdAt),
|
||||
updatedAt: new Date(state.updatedAt),
|
||||
steps: state.steps?.map((step: { id: string; completed: boolean; data: unknown; validatedAt: string | null }) => ({
|
||||
...step,
|
||||
validatedAt: step.validatedAt ? new Date(step.validatedAt) : null,
|
||||
})) || [],
|
||||
documents: state.documents?.map((doc: { uploadedAt: string; validFrom?: string; validUntil?: string; aiAnalysis?: { analyzedAt: string } }) => ({
|
||||
...doc,
|
||||
uploadedAt: new Date(doc.uploadedAt),
|
||||
validFrom: doc.validFrom ? new Date(doc.validFrom) : null,
|
||||
validUntil: doc.validUntil ? new Date(doc.validUntil) : null,
|
||||
aiAnalysis: doc.aiAnalysis ? {
|
||||
...doc.aiAnalysis,
|
||||
analyzedAt: new Date(doc.aiAnalysis.analyzedAt),
|
||||
} : null,
|
||||
})) || [],
|
||||
derivedTOMs: state.derivedTOMs?.map((tom: { implementationDate?: string; reviewDate?: string }) => ({
|
||||
...tom,
|
||||
implementationDate: tom.implementationDate ? new Date(tom.implementationDate) : null,
|
||||
reviewDate: tom.reviewDate ? new Date(tom.reviewDate) : null,
|
||||
})) || [],
|
||||
gapAnalysis: state.gapAnalysis ? {
|
||||
...state.gapAnalysis,
|
||||
generatedAt: new Date(state.gapAnalysis.generatedAt),
|
||||
} : null,
|
||||
exports: state.exports?.map((exp: { generatedAt: string }) => ({
|
||||
...exp,
|
||||
generatedAt: new Date(exp.generatedAt),
|
||||
})) || [],
|
||||
}
|
||||
|
||||
// Initialize rules engine
|
||||
const engine = new TOMRulesEngine()
|
||||
|
||||
// Perform gap analysis using derived TOMs and documents from state
|
||||
const gapAnalysis = engine.performGapAnalysis(
|
||||
parsedState.derivedTOMs,
|
||||
parsedState.documents
|
||||
)
|
||||
|
||||
// Calculate detailed metrics
|
||||
const metrics = calculateGapMetrics(gapAnalysis)
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: {
|
||||
gapAnalysis,
|
||||
metrics,
|
||||
generatedAt: gapAnalysis.generatedAt.toISOString(),
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Failed to perform gap analysis:', error)
|
||||
return NextResponse.json(
|
||||
{ success: false, error: 'Failed to perform gap analysis' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function calculateGapMetrics(gapAnalysis: GapAnalysisResult) {
|
||||
const totalGaps = gapAnalysis.missingControls.length +
|
||||
gapAnalysis.partialControls.length +
|
||||
gapAnalysis.missingEvidence.length
|
||||
|
||||
const criticalGaps = gapAnalysis.missingControls.filter(
|
||||
(c) => c.priority === 'CRITICAL' || c.priority === 'HIGH'
|
||||
).length
|
||||
|
||||
const mediumGaps = gapAnalysis.missingControls.filter(
|
||||
(c) => c.priority === 'MEDIUM'
|
||||
).length
|
||||
|
||||
const lowGaps = gapAnalysis.missingControls.filter(
|
||||
(c) => c.priority === 'LOW'
|
||||
).length
|
||||
|
||||
// Group missing controls by category
|
||||
const gapsByCategory: Record<string, number> = {}
|
||||
gapAnalysis.missingControls.forEach((control) => {
|
||||
const category = control.controlId.split('-')[1] || 'OTHER'
|
||||
gapsByCategory[category] = (gapsByCategory[category] || 0) + 1
|
||||
})
|
||||
|
||||
// Calculate compliance readiness
|
||||
const maxScore = 100
|
||||
const deductionPerCritical = 10
|
||||
const deductionPerMedium = 5
|
||||
const deductionPerLow = 2
|
||||
const deductionPerPartial = 3
|
||||
const deductionPerMissingEvidence = 1
|
||||
|
||||
const deductions =
|
||||
criticalGaps * deductionPerCritical +
|
||||
mediumGaps * deductionPerMedium +
|
||||
lowGaps * deductionPerLow +
|
||||
gapAnalysis.partialControls.length * deductionPerPartial +
|
||||
gapAnalysis.missingEvidence.length * deductionPerMissingEvidence
|
||||
|
||||
const complianceReadiness = Math.max(0, Math.min(100, maxScore - deductions))
|
||||
|
||||
// Prioritized action items
|
||||
const prioritizedActions = [
|
||||
...gapAnalysis.missingControls
|
||||
.filter((c) => c.priority === 'CRITICAL')
|
||||
.map((c) => ({
|
||||
type: 'MISSING_CONTROL',
|
||||
priority: 'CRITICAL',
|
||||
controlId: c.controlId,
|
||||
reason: c.reason,
|
||||
action: `Implement control ${c.controlId}`,
|
||||
})),
|
||||
...gapAnalysis.missingControls
|
||||
.filter((c) => c.priority === 'HIGH')
|
||||
.map((c) => ({
|
||||
type: 'MISSING_CONTROL',
|
||||
priority: 'HIGH',
|
||||
controlId: c.controlId,
|
||||
reason: c.reason,
|
||||
action: `Implement control ${c.controlId}`,
|
||||
})),
|
||||
...gapAnalysis.partialControls.map((c) => ({
|
||||
type: 'PARTIAL_CONTROL',
|
||||
priority: 'MEDIUM',
|
||||
controlId: c.controlId,
|
||||
missingAspects: c.missingAspects,
|
||||
action: `Complete implementation of ${c.controlId}`,
|
||||
})),
|
||||
...gapAnalysis.missingEvidence.map((e) => ({
|
||||
type: 'MISSING_EVIDENCE',
|
||||
priority: 'LOW',
|
||||
controlId: e.controlId,
|
||||
requiredEvidence: e.requiredEvidence,
|
||||
action: `Upload evidence for ${e.controlId}`,
|
||||
})),
|
||||
]
|
||||
|
||||
return {
|
||||
totalGaps,
|
||||
criticalGaps,
|
||||
mediumGaps,
|
||||
lowGaps,
|
||||
partialControls: gapAnalysis.partialControls.length,
|
||||
missingEvidence: gapAnalysis.missingEvidence.length,
|
||||
gapsByCategory,
|
||||
complianceReadiness,
|
||||
overallScore: gapAnalysis.overallScore,
|
||||
prioritizedActionsCount: prioritizedActions.length,
|
||||
prioritizedActions: prioritizedActions.slice(0, 10), // Top 10 actions
|
||||
}
|
||||
}
|
||||
|
||||
export async function OPTIONS() {
|
||||
return NextResponse.json(
|
||||
{ status: 'ok' },
|
||||
{
|
||||
headers: {
|
||||
Allow: 'POST, OPTIONS',
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user