2 Commits

Author SHA1 Message Date
Benjamin Admin
71bc48449d fix(training): resolve Gin route param conflicts for content/media routes
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 45s
CI / test-python-backend-compliance (push) Successful in 34s
CI / test-python-document-crawler (push) Successful in 22s
CI / test-python-dsms-gateway (push) Successful in 20s
Use consistent :moduleId param name for content routes and :mediaId for
media routes. Add param adapters for handlers that expect different names.
Fix frontend media API paths to match backend route structure.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 09:52:15 +01:00
Benjamin Admin
e5cb5e37ad feat(training): register training routes and add missing academy API functions
Connect the existing training engine handlers (40+ endpoints) to the router
in main.go. This was the critical blocker preventing the training content
pipeline from being accessible. Also adds generateCourse, generateVideos,
and getVideoStatus functions to the academy API client, plus the
GenerateCourseRequest type.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 09:46:16 +01:00
4 changed files with 133 additions and 2 deletions

View File

@@ -10,6 +10,7 @@ import {
CourseCategory,
CourseCreateRequest,
CourseUpdateRequest,
GenerateCourseRequest,
Enrollment,
EnrollmentStatus,
EnrollmentListResponse,
@@ -273,6 +274,52 @@ export async function fetchAcademyStatistics(): Promise<AcademyStatistics> {
)
}
// =============================================================================
// COURSE GENERATION (via Training Engine)
// =============================================================================
/**
* KI-gestuetzten Kurs generieren (nutzt intern Training Content Pipeline)
*/
export async function generateCourse(request: GenerateCourseRequest): Promise<Course> {
return fetchWithTimeout<Course>(
`${ACADEMY_API_BASE}/courses/generate`,
{
method: 'POST',
body: JSON.stringify(request)
},
120000 // 2 min timeout for LLM generation
)
}
/**
* Videos fuer alle Lektionen eines Kurses generieren
*/
export async function generateVideos(courseId: string): Promise<{ status: string; jobId?: string }> {
return fetchWithTimeout<{ status: string; jobId?: string }>(
`${ACADEMY_API_BASE}/courses/${courseId}/generate-videos`,
{
method: 'POST'
},
300000 // 5 min timeout for video generation
)
}
/**
* Video-Generierungsstatus abrufen
*/
export async function getVideoStatus(courseId: string): Promise<{
status: string
total: number
completed: number
failed: number
videos: Array<{ lessonId: string; status: string; url?: string }>
}> {
return fetchWithTimeout(
`${ACADEMY_API_BASE}/courses/${courseId}/video-status`
)
}
// =============================================================================
// SDK PROXY FUNCTION (wraps fetchCourses + fetchAcademyStatistics)
// =============================================================================

View File

