package handlers import ( "errors" "log" "sno/internal/middleware" "sno/internal/models" "sno/internal/service" "strconv" "github.com/gofiber/fiber/v2" "github.com/jackc/pgx/v5" ) // QuizHandler handles HTTP requests for quizzes. type QuizHandler struct { quizService service.QuizService userService service.UserService } // NewQuizHandler creates a new instance of a quiz handler. func NewQuizHandler(quizService service.QuizService, userService service.UserService) *QuizHandler { return &QuizHandler{quizService: quizService, userService: userService} } // GetAllQuizzes handles the request to get all active quizzes. // @Summary Get all active quizzes // @Description Returns a list of all active quizzes // @Tags quizzes // @Accept json // @Produce json // @Success 200 {object} object{success=bool,message=string,data=[]models.Quiz} // @Failure 500 {object} object{success=bool,message=string} // @Router /api/quizzes [get] // @Security ApiKeyAuth func (h *QuizHandler) GetAllQuizzes(c *fiber.Ctx) error { quizzes, err := h.quizService.ListActiveQuizzes(c.Context()) if err != nil { log.Printf("ERROR: Failed to retrieve quizzes: %v", err) return c.Status(fiber.StatusInternalServerError).JSON(Response{ Success: false, Message: "Failed to retrieve quizzes", }) } return c.Status(fiber.StatusOK).JSON(Response{ Success: true, Message: "Quizzes retrieved successfully", Data: quizzes, }) } // CreateQuiz handles the request to create a new quiz. // @Summary Create a new quiz // @Description Creates a new quiz (admin/operator only) // @Tags quizzes // @Accept json // @Produce json // @Param quiz body models.Quiz true "Quiz object" // @Success 201 {object} object{success=bool,message=string,data=models.Quiz} // @Failure 400 {object} object{success=bool,message=string} // @Failure 500 {object} object{success=bool,message=string} // @Router /api/admin/quizzes [post] // @Security ApiKeyAuth func (h *QuizHandler) CreateQuiz(c *fiber.Ctx) error { var quiz models.Quiz if err := c.BodyParser(&quiz); err != nil { return c.Status(fiber.StatusBadRequest).JSON(Response{ Success: false, Message: "Cannot parse JSON", }) } createdQuiz, err := h.quizService.CreateQuiz(c.Context(), &quiz) if err != nil { log.Printf("ERROR: Failed to create quiz: %v", err) return c.Status(fiber.StatusInternalServerError).JSON(Response{ Success: false, Message: "Failed to create quiz", }) } return c.Status(fiber.StatusCreated).JSON(Response{ Success: true, Message: "Quiz created successfully", Data: createdQuiz, }) } // GetQuizByID handles the request to get a single quiz by its ID. // @Summary Get quiz by ID // @Description Returns a single quiz with all its questions // @Tags quizzes // @Accept json // @Produce json // @Param id path int true "Quiz ID" // @Success 200 {object} object{success=bool,message=string,data=models.Quiz} // @Failure 400 {object} object{success=bool,message=string} // @Failure 404 {object} object{success=bool,message=string} // @Failure 500 {object} object{success=bool,message=string} // @Router /api/quizzes/{id} [get] // @Security ApiKeyAuth func (h *QuizHandler) GetQuizByID(c *fiber.Ctx) error { id, err := strconv.Atoi(c.Params("id")) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(Response{ Success: false, Message: "Invalid quiz ID", }) } quiz, err := h.quizService.GetQuizByID(c.Context(), id) if err != nil { if errors.Is(err, pgx.ErrNoRows) { return c.Status(fiber.StatusNotFound).JSON(Response{ Success: false, Message: "Quiz not found", }) } log.Printf("ERROR: Failed to get quiz by ID: %v", err) return c.Status(fiber.StatusInternalServerError).JSON(Response{ Success: false, Message: "Failed to retrieve quiz", }) } return c.Status(fiber.StatusOK).JSON(Response{ Success: true, Message: "Quiz retrieved successfully", Data: quiz, }) } // SubmitQuiz handles the submission of quiz answers. // @Summary Submit quiz answers // @Description Submits quiz answers and calculates score/stars earned // @Tags quizzes // @Accept json // @Produce json // @Param id path int true "Quiz ID" // @Param submission body models.SubmissionRequest true "Quiz submission" // @Success 200 {object} object{success=bool,message=string,data=object} // @Failure 400 {object} object{success=bool,message=string} // @Failure 500 {object} object{success=bool,message=string} // @Router /api/quizzes/{id}/submit [post] // @Security ApiKeyAuth func (h *QuizHandler) SubmitQuiz(c *fiber.Ctx) error { quizID, err := strconv.Atoi(c.Params("id")) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(Response{ Success: false, Message: "Invalid quiz ID", }) } var submission models.SubmissionRequest if err := c.BodyParser(&submission); err != nil { return c.Status(fiber.StatusBadRequest).JSON(Response{ Success: false, Message: "Cannot parse submission JSON", }) } // Get user data from auth middleware userData := middleware.GetTelegramUser(c) if userData == nil { return c.Status(fiber.StatusUnauthorized).JSON(Response{ Success: false, Message: "User not authenticated", }) } // Ensure user exists in database _, err = h.userService.GetOrCreateUser(c.Context(), userData.ID, userData.FirstName, userData.LastName, userData.Username, userData.PhotoURL) if err != nil { log.Printf("ERROR: Failed to get or create user: %v", err) return c.Status(fiber.StatusInternalServerError).JSON(Response{ Success: false, Message: "Failed to create user profile", }) } result, err := h.quizService.SubmitQuiz(c.Context(), userData.ID, quizID, submission) if err != nil { log.Printf("ERROR: Failed to submit quiz: %v", err) return c.Status(fiber.StatusInternalServerError).JSON(Response{ Success: false, Message: err.Error(), }) } return c.Status(fiber.StatusOK).JSON(Response{ Success: true, Message: "Quiz submitted successfully", Data: result, }) } // CanRepeat handles the request to check if a user can repeat a quiz. // @Summary Check if quiz can be repeated // @Description Checks if user can repeat a quiz and when it will be available // @Tags quizzes // @Accept json // @Produce json // @Param id path int true "Quiz ID" // @Success 200 {object} object{success=bool,message=string,data=models.CanRepeatResponse} // @Failure 400 {object} object{success=bool,message=string} // @Failure 500 {object} object{success=bool,message=string} // @Router /api/quizzes/{id}/can-repeat [get] // @Security ApiKeyAuth func (h *QuizHandler) CanRepeat(c *fiber.Ctx) error { quizID, err := strconv.Atoi(c.Params("id")) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(Response{ Success: false, Message: "Invalid quiz ID", }) } // Get user data from auth middleware userData := middleware.GetTelegramUser(c) if userData == nil { return c.Status(fiber.StatusUnauthorized).JSON(Response{ Success: false, Message: "User not authenticated", }) } canRepeatResponse, err := h.quizService.CanUserRepeatQuiz(c.Context(), userData.ID, quizID) if err != nil { log.Printf("ERROR: Failed to check if quiz can be repeated: %v", err) return c.Status(fiber.StatusInternalServerError).JSON(Response{ Success: false, Message: "Failed to check quiz status", }) } return c.Status(fiber.StatusOK).JSON(Response{ Success: true, Message: "Quiz status retrieved successfully", Data: canRepeatResponse, }) } // UpdateQuiz handles the request to update an existing quiz. // @Summary Update a quiz // @Description Updates an existing quiz (admin only) // @Tags quizzes // @Accept json // @Produce json // @Param id path int true "Quiz ID" // @Param quiz body models.Quiz true "Updated quiz object" // @Success 200 {object} object{success=bool,message=string,data=models.Quiz} // @Failure 400 {object} object{success=bool,message=string} // @Failure 500 {object} object{success=bool,message=string} // @Router /api/admin/quizzes/{id} [put] // @Security ApiKeyAuth func (h *QuizHandler) UpdateQuiz(c *fiber.Ctx) error { id, err := strconv.Atoi(c.Params("id")) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(Response{ Success: false, Message: "Invalid quiz ID", }) } var quiz models.Quiz if err := c.BodyParser(&quiz); err != nil { return c.Status(fiber.StatusBadRequest).JSON(Response{ Success: false, Message: "Cannot parse JSON", }) } updatedQuiz, err := h.quizService.UpdateQuiz(c.Context(), id, &quiz) if err != nil { log.Printf("ERROR: Failed to update quiz: %v", err) return c.Status(fiber.StatusInternalServerError).JSON(Response{ Success: false, Message: err.Error(), }) } return c.Status(fiber.StatusOK).JSON(Response{ Success: true, Message: "Quiz updated successfully", Data: updatedQuiz, }) } // DeleteQuiz handles the request to delete a quiz. // @Summary Delete a quiz // @Description Deletes a quiz (admin only) // @Tags quizzes // @Accept json // @Produce json // @Param id path int true "Quiz ID" // @Success 200 {object} object{success=bool,message=string} // @Failure 400 {object} object{success=bool,message=string} // @Failure 500 {object} object{success=bool,message=string} // @Router /api/admin/quizzes/{id} [delete] // @Security ApiKeyAuth func (h *QuizHandler) DeleteQuiz(c *fiber.Ctx) error { id, err := strconv.Atoi(c.Params("id")) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(Response{ Success: false, Message: "Invalid quiz ID", }) } err = h.quizService.DeleteQuiz(c.Context(), id) if err != nil { log.Printf("ERROR: Failed to delete quiz: %v", err) return c.Status(fiber.StatusInternalServerError).JSON(Response{ Success: false, Message: err.Error(), }) } return c.Status(fiber.StatusOK).JSON(Response{ Success: true, Message: "Quiz deleted successfully", }) }