Add lesson content editor, quiz test endpoint, and lesson update API
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 36s
CI / test-python-backend-compliance (push) Successful in 31s
CI / test-python-document-crawler (push) Successful in 23s
CI / test-python-dsms-gateway (push) Successful in 21s
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 36s
CI / test-python-backend-compliance (push) Successful in 31s
CI / test-python-document-crawler (push) Successful in 23s
CI / test-python-dsms-gateway (push) Successful in 21s
- Backend: UpdateLesson handler (PUT /lessons/:id) for editing title, content, quiz questions - Backend: TestQuiz handler (POST /lessons/:id/quiz-test) for quiz evaluation without enrollment - Frontend: Content editor with markdown textarea, save, and approve-for-video workflow - Frontend: Fix quiz endpoint to /lessons/:id/quiz-test Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -572,6 +572,131 @@ func (h *AcademyHandlers) SubmitQuiz(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Lesson Update
|
||||
// ============================================================================
|
||||
|
||||
// UpdateLesson updates a lesson's content, title, or quiz questions
|
||||
// PUT /sdk/v1/academy/lessons/:id
|
||||
func (h *AcademyHandlers) UpdateLesson(c *gin.Context) {
|
||||
lessonID, err := uuid.Parse(c.Param("id"))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid lesson ID"})
|
||||
return
|
||||
}
|
||||
|
||||
lesson, err := h.store.GetLesson(c.Request.Context(), lessonID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
if lesson == nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "lesson not found"})
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Title *string `json:"title"`
|
||||
Description *string `json:"description"`
|
||||
ContentURL *string `json:"content_url"`
|
||||
DurationMinutes *int `json:"duration_minutes"`
|
||||
QuizQuestions *[]academy.QuizQuestion `json:"quiz_questions"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if req.Title != nil {
|
||||
lesson.Title = *req.Title
|
||||
}
|
||||
if req.Description != nil {
|
||||
lesson.Description = *req.Description
|
||||
}
|
||||
if req.ContentURL != nil {
|
||||
lesson.ContentURL = *req.ContentURL
|
||||
}
|
||||
if req.DurationMinutes != nil {
|
||||
lesson.DurationMinutes = *req.DurationMinutes
|
||||
}
|
||||
if req.QuizQuestions != nil {
|
||||
lesson.QuizQuestions = *req.QuizQuestions
|
||||
}
|
||||
|
||||
if err := h.store.UpdateLesson(c.Request.Context(), lesson); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"lesson": lesson})
|
||||
}
|
||||
|
||||
// TestQuiz evaluates quiz answers without requiring an enrollment
|
||||
// POST /sdk/v1/academy/lessons/:id/quiz-test
|
||||
func (h *AcademyHandlers) TestQuiz(c *gin.Context) {
|
||||
lessonID, err := uuid.Parse(c.Param("id"))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid lesson ID"})
|
||||
return
|
||||
}
|
||||
|
||||
lesson, err := h.store.GetLesson(c.Request.Context(), lessonID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
if lesson == nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "lesson not found"})
|
||||
return
|
||||
}
|
||||
|
||||
if len(lesson.QuizQuestions) == 0 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "lesson has no quiz questions"})
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Answers []int `json:"answers"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if len(req.Answers) != len(lesson.QuizQuestions) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "number of answers must match number of questions"})
|
||||
return
|
||||
}
|
||||
|
||||
correctCount := 0
|
||||
var results []academy.QuizResult
|
||||
for i, question := range lesson.QuizQuestions {
|
||||
correct := req.Answers[i] == question.CorrectIndex
|
||||
if correct {
|
||||
correctCount++
|
||||
}
|
||||
results = append(results, academy.QuizResult{
|
||||
Question: question.Question,
|
||||
Correct: correct,
|
||||
Explanation: question.Explanation,
|
||||
})
|
||||
}
|
||||
|
||||
totalQuestions := len(lesson.QuizQuestions)
|
||||
score := 0
|
||||
if totalQuestions > 0 {
|
||||
score = (correctCount * 100) / totalQuestions
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, academy.SubmitQuizResponse{
|
||||
Score: score,
|
||||
Passed: score >= 70,
|
||||
CorrectAnswers: correctCount,
|
||||
TotalQuestions: totalQuestions,
|
||||
Results: results,
|
||||
})
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Statistics
|
||||
// ============================================================================
|
||||
|
||||
Reference in New Issue
Block a user