package handlers import ( "Yimaru-Backend/internal/domain" "Yimaru-Backend/internal/services/practicecontent" "errors" "strconv" "github.com/gofiber/fiber/v2" ) func fullUpdatePracticeQuestionResponses(questions []domain.QuestionWithDetails) []questionRes { out := make([]questionRes, 0, len(questions)) for _, question := range questions { options := make([]optionRes, 0, len(question.Options)) for _, opt := range question.Options { options = append(options, optionRes{ ID: opt.ID, OptionText: opt.OptionText, OptionOrder: opt.OptionOrder, IsCorrect: opt.IsCorrect, }) } shortAnswers := make([]shortAnswerRes, 0, len(question.ShortAnswers)) for _, sa := range question.ShortAnswers { shortAnswers = append(shortAnswers, shortAnswerRes{ ID: sa.ID, AcceptableAnswer: sa.AcceptableAnswer, MatchType: sa.MatchType, }) } var audioCorrectAnswerText *string if question.AudioAnswer != nil { audioCorrectAnswerText = &question.AudioAnswer.CorrectAnswerText } out = append(out, questionRes{ ID: question.ID, QuestionText: questionTextField(question.QuestionType, question.QuestionText), QuestionType: question.QuestionType, QuestionTypeDefinitionID: question.QuestionTypeDefinitionID, DynamicPayload: question.DynamicPayload, DifficultyLevel: question.DifficultyLevel, Points: question.Points, Explanation: question.Explanation, Tips: question.Tips, VoicePrompt: question.VoicePrompt, SampleAnswerVoicePrompt: question.SampleAnswerVoicePrompt, ImageURL: question.ImageURL, Status: question.Status, CreatedAt: question.CreatedAt.String(), Options: options, ShortAnswers: shortAnswers, AudioCorrectAnswerText: audioCorrectAnswerText, }) } return out } func fullUpdatePracticeResponse(result domain.FullUpdatePracticeResult) fiber.Map { return fiber.Map{ "practice": result.Practice, "question_set": questionSetResFromDomain(result.QuestionSet), "questions": fullUpdatePracticeQuestionResponses(result.Questions), } } func questionSetResFromDomain(set domain.QuestionSet) questionSetRes { return questionSetRes{ ID: set.ID, Title: set.Title, Description: set.Description, SetType: set.SetType, OwnerType: set.OwnerType, OwnerID: set.OwnerID, BannerImage: set.BannerImage, Persona: set.Persona, TimeLimitMinutes: set.TimeLimitMinutes, PassingScore: set.PassingScore, ShuffleQuestions: set.ShuffleQuestions, Status: set.Status, IntroVideoURL: set.IntroVideoURL, CreatedAt: set.CreatedAt.String(), } } func mapFullUpdatePracticeError(err error) (int, string) { switch { case errors.Is(err, practicecontent.ErrPracticeNotFound): return fiber.StatusNotFound, "Practice not found" case errors.Is(err, practicecontent.ErrQuestionSetNotFound): return fiber.StatusNotFound, "Question set not found" case errors.Is(err, practicecontent.ErrQuestionNotFound): return fiber.StatusNotFound, "Question not found" case errors.Is(err, domain.ErrPersonaNotFound): return fiber.StatusNotFound, "Persona not found" case errors.Is(err, practicecontent.ErrInvalidQuestionItem): return fiber.StatusBadRequest, "Invalid question item" default: return fiber.StatusBadRequest, "Failed to update practice" } } // UpdateLmsPracticeFull godoc // @Summary Full update LMS practice with question set and questions // @Description Updates practice metadata, linked question set settings, and syncs questions in one request. Questions omitted from the questions array are removed from the set (not deleted from the bank). // @Tags practices // @Accept json // @Produce json // @Param id path int true "Practice ID" // @Param body body domain.FullUpdatePracticeInput true "Full practice update payload" // @Success 200 {object} domain.Response // @Failure 400 {object} domain.ErrorResponse // @Failure 404 {object} domain.ErrorResponse // @Router /api/v1/practices/{id}/full [put] func (h *Handler) UpdateLmsPracticeFull(c *fiber.Ctx) error { id, err := strconv.ParseInt(c.Params("id"), 10, 64) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid practice id", Error: err.Error(), }) } var req domain.FullUpdatePracticeInput if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid request body", Error: err.Error(), }) } result, err := h.practiceContentSvc.UpdateLmsPracticeFull(c.Context(), id, req) if err != nil { status, msg := mapFullUpdatePracticeError(err) return c.Status(status).JSON(domain.ErrorResponse{ Message: msg, Error: err.Error(), }) } return c.JSON(domain.Response{ Message: "Practice updated successfully", Data: fullUpdatePracticeResponse(result), Success: true, StatusCode: fiber.StatusOK, }) } // UpdateExamPrepPracticeFull godoc // @Summary Full update exam-prep practice with question set and questions // @Description Updates exam-prep practice metadata, linked question set settings, and syncs questions in one request. // @Tags exam-prep // @Accept json // @Produce json // @Param id path int true "Exam prep practice ID" // @Param body body domain.FullUpdatePracticeInput true "Full practice update payload" // @Success 200 {object} domain.Response // @Failure 400 {object} domain.ErrorResponse // @Failure 404 {object} domain.ErrorResponse // @Router /api/v1/exam-prep/practices/{id}/full [put] func (h *Handler) UpdateExamPrepPracticeFull(c *fiber.Ctx) error { id, err := strconv.ParseInt(c.Params("id"), 10, 64) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid practice id", Error: err.Error(), }) } var req domain.FullUpdatePracticeInput if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid request body", Error: err.Error(), }) } result, err := h.practiceContentSvc.UpdateExamPrepPracticeFull(c.Context(), id, req) if err != nil { status, msg := mapFullUpdatePracticeError(err) return c.Status(status).JSON(domain.ErrorResponse{ Message: msg, Error: err.Error(), }) } return c.JSON(domain.Response{ Message: "Practice updated successfully", Data: fullUpdatePracticeResponse(result), Success: true, StatusCode: fiber.StatusOK, }) }