package handlers import ( "net/http" "strconv" "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" ) // ============================================================================ // Module Endpoints // ============================================================================ // ListModules returns all training modules for the tenant // GET /sdk/v1/training/modules func (h *TrainingHandlers) ListModules(c *gin.Context) { tenantID := rbac.GetTenantID(c) filters := &training.ModuleFilters{ Limit: 50, Offset: 0, } if v := c.Query("regulation_area"); v != "" { filters.RegulationArea = training.RegulationArea(v) } if v := c.Query("frequency_type"); v != "" { filters.FrequencyType = training.FrequencyType(v) } if v := c.Query("search"); v != "" { filters.Search = v } if v := c.Query("limit"); v != "" { if n, err := strconv.Atoi(v); err == nil { filters.Limit = n } } if v := c.Query("offset"); v != "" { if n, err := strconv.Atoi(v); err == nil { filters.Offset = n } } modules, total, err := h.store.ListModules(c.Request.Context(), tenantID, filters) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, training.ModuleListResponse{ Modules: modules, Total: total, }) } // GetModule returns a single training module with content and quiz // GET /sdk/v1/training/modules/:id func (h *TrainingHandlers) GetModule(c *gin.Context) { id, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid module ID"}) return } module, err := h.store.GetModule(c.Request.Context(), id) 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 } // Include content and quiz questions content, _ := h.store.GetPublishedContent(c.Request.Context(), id) questions, _ := h.store.ListQuizQuestions(c.Request.Context(), id) c.JSON(http.StatusOK, gin.H{ "module": module, "content": content, "questions": questions, }) } // CreateModule creates a new training module // POST /sdk/v1/training/modules func (h *TrainingHandlers) CreateModule(c *gin.Context) { tenantID := rbac.GetTenantID(c) var req training.CreateModuleRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } module := &training.TrainingModule{ TenantID: tenantID, ModuleCode: req.ModuleCode, Title: req.Title, Description: req.Description, RegulationArea: req.RegulationArea, NIS2Relevant: req.NIS2Relevant, ISOControls: req.ISOControls, FrequencyType: req.FrequencyType, ValidityDays: req.ValidityDays, RiskWeight: req.RiskWeight, ContentType: req.ContentType, DurationMinutes: req.DurationMinutes, PassThreshold: req.PassThreshold, } if module.ValidityDays == 0 { module.ValidityDays = 365 } if module.RiskWeight == 0 { module.RiskWeight = 2.0 } if module.ContentType == "" { module.ContentType = "text" } if module.PassThreshold == 0 { module.PassThreshold = 70 } if module.ISOControls == nil { module.ISOControls = []string{} } if err := h.store.CreateModule(c.Request.Context(), module); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusCreated, module) } // UpdateModule updates a training module // PUT /sdk/v1/training/modules/:id func (h *TrainingHandlers) UpdateModule(c *gin.Context) { id, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid module ID"}) return } module, err := h.store.GetModule(c.Request.Context(), id) 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 } var req training.UpdateModuleRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if req.Title != nil { module.Title = *req.Title } if req.Description != nil { module.Description = *req.Description } if req.NIS2Relevant != nil { module.NIS2Relevant = *req.NIS2Relevant } if req.ISOControls != nil { module.ISOControls = req.ISOControls } if req.ValidityDays != nil { module.ValidityDays = *req.ValidityDays } if req.RiskWeight != nil { module.RiskWeight = *req.RiskWeight } if req.DurationMinutes != nil { module.DurationMinutes = *req.DurationMinutes } if req.PassThreshold != nil { module.PassThreshold = *req.PassThreshold } if req.IsActive != nil { module.IsActive = *req.IsActive } if err := h.store.UpdateModule(c.Request.Context(), module); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, module) } // DeleteModule deletes a training module // DELETE /sdk/v1/training/modules/:id func (h *TrainingHandlers) DeleteModule(c *gin.Context) { id, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid module ID"}) return } module, err := h.store.GetModule(c.Request.Context(), id) 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 } if err := h.store.DeleteModule(c.Request.Context(), id); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"status": "deleted"}) }