quzuu-api-test / controllers /event_exam_controller.go
lifedebugger's picture
Deploy files from GitHub repository
ae45d89
package controllers
import (
"strconv"
"abdanhafidz.com/go-boilerplate/models/dto"
entity "abdanhafidz.com/go-boilerplate/models/entity"
http_error "abdanhafidz.com/go-boilerplate/models/error"
"abdanhafidz.com/go-boilerplate/services"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
type EventExamController interface {
Attempt(ctx *gin.Context)
Answer(ctx *gin.Context)
Submit(ctx *gin.Context)
List(ctx *gin.Context)
Scoreboard(ctx *gin.Context)
EventScoreboard(ctx *gin.Context)
}
type eventExamController struct {
eventExamService services.EventExamService
}
func NewEventExamController(eventExamService services.EventExamService) EventExamController {
return &eventExamController{
eventExamService: eventExamService,
}
}
// Exam Event Attempt godoc
// @Summary Attempt Exam Event
// @Description Start an attempt for a specific exam in an event
// @Tags Exam Event
// @Accept json
// @Produce json
// @Param event_slug path string true "Event Slug"
// @Param exam_slug path string true "Exam Slug"
// @Success 200 {object} dto.SuccessResponse[models.EventExamAttempt]
// @Failure 400 {object} dto.ErrorResponse
// @Router /api/v1/events/{event_slug}/exam/{exam_slug}/attempt [get]
func (c *eventExamController) Attempt(ctx *gin.Context) {
eventSlug := ctx.Param("event_slug")
examSlug := ctx.Param("exam_slug")
accountId := ParseAccountId(ctx)
res, err := c.eventExamService.AttemptEventExam(ctx.Request.Context(), eventSlug, examSlug, accountId)
ResponseJSON(ctx, gin.H{"event_slug": eventSlug, "exam_slug": examSlug}, res, err)
}
// Answer Exam Event godoc
// @Summary Answer Exam Event Question
// @Description Submit an answer for a specific question in an exam attempt
// @Tags Exam Event
// @Accept json
// @Produce json
// @Param event_slug path string true "Event Slug"
// @Param attempt_id path string true "Exam Attempt ID"
// @Param request body dto.AnswerEventExamRequest true "Answer Exam Event Request"
// @Success 200 {object} dto.SuccessResponse[dto.AnswerEventExamRequest]
// @Failure 400 {object} dto.ErrorResponse
// @Router /api/v1/events/{event_slug}/exam/{attempt_id}/answer_question [post]
func (c *eventExamController) Answer(ctx *gin.Context) {
eventSlug := ctx.Param("event_slug")
attemptId, err := uuid.Parse(ctx.Param("attempt_id"))
if err != nil {
ResponseJSON[any](ctx, gin.H{"attempt_id": ctx.Param("attempt_id")}, nil, http_error.INVALID_TOKEN)
return
}
req := RequestJSON[dto.AnswerEventExamRequest](ctx)
res, err := c.eventExamService.AnswerEventExam(ctx.Request.Context(), eventSlug, attemptId, req.QuestionId, req.Answer)
ResponseJSON(ctx, gin.H{"cp_grader_result": res}, req, err)
}
// Submit Exam Event godoc
// @Summary Submit Exam Event
// @Description Submit the exam attempt for evaluation
// @Tags Exam Event
// @Accept json
// @Produce json
// @Param event_slug path string true "Event Slug"
// @Param attempt_id path string true "Exam Attempt ID"
// @Success 200 {object} dto.SuccessResponse[entity.Result]
// @Failure 400 {object} dto.ErrorResponse
// @Router /api/v1/events/{event_slug}/exam/{attempt_id}/submit [post]
func (c *eventExamController) Submit(ctx *gin.Context) {
attemptId, err := uuid.Parse(ctx.Param("attempt_id"))
if err != nil {
ResponseJSON[any](ctx, gin.H{"attempt_id": ctx.Param("attempt_id")}, nil, http_error.INVALID_TOKEN)
return
}
res, err := c.eventExamService.SubmitEventExam(ctx.Request.Context(), attemptId)
ResponseJSON(ctx, gin.H{}, res, err)
}
// List Exam by Event godoc
// @Summary List Exams by Event
// @Description Retrieve a list of exams associated with a specific event
// @Tags Exam Event
// @Accept json
// @Produce json
// @Param event_slug path string true "Event Slug"
// @Success 200 {object} dto.SuccessResponse[[]models.Exam]
// @Failure 400 {object} dto.ErrorResponse
// @Router /api/v1/events/{event_slug}/exam [get]
func (c *eventExamController) List(ctx *gin.Context) {
eventSlug := ctx.Param("event_slug")
accountId := ParseAccountId(ctx)
res, err := c.eventExamService.ListExamByEvent(ctx.Request.Context(), eventSlug, accountId)
ResponseJSON(ctx, gin.H{}, res, err)
}
func parseScoreboardPagination(ctx *gin.Context, defaultSort string, defaultOrder string) (int, int, entity.Pagination) {
limit, _ := strconv.Atoi(ctx.DefaultQuery("limit", "10"))
page, _ := strconv.Atoi(ctx.DefaultQuery("page", "1"))
search := ctx.DefaultQuery("search", "")
sortBy := ctx.DefaultQuery("sortBy", defaultSort)
order := ctx.DefaultQuery("orderBy", "")
if order == "" {
order = ctx.DefaultQuery("order", defaultOrder)
}
if limit < 1 {
limit = 10
} else if limit > 100 {
limit = 100
}
if page < 1 {
page = 1
}
offset := (page - 1) * limit
return limit, page, entity.Pagination{Limit: limit, Offset: offset, Search: search, SortBy: sortBy, Order: order}
}
func buildPaginationMeta(limit int, page int, total int64) gin.H {
totalPages := 1
if total > 0 {
totalPages = int((total + int64(limit) - 1) / int64(limit))
}
if page > totalPages {
page = totalPages
}
return gin.H{
"totalItems": total,
"totalPages": totalPages,
"currentPage": page,
"limit": limit,
}
}
// Scoreboard godoc
// @Summary Exam Scoreboard
// @Description Retrieve a paginated scoreboard of participants ranked by their performance in a specific exam within an event
// @Tags Exam Event
// @Accept json
// @Produce json
// @Param event_slug path string true "Event Slug"
// @Param exam_slug path string true "Exam Slug"
// @Param limit query int false "Number of items per page" default(10)
// @Param page query int false "Page number" default(1)
// @Param search query string false "Search keyword"
// @Param sortBy query string false "Sort field: 'score' (default) or 'duration'"
// @Param orderBy query string false "Sort order: 'asc' or 'desc'"
// @Param order query string false "Sort order alias: 'asc' or 'desc'"
// @Success 200 {object} dto.SuccessResponse[[]dto.ExamScoreboardItem]
// @Failure 400 {object} dto.ErrorResponse
// @Security BearerAuth
// @Router /api/v1/events/{event_slug}/{exam_slug}/scoreboard [get]
func (c *eventExamController) Scoreboard(ctx *gin.Context) {
eventSlug := ctx.Param("event_slug")
examSlug := ctx.Param("exam_slug")
accountId := ParseAccountId(ctx)
limit, page, p := parseScoreboardPagination(ctx, "score", "desc")
list, total, err := c.eventExamService.Scoreboard(ctx.Request.Context(), eventSlug, examSlug, accountId, p)
meta := buildPaginationMeta(limit, page, total)
ResponseJSON(ctx, meta, list, err)
}
// Event Scoreboard godoc
// @Summary Event Scoreboard
// @Description Retrieve a paginated scoreboard of participants based on total score across all exams in an event
// @Tags Exam Event
// @Accept json
// @Produce json
// @Param event_slug path string true "Event Slug"
// @Param limit query int false "Number of items per page" default(10)
// @Param page query int false "Page number" default(1)
// @Param search query string false "Search keyword"
// @Param sortBy query string false "Sort field: 'total_score' (default), 'username', or 'full_name'"
// @Param orderBy query string false "Sort order: 'asc' or 'desc'"
// @Param order query string false "Sort order alias: 'asc' or 'desc'"
// @Success 200 {object} dto.SuccessResponse[[]dto.EventScoreboardItem]
// @Failure 400 {object} dto.ErrorResponse
// @Security BearerAuth
// @Router /api/v1/events/{event_slug}/scoreboard [get]
func (c *eventExamController) EventScoreboard(ctx *gin.Context) {
eventSlug := ctx.Param("event_slug")
accountId := ParseAccountId(ctx)
limit, page, p := parseScoreboardPagination(ctx, "total_score", "desc")
list, total, err := c.eventExamService.EventScoreboard(ctx.Request.Context(), eventSlug, accountId, p)
meta := buildPaginationMeta(limit, page, total)
ResponseJSON(ctx, meta, list, err)
}