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) }