refactor(go/handlers): split iace_handler and training_handlers into focused files
iace_handler.go (2706 LOC) split into 9 files: - iace_handler.go: struct, constructor, shared helpers (~156 LOC) - iace_handler_projects.go: project CRUD + InitFromProfile (~310 LOC) - iace_handler_components.go: components + classification (~387 LOC) - iace_handler_hazards.go: hazard library, CRUD, risk assessment (~469 LOC) - iace_handler_mitigations.go: mitigations, evidence, verification plans (~293 LOC) - iace_handler_techfile.go: CE tech file generation/export (~452 LOC) - iace_handler_monitoring.go: monitoring events + audit trail (~134 LOC) - iace_handler_refdata.go: ISO 12100 ref data, patterns, suggestions (~465 LOC) - iace_handler_rag.go: RAG library search + section enrichment (~142 LOC) training_handlers.go (1864 LOC) split into 9 files: - training_handlers.go: struct + constructor (~23 LOC) - training_handlers_modules.go: module CRUD (~226 LOC) - training_handlers_matrix.go: CTM matrix endpoints (~95 LOC) - training_handlers_assignments.go: assignment lifecycle (~243 LOC) - training_handlers_quiz.go: quiz submit/grade/attempts (~185 LOC) - training_handlers_content.go: LLM content/audio/video generation (~274 LOC) - training_handlers_media.go: media, streaming, interactive video (~325 LOC) - training_handlers_blocks.go: block configs + canonical controls (~280 LOC) - training_handlers_stats.go: deadlines, escalation, audit, certificates (~290 LOC) All files remain in package handlers. Zero behavior changes. All exported function names preserved. All files under 500 LOC hard cap. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,274 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/breakpilot/ai-compliance-sdk/internal/rbac"
|
||||
"github.com/breakpilot/ai-compliance-sdk/internal/training"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// ============================================================================
|
||||
// Content Endpoints
|
||||
// ============================================================================
|
||||
|
||||
// GenerateContent generates module content via LLM
|
||||
// POST /sdk/v1/training/content/generate
|
||||
func (h *TrainingHandlers) GenerateContent(c *gin.Context) {
|
||||
var req training.GenerateContentRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
module, err := h.store.GetModule(c.Request.Context(), req.ModuleID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
if module == nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "module not found"})
|
||||
return
|
||||
}
|
||||
|
||||
content, err := h.contentGenerator.GenerateModuleContent(c.Request.Context(), *module, req.Language)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, content)
|
||||
}
|
||||
|
||||
// GenerateQuiz generates quiz questions via LLM
|
||||
// POST /sdk/v1/training/content/generate-quiz
|
||||
func (h *TrainingHandlers) GenerateQuiz(c *gin.Context) {
|
||||
var req training.GenerateQuizRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
module, err := h.store.GetModule(c.Request.Context(), req.ModuleID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
if module == nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "module not found"})
|
||||
return
|
||||
}
|
||||
|
||||
count := req.Count
|
||||
if count <= 0 {
|
||||
count = 5
|
||||
}
|
||||
|
||||
questions, err := h.contentGenerator.GenerateQuizQuestions(c.Request.Context(), *module, count)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"questions": questions,
|
||||
"total": len(questions),
|
||||
})
|
||||
}
|
||||
|
||||
// GetContent returns published content for a module
|
||||
// GET /sdk/v1/training/content/:moduleId
|
||||
func (h *TrainingHandlers) GetContent(c *gin.Context) {
|
||||
moduleID, err := uuid.Parse(c.Param("moduleId"))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid module ID"})
|
||||
return
|
||||
}
|
||||
|
||||
content, err := h.store.GetPublishedContent(c.Request.Context(), moduleID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
if content == nil {
|
||||
// Try latest unpublished
|
||||
content, err = h.store.GetLatestContent(c.Request.Context(), moduleID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
}
|
||||
if content == nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "no content found for this module"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, content)
|
||||
}
|
||||
|
||||
// PublishContent publishes a content version
|
||||
// POST /sdk/v1/training/content/:id/publish
|
||||
func (h *TrainingHandlers) PublishContent(c *gin.Context) {
|
||||
id, err := uuid.Parse(c.Param("id"))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid content ID"})
|
||||
return
|
||||
}
|
||||
|
||||
reviewedBy := rbac.GetUserID(c)
|
||||
|
||||
if err := h.store.PublishContent(c.Request.Context(), id, reviewedBy); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"status": "published"})
|
||||
}
|
||||
|
||||
// GenerateAllContent generates content for all modules that don't have content yet
|
||||
// POST /sdk/v1/training/content/generate-all
|
||||
func (h *TrainingHandlers) GenerateAllContent(c *gin.Context) {
|
||||
tenantID := rbac.GetTenantID(c)
|
||||
|
||||
language := "de"
|
||||
if v := c.Query("language"); v != "" {
|
||||
language = v
|
||||
}
|
||||
|
||||
result, err := h.contentGenerator.GenerateAllModuleContent(c.Request.Context(), tenantID, language)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, result)
|
||||
}
|
||||
|
||||
// GenerateAllQuizzes generates quiz questions for all modules that don't have questions yet
|
||||
// POST /sdk/v1/training/content/generate-all-quiz
|
||||
func (h *TrainingHandlers) GenerateAllQuizzes(c *gin.Context) {
|
||||
tenantID := rbac.GetTenantID(c)
|
||||
|
||||
count := 5
|
||||
|
||||
result, err := h.contentGenerator.GenerateAllQuizQuestions(c.Request.Context(), tenantID, count)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, result)
|
||||
}
|
||||
|
||||
// GenerateAudio generates audio for a module via TTS service
|
||||
// POST /sdk/v1/training/content/:moduleId/generate-audio
|
||||
func (h *TrainingHandlers) GenerateAudio(c *gin.Context) {
|
||||
moduleID, err := uuid.Parse(c.Param("moduleId"))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid module ID"})
|
||||
return
|
||||
}
|
||||
|
||||
module, err := h.store.GetModule(c.Request.Context(), moduleID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
if module == nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "module not found"})
|
||||
return
|
||||
}
|
||||
|
||||
media, err := h.contentGenerator.GenerateAudio(c.Request.Context(), *module)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, media)
|
||||
}
|
||||
|
||||
// GenerateVideo generates a presentation video for a module
|
||||
// POST /sdk/v1/training/content/:moduleId/generate-video
|
||||
func (h *TrainingHandlers) GenerateVideo(c *gin.Context) {
|
||||
moduleID, err := uuid.Parse(c.Param("moduleId"))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid module ID"})
|
||||
return
|
||||
}
|
||||
|
||||
module, err := h.store.GetModule(c.Request.Context(), moduleID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
if module == nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "module not found"})
|
||||
return
|
||||
}
|
||||
|
||||
media, err := h.contentGenerator.GenerateVideo(c.Request.Context(), *module)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, media)
|
||||
}
|
||||
|
||||
// PreviewVideoScript generates and returns a video script preview without creating the video
|
||||
// POST /sdk/v1/training/content/:moduleId/preview-script
|
||||
func (h *TrainingHandlers) PreviewVideoScript(c *gin.Context) {
|
||||
moduleID, err := uuid.Parse(c.Param("moduleId"))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid module ID"})
|
||||
return
|
||||
}
|
||||
|
||||
module, err := h.store.GetModule(c.Request.Context(), moduleID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
if module == nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "module not found"})
|
||||
return
|
||||
}
|
||||
|
||||
script, err := h.contentGenerator.GenerateVideoScript(c.Request.Context(), *module)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, script)
|
||||
}
|
||||
|
||||
// GenerateInteractiveVideo triggers the full interactive video pipeline
|
||||
// POST /sdk/v1/training/content/:moduleId/generate-interactive
|
||||
func (h *TrainingHandlers) GenerateInteractiveVideo(c *gin.Context) {
|
||||
moduleID, err := uuid.Parse(c.Param("moduleId"))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid module ID"})
|
||||
return
|
||||
}
|
||||
|
||||
module, err := h.store.GetModule(c.Request.Context(), moduleID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
if module == nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "module not found"})
|
||||
return
|
||||
}
|
||||
|
||||
media, err := h.contentGenerator.GenerateInteractiveVideo(c.Request.Context(), *module)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, media)
|
||||
}
|
||||
Reference in New Issue
Block a user