sno-quiz/backend/internal/handlers/quiz_handler.go
2025-09-17 22:22:14 +03:00

327 lines
9.7 KiB
Go

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",
})
}