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" ) // ============================================================================ // Quiz Endpoints // ============================================================================ // GetQuiz returns quiz questions for a module // GET /sdk/v1/training/quiz/:moduleId func (h *TrainingHandlers) GetQuiz(c *gin.Context) { moduleID, err := uuid.Parse(c.Param("moduleId")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid module ID"}) return } questions, err := h.store.ListQuizQuestions(c.Request.Context(), moduleID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } // Strip correct_index for the student-facing response type safeQuestion struct { ID uuid.UUID `json:"id"` Question string `json:"question"` Options []string `json:"options"` Difficulty string `json:"difficulty"` } safe := make([]safeQuestion, len(questions)) for i, q := range questions { safe[i] = safeQuestion{ ID: q.ID, Question: q.Question, Options: q.Options, Difficulty: string(q.Difficulty), } } c.JSON(http.StatusOK, gin.H{ "questions": safe, "total": len(safe), }) } // SubmitQuiz submits quiz answers and returns the score // POST /sdk/v1/training/quiz/:moduleId/submit func (h *TrainingHandlers) SubmitQuiz(c *gin.Context) { moduleID, err := uuid.Parse(c.Param("moduleId")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid module ID"}) return } tenantID := rbac.GetTenantID(c) var req training.SubmitTrainingQuizRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } // Get the correct answers questions, err := h.store.ListQuizQuestions(c.Request.Context(), moduleID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } // Build answer map questionMap := make(map[uuid.UUID]training.QuizQuestion) for _, q := range questions { questionMap[q.ID] = q } // Score the answers correctCount := 0 totalCount := len(req.Answers) scoredAnswers := make([]training.QuizAnswer, len(req.Answers)) for i, answer := range req.Answers { q, exists := questionMap[answer.QuestionID] correct := exists && answer.SelectedIndex == q.CorrectIndex scoredAnswers[i] = training.QuizAnswer{ QuestionID: answer.QuestionID, SelectedIndex: answer.SelectedIndex, Correct: correct, } if correct { correctCount++ } } score := float64(0) if totalCount > 0 { score = float64(correctCount) / float64(totalCount) * 100 } // Get module for pass threshold module, _ := h.store.GetModule(c.Request.Context(), moduleID) threshold := 70 if module != nil { threshold = module.PassThreshold } passed := score >= float64(threshold) // Record the attempt userID := rbac.GetUserID(c) attempt := &training.QuizAttempt{ AssignmentID: req.AssignmentID, UserID: userID, Answers: scoredAnswers, Score: score, Passed: passed, CorrectCount: correctCount, TotalCount: totalCount, DurationSeconds: req.DurationSeconds, } if err := h.store.CreateQuizAttempt(c.Request.Context(), attempt); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } // Update assignment quiz result // Count total attempts attempts, _ := h.store.ListQuizAttempts(c.Request.Context(), req.AssignmentID) h.store.UpdateAssignmentQuizResult(c.Request.Context(), req.AssignmentID, score, passed, len(attempts)) // Audit log h.store.LogAction(c.Request.Context(), &training.AuditLogEntry{ TenantID: tenantID, UserID: &userID, Action: training.AuditActionQuizSubmitted, EntityType: training.AuditEntityQuiz, EntityID: &attempt.ID, Details: map[string]interface{}{ "module_id": moduleID.String(), "score": score, "passed": passed, "correct_count": correctCount, "total_count": totalCount, }, }) c.JSON(http.StatusOK, training.SubmitTrainingQuizResponse{ AttemptID: attempt.ID, Score: score, Passed: passed, CorrectCount: correctCount, TotalCount: totalCount, Threshold: threshold, }) } // GetQuizAttempts returns quiz attempts for an assignment // GET /sdk/v1/training/quiz/attempts/:assignmentId func (h *TrainingHandlers) GetQuizAttempts(c *gin.Context) { assignmentID, err := uuid.Parse(c.Param("assignmentId")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid assignment ID"}) return } attempts, err := h.store.ListQuizAttempts(c.Request.Context(), assignmentID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{ "attempts": attempts, "total": len(attempts), }) }