package handler import ( "context" "net/http" "strconv" "eslogad-be/internal/appcontext" "eslogad-be/internal/constants" "eslogad-be/internal/contract" "eslogad-be/internal/logger" "github.com/gin-gonic/gin" "github.com/google/uuid" ) type VoteEventService interface { CreateVoteEvent(ctx context.Context, req *contract.CreateVoteEventRequest) (*contract.VoteEventResponse, error) GetVoteEventByID(ctx context.Context, id uuid.UUID) (*contract.VoteEventResponse, error) GetActiveEvents(ctx context.Context) ([]contract.VoteEventResponse, error) ListVoteEvents(ctx context.Context, req *contract.ListVoteEventsRequest) (*contract.ListVoteEventsResponse, error) UpdateVoteEvent(ctx context.Context, id uuid.UUID, req *contract.UpdateVoteEventRequest) (*contract.VoteEventResponse, error) DeleteVoteEvent(ctx context.Context, id uuid.UUID) error CreateCandidate(ctx context.Context, req *contract.CreateCandidateRequest) (*contract.CandidateResponse, error) GetCandidates(ctx context.Context, eventID uuid.UUID) ([]contract.CandidateResponse, error) SubmitVote(ctx context.Context, userID uuid.UUID, req *contract.SubmitVoteRequest) (*contract.VoteResponse, error) GetVoteResults(ctx context.Context, eventID uuid.UUID) (*contract.VoteResultsResponse, error) CheckVoteStatus(ctx context.Context, userID, eventID uuid.UUID) (*contract.CheckVoteStatusResponse, error) } type VoteEventHandler struct { voteEventService VoteEventService } func NewVoteEventHandler(voteEventService VoteEventService) *VoteEventHandler { return &VoteEventHandler{ voteEventService: voteEventService, } } func (h *VoteEventHandler) CreateVoteEvent(c *gin.Context) { var req contract.CreateVoteEventRequest if err := c.ShouldBindJSON(&req); err != nil { logger.FromContext(c).WithError(err).Error("VoteEventHandler::CreateVoteEvent -> request binding failed") h.sendValidationErrorResponse(c, "Invalid request body", constants.MissingFieldErrorCode) return } voteEventResponse, err := h.voteEventService.CreateVoteEvent(c.Request.Context(), &req) if err != nil { logger.FromContext(c).WithError(err).Error("VoteEventHandler::CreateVoteEvent -> Failed to create vote event") h.sendErrorResponse(c, err.Error(), http.StatusInternalServerError) return } logger.FromContext(c).Infof("VoteEventHandler::CreateVoteEvent -> Successfully created vote event = %+v", voteEventResponse) c.JSON(http.StatusCreated, contract.BuildSuccessResponse(voteEventResponse)) } func (h *VoteEventHandler) GetVoteEvent(c *gin.Context) { eventIDStr := c.Param("id") eventID, err := uuid.Parse(eventIDStr) if err != nil { logger.FromContext(c).WithError(err).Error("VoteEventHandler::GetVoteEvent -> Invalid event ID") h.sendValidationErrorResponse(c, "Invalid event ID", constants.MalformedFieldErrorCode) return } voteEventResponse, err := h.voteEventService.GetVoteEventByID(c.Request.Context(), eventID) if err != nil { logger.FromContext(c).WithError(err).Error("VoteEventHandler::GetVoteEvent -> Failed to get vote event") h.sendErrorResponse(c, err.Error(), http.StatusInternalServerError) return } logger.FromContext(c).Infof("VoteEventHandler::GetVoteEvent -> Successfully retrieved vote event = %+v", voteEventResponse) c.JSON(http.StatusOK, contract.BuildSuccessResponse(voteEventResponse)) } func (h *VoteEventHandler) GetActiveEvents(c *gin.Context) { events, err := h.voteEventService.GetActiveEvents(c.Request.Context()) if err != nil { logger.FromContext(c).WithError(err).Error("VoteEventHandler::GetActiveEvents -> Failed to get active events") h.sendErrorResponse(c, err.Error(), http.StatusInternalServerError) return } logger.FromContext(c).Infof("VoteEventHandler::GetActiveEvents -> Successfully retrieved %d active events", len(events)) c.JSON(http.StatusOK, contract.BuildSuccessResponse(map[string]interface{}{ "events": events, "count": len(events), })) } func (h *VoteEventHandler) ListVoteEvents(c *gin.Context) { req := &contract.ListVoteEventsRequest{ Page: 1, Limit: 10, } if page := c.Query("page"); page != "" { if p, err := strconv.Atoi(page); err == nil && p > 0 { req.Page = p } } if limit := c.Query("limit"); limit != "" { if l, err := strconv.Atoi(limit); err == nil && l > 0 && l <= 100 { req.Limit = l } } voteEventsResponse, err := h.voteEventService.ListVoteEvents(c.Request.Context(), req) if err != nil { logger.FromContext(c).WithError(err).Error("VoteEventHandler::ListVoteEvents -> Failed to list vote events") h.sendErrorResponse(c, err.Error(), http.StatusInternalServerError) return } logger.FromContext(c).Infof("VoteEventHandler::ListVoteEvents -> Successfully listed vote events") c.JSON(http.StatusOK, contract.BuildSuccessResponse(voteEventsResponse)) } func (h *VoteEventHandler) UpdateVoteEvent(c *gin.Context) { eventIDStr := c.Param("id") eventID, err := uuid.Parse(eventIDStr) if err != nil { logger.FromContext(c).WithError(err).Error("VoteEventHandler::UpdateVoteEvent -> Invalid event ID") h.sendValidationErrorResponse(c, "Invalid event ID", constants.MalformedFieldErrorCode) return } var req contract.UpdateVoteEventRequest if err := c.ShouldBindJSON(&req); err != nil { logger.FromContext(c).WithError(err).Error("VoteEventHandler::UpdateVoteEvent -> request binding failed") h.sendValidationErrorResponse(c, "Invalid request body", constants.MissingFieldErrorCode) return } voteEventResponse, err := h.voteEventService.UpdateVoteEvent(c.Request.Context(), eventID, &req) if err != nil { logger.FromContext(c).WithError(err).Error("VoteEventHandler::UpdateVoteEvent -> Failed to update vote event") h.sendErrorResponse(c, err.Error(), http.StatusInternalServerError) return } logger.FromContext(c).Infof("VoteEventHandler::UpdateVoteEvent -> Successfully updated vote event = %+v", voteEventResponse) c.JSON(http.StatusOK, contract.BuildSuccessResponse(voteEventResponse)) } func (h *VoteEventHandler) DeleteVoteEvent(c *gin.Context) { eventIDStr := c.Param("id") eventID, err := uuid.Parse(eventIDStr) if err != nil { logger.FromContext(c).WithError(err).Error("VoteEventHandler::DeleteVoteEvent -> Invalid event ID") h.sendValidationErrorResponse(c, "Invalid event ID", constants.MalformedFieldErrorCode) return } err = h.voteEventService.DeleteVoteEvent(c.Request.Context(), eventID) if err != nil { logger.FromContext(c).WithError(err).Error("VoteEventHandler::DeleteVoteEvent -> Failed to delete vote event") h.sendErrorResponse(c, err.Error(), http.StatusInternalServerError) return } logger.FromContext(c).Info("VoteEventHandler::DeleteVoteEvent -> Successfully deleted vote event") c.JSON(http.StatusOK, contract.BuildSuccessResponse(map[string]string{ "message": "Vote event deleted successfully", })) } func (h *VoteEventHandler) CreateCandidate(c *gin.Context) { var req contract.CreateCandidateRequest if err := c.ShouldBindJSON(&req); err != nil { logger.FromContext(c).WithError(err).Error("VoteEventHandler::CreateCandidate -> request binding failed") h.sendValidationErrorResponse(c, "Invalid request body", constants.MissingFieldErrorCode) return } candidateResponse, err := h.voteEventService.CreateCandidate(c.Request.Context(), &req) if err != nil { logger.FromContext(c).WithError(err).Error("VoteEventHandler::CreateCandidate -> Failed to create candidate") h.sendErrorResponse(c, err.Error(), http.StatusInternalServerError) return } logger.FromContext(c).Infof("VoteEventHandler::CreateCandidate -> Successfully created candidate = %+v", candidateResponse) c.JSON(http.StatusCreated, contract.BuildSuccessResponse(candidateResponse)) } func (h *VoteEventHandler) GetCandidates(c *gin.Context) { eventIDStr := c.Param("id") eventID, err := uuid.Parse(eventIDStr) if err != nil { logger.FromContext(c).WithError(err).Error("VoteEventHandler::GetCandidates -> Invalid event ID") h.sendValidationErrorResponse(c, "Invalid event ID", constants.MalformedFieldErrorCode) return } candidates, err := h.voteEventService.GetCandidates(c.Request.Context(), eventID) if err != nil { logger.FromContext(c).WithError(err).Error("VoteEventHandler::GetCandidates -> Failed to get candidates") h.sendErrorResponse(c, err.Error(), http.StatusInternalServerError) return } logger.FromContext(c).Infof("VoteEventHandler::GetCandidates -> Successfully retrieved %d candidates", len(candidates)) c.JSON(http.StatusOK, contract.BuildSuccessResponse(map[string]interface{}{ "candidates": candidates, "count": len(candidates), })) } func (h *VoteEventHandler) SubmitVote(c *gin.Context) { appCtx := appcontext.FromGinContext(c.Request.Context()) if appCtx.UserID == uuid.Nil { h.sendErrorResponse(c, "Unauthorized", http.StatusUnauthorized) return } var req contract.SubmitVoteRequest if err := c.ShouldBindJSON(&req); err != nil { logger.FromContext(c).WithError(err).Error("VoteEventHandler::SubmitVote -> request binding failed") h.sendValidationErrorResponse(c, "Invalid request body", constants.MissingFieldErrorCode) return } voteResponse, err := h.voteEventService.SubmitVote(c.Request.Context(), appCtx.UserID, &req) if err != nil { logger.FromContext(c).WithError(err).Error("VoteEventHandler::SubmitVote -> Failed to submit vote") h.sendErrorResponse(c, err.Error(), http.StatusBadRequest) return } logger.FromContext(c).Infof("VoteEventHandler::SubmitVote -> Successfully submitted vote = %+v", voteResponse) c.JSON(http.StatusCreated, contract.BuildSuccessResponse(voteResponse)) } func (h *VoteEventHandler) GetVoteResults(c *gin.Context) { eventIDStr := c.Param("id") eventID, err := uuid.Parse(eventIDStr) if err != nil { logger.FromContext(c).WithError(err).Error("VoteEventHandler::GetVoteResults -> Invalid event ID") h.sendValidationErrorResponse(c, "Invalid event ID", constants.MalformedFieldErrorCode) return } voteEvent, err := h.voteEventService.GetVoteEventByID(c.Request.Context(), eventID) if err != nil { logger.FromContext(c).WithError(err).Error("VoteEventHandler::GetVoteResults -> Failed to get vote event") h.sendErrorResponse(c, err.Error(), http.StatusInternalServerError) return } if !voteEvent.ResultsOpen { logger.FromContext(c).Info("VoteEventHandler::GetVoteResults -> Results not open for viewing") h.sendErrorResponse(c, "Results are not open for viewing", http.StatusForbidden) return } results, err := h.voteEventService.GetVoteResults(c.Request.Context(), eventID) if err != nil { logger.FromContext(c).WithError(err).Error("VoteEventHandler::GetVoteResults -> Failed to get vote results") h.sendErrorResponse(c, err.Error(), http.StatusInternalServerError) return } logger.FromContext(c).Infof("VoteEventHandler::GetVoteResults -> Successfully retrieved vote results") c.JSON(http.StatusOK, contract.BuildSuccessResponse(results)) } func (h *VoteEventHandler) CheckVoteStatus(c *gin.Context) { appCtx := appcontext.FromGinContext(c.Request.Context()) if appCtx.UserID == uuid.Nil { h.sendErrorResponse(c, "Unauthorized", http.StatusUnauthorized) return } eventIDStr := c.Param("id") eventID, err := uuid.Parse(eventIDStr) if err != nil { logger.FromContext(c).WithError(err).Error("VoteEventHandler::CheckVoteStatus -> Invalid event ID") h.sendValidationErrorResponse(c, "Invalid event ID", constants.MalformedFieldErrorCode) return } status, err := h.voteEventService.CheckVoteStatus(c.Request.Context(), appCtx.UserID, eventID) if err != nil { logger.FromContext(c).WithError(err).Error("VoteEventHandler::CheckVoteStatus -> Failed to check vote status") h.sendErrorResponse(c, err.Error(), http.StatusInternalServerError) return } logger.FromContext(c).Infof("VoteEventHandler::CheckVoteStatus -> Successfully checked vote status") c.JSON(http.StatusOK, contract.BuildSuccessResponse(status)) } func (h *VoteEventHandler) sendErrorResponse(c *gin.Context, message string, statusCode int) { errorResponse := &contract.ErrorResponse{ Error: message, Code: statusCode, Details: map[string]interface{}{}, } c.JSON(statusCode, errorResponse) } func (h *VoteEventHandler) sendValidationErrorResponse(c *gin.Context, message string, errorCode string) { statusCode := constants.HttpErrorMap[errorCode] if statusCode == 0 { statusCode = http.StatusBadRequest } errorResponse := &contract.ErrorResponse{ Error: message, Code: statusCode, Details: map[string]interface{}{ "error_code": errorCode, "entity": "vote_event", }, } c.JSON(statusCode, errorResponse) }