@@ -215,6 +215,15 @@ export interface CourseUpdateRequest {
requiredForRoles?: string[]
}
export interface GenerateCourseRequest {
title: string
category: CourseCategory
description?: string
regulationArea?: string
language?: string
durationMinutes?: number
}
export interface EnrollUserRequest {
courseId: string
userId: string

View File

@@ -292,11 +292,11 @@ export async function getModuleMedia(moduleId: string): Promise<{ media: Trainin
}
export async function getMediaURL(mediaId: string): Promise<{ bucket: string; object_key: string; mime_type: string }> {
return apiFetch(`/media/module/${mediaId}/url`)
return apiFetch(`/media/${mediaId}/url`)
}
export async function publishMedia(mediaId: string, publish?: boolean): Promise<{ status: string; is_published: boolean }> {
return apiFetch(`/media/module/${mediaId}/publish`, {
return apiFetch(`/media/${mediaId}/publish`, {
method: 'POST',
body: JSON.stringify({ publish: publish !== false }),
})

View File

@@ -18,6 +18,7 @@ import (
"github.com/breakpilot/ai-compliance-sdk/internal/academy"
"github.com/breakpilot/ai-compliance-sdk/internal/incidents"
"github.com/breakpilot/ai-compliance-sdk/internal/roadmap"
"github.com/breakpilot/ai-compliance-sdk/internal/training"
"github.com/breakpilot/ai-compliance-sdk/internal/ucca"
"github.com/breakpilot/ai-compliance-sdk/internal/whistleblower"
"github.com/breakpilot/ai-compliance-sdk/internal/iace"
@@ -69,6 +70,7 @@ func main() {
incidentStore := incidents.NewStore(pool)
vendorStore := vendor.NewStore(pool)
iaceStore := iace.NewStore(pool)
trainingStore := training.NewStore(pool)
// Initialize services
rbacService := rbac.NewService(rbacStore)
@@ -90,6 +92,10 @@ func main() {
// Initialize PII detector
piiDetector := llm.NewPIIDetectorWithPatterns(llm.AllPIIPatterns())
// Initialize TTS client and content generator for training
ttsClient := training.NewTTSClient(cfg.TTSServiceURL)
contentGenerator := training.NewContentGenerator(providerRegistry, piiDetector, trainingStore, ttsClient)
// Initialize access gate
accessGate := llm.NewAccessGate(policyEngine, piiDetector, providerRegistry)
@@ -113,6 +119,7 @@ func main() {
incidentHandlers := handlers.NewIncidentHandlers(incidentStore)
vendorHandlers := handlers.NewVendorHandlers(vendorStore)
iaceHandler := handlers.NewIACEHandler(iaceStore)
trainingHandlers := handlers.NewTrainingHandlers(trainingStore, contentGenerator)
// Initialize middleware
rbacMiddleware := rbac.NewMiddleware(rbacService, policyEngine)
@@ -478,6 +485,74 @@ func main() {
academyRoutes.GET("/stats", academyHandlers.GetStatistics)
}
// Training Engine routes - Compliance Training Content Pipeline
trainingRoutes := v1.Group("/training")
{
// Module CRUD
trainingRoutes.GET("/modules", trainingHandlers.ListModules)
trainingRoutes.GET("/modules/:id", trainingHandlers.GetModule)
trainingRoutes.POST("/modules", trainingHandlers.CreateModule)
trainingRoutes.PUT("/modules/:id", trainingHandlers.UpdateModule)
// Compliance Training Matrix (CTM)
trainingRoutes.GET("/matrix", trainingHandlers.GetMatrix)
trainingRoutes.GET("/matrix/:role", trainingHandlers.GetMatrixForRole)
trainingRoutes.POST("/matrix", trainingHandlers.SetMatrixEntry)
trainingRoutes.DELETE("/matrix/:role/:moduleId", trainingHandlers.DeleteMatrixEntry)
// Assignments
trainingRoutes.POST("/assignments/compute", trainingHandlers.ComputeAssignments)
trainingRoutes.GET("/assignments", trainingHandlers.ListAssignments)
trainingRoutes.GET("/assignments/:id", trainingHandlers.GetAssignment)
trainingRoutes.POST("/assignments/:id/start", trainingHandlers.StartAssignment)
trainingRoutes.POST("/assignments/:id/progress", trainingHandlers.UpdateAssignmentProgress)
trainingRoutes.POST("/assignments/:id/complete", trainingHandlers.CompleteAssignment)
// Quiz
trainingRoutes.GET("/quiz/:moduleId", trainingHandlers.GetQuiz)
trainingRoutes.POST("/quiz/:moduleId/submit", trainingHandlers.SubmitQuiz)
trainingRoutes.GET("/quiz/attempts/:assignmentId", trainingHandlers.GetQuizAttempts)
// Content Generation (LLM)
trainingRoutes.POST("/content/generate", trainingHandlers.GenerateContent)
trainingRoutes.POST("/content/generate-quiz", trainingHandlers.GenerateQuiz)
trainingRoutes.POST("/content/generate-all", trainingHandlers.GenerateAllContent)
trainingRoutes.POST("/content/generate-all-quiz", trainingHandlers.GenerateAllQuizzes)
trainingRoutes.GET("/content/:moduleId", trainingHandlers.GetContent)
// PublishContent expects c.Param("id") but route uses :moduleId for Gin compatibility
trainingRoutes.POST("/content/:moduleId/publish", func(c *gin.Context) {
c.Params = append(c.Params, gin.Param{Key: "id", Value: c.Param("moduleId")})
trainingHandlers.PublishContent(c)
})
// Media (Audio/Video via TTS Service)
trainingRoutes.POST("/content/:moduleId/generate-audio", trainingHandlers.GenerateAudio)
trainingRoutes.POST("/content/:moduleId/generate-video", trainingHandlers.GenerateVideo)
trainingRoutes.POST("/content/:moduleId/preview-script", trainingHandlers.PreviewVideoScript)
trainingRoutes.GET("/media/module/:moduleId", trainingHandlers.GetModuleMedia)
// Media detail routes use :mediaId consistently
trainingRoutes.GET("/media/:mediaId/url", func(c *gin.Context) {
c.Params = append(c.Params, gin.Param{Key: "id", Value: c.Param("mediaId")})
trainingHandlers.GetMediaURL(c)
})
trainingRoutes.POST("/media/:mediaId/publish", func(c *gin.Context) {
c.Params = append(c.Params, gin.Param{Key: "id", Value: c.Param("mediaId")})
trainingHandlers.PublishMedia(c)
})
// Deadlines & Escalation
trainingRoutes.GET("/deadlines", trainingHandlers.GetDeadlines)
trainingRoutes.GET("/deadlines/overdue", trainingHandlers.GetOverdueDeadlines)
trainingRoutes.POST("/escalation/check", trainingHandlers.CheckEscalation)
// Audit & Statistics
trainingRoutes.GET("/audit-log", trainingHandlers.GetAuditLog)
trainingRoutes.GET("/stats", trainingHandlers.GetStats)
// Certificates
trainingRoutes.GET("/certificates/:id/verify", trainingHandlers.VerifyCertificate)
}
// Whistleblower routes - Hinweisgebersystem (HinSchG)
whistleblowerRoutes := v1.Group("/whistleblower")
{