Spaces:
Running
Running
File size: 8,852 Bytes
5d4252b d0e1b44 5d4252b 132b9f0 d0e1b44 5d4252b ae45d89 5d4252b d0e1b44 5d4252b d0e1b44 5d4252b 132b9f0 d0e1b44 132b9f0 d0e1b44 132b9f0 d0e1b44 132b9f0 d0e1b44 132b9f0 d0e1b44 132b9f0 d0e1b44 132b9f0 d0e1b44 132b9f0 d0e1b44 132b9f0 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 | 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)
}
|