Files
MyClub/internal/controllers/poll_controller_refactored.go
T
Tomas Dvorak 68e69e00cc dev day #65,5
2025-10-20 10:40:55 +02:00

426 lines
10 KiB
Go

package controllers
import (
"fotbal-club/internal/models"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
// PollControllerRefactored demonstrates refactored poll controller using utility helpers
// This is an example showing how to use the new utilities
type PollControllerRefactored struct {
DB *gorm.DB
}
// NewPollControllerRefactored creates a new refactored poll controller
func NewPollControllerRefactored(db *gorm.DB) *PollControllerRefactored {
return &PollControllerRefactored{DB: db}
}
// GetPolls returns paginated list of polls with filtering and search
// GET /api/v1/polls?search=election&active=true&sort=created_at:desc&page=1&page_size=20
func (pcr *PollControllerRefactored) GetPolls(c *gin.Context) {
// Build query with search, filter, and sort
query := QueryParser.BuildQueryChain(c, pcr.DB.Model(&models.Poll{})).
WithSearch("title", "description").
WithSort("created_at", "desc").
WithBoolFilter("active", "active").
Build()
// Paginate and preload relationships
var polls []models.Poll
meta, err := Paginator.PaginateWithPreload(c, query, &polls, "Options")
if err != nil {
Respond.InternalError(c, "Failed to retrieve polls")
return
}
Respond.SuccessWithMeta(c, polls, meta, "Polls retrieved successfully")
}
// GetPoll returns a single poll by ID
// GET /api/v1/polls/:id
func (pcr *PollControllerRefactored) GetPoll(c *gin.Context) {
id := c.Param("id")
var poll models.Poll
if err := pcr.DB.Preload("Options").First(&poll, id).Error; err != nil {
if err == gorm.ErrRecordNotFound {
Respond.NotFound(c, "Poll not found")
return
}
Respond.InternalError(c, "Failed to retrieve poll")
return
}
Respond.Success(c, poll, "Poll retrieved successfully")
}
// CreatePoll creates a new poll with validation
// POST /api/v1/polls
func (pcr *PollControllerRefactored) CreatePoll(c *gin.Context) {
type CreatePollRequest struct {
Title string `json:"title" validate:"required,min=3,max=200"`
Description string `json:"description" validate:"omitempty,max=500"`
Active *bool `json:"active"`
MultiChoice *bool `json:"multi_choice"`
Options []string `json:"options" validate:"required,min=2"`
}
var req CreatePollRequest
if err := c.ShouldBindJSON(&req); err != nil {
Respond.BadRequest(c, "Invalid JSON: "+err.Error())
return
}
// Validate request
if !Validator.ValidateAndRespond(c, req) {
return
}
// Sanitize inputs
req.Title = Validator.SanitizeString(req.Title)
req.Description = Validator.SanitizeString(req.Description)
// Set defaults
active := true
if req.Active != nil {
active = *req.Active
}
multiChoice := false
if req.MultiChoice != nil {
multiChoice = *req.MultiChoice
}
status := "draft"
if active {
status = "active"
}
maxChoices := 1
if multiChoice {
maxChoices = len(req.Options)
if maxChoices == 0 {
maxChoices = 1
}
}
// Create poll
poll := models.Poll{
Title: req.Title,
Description: req.Description,
Status: status,
AllowMultiple: multiChoice,
MaxChoices: maxChoices,
}
if err := pcr.DB.Create(&poll).Error; err != nil {
Respond.InternalError(c, "Failed to create poll")
return
}
// Create options
for _, optionText := range req.Options {
option := models.PollOption{
PollID: poll.ID,
Text: Validator.SanitizeString(optionText),
}
if err := pcr.DB.Create(&option).Error; err != nil {
Respond.InternalError(c, "Failed to create poll option")
return
}
}
// Reload with options
pcr.DB.Preload("Options").First(&poll, poll.ID)
// Log creation
if AuditLogger != nil {
_ = AuditLogger.LogCreate(c, "Poll", poll.ID, "Poll created: "+poll.Title)
}
Respond.Created(c, poll, "Poll created successfully")
}
// UpdatePoll updates an existing poll
// PUT /api/v1/polls/:id
func (pcr *PollControllerRefactored) UpdatePoll(c *gin.Context) {
id := c.Param("id")
var poll models.Poll
if err := pcr.DB.First(&poll, id).Error; err != nil {
if err == gorm.ErrRecordNotFound {
Respond.NotFound(c, "Poll not found")
return
}
Respond.InternalError(c, "Failed to retrieve poll")
return
}
// Store old values for audit
oldTitle := poll.Title
oldStatus := poll.Status
type UpdatePollRequest struct {
Title string `json:"title" validate:"omitempty,min=3,max=200"`
Description string `json:"description" validate:"omitempty,max=500"`
Active *bool `json:"active"`
MultiChoice *bool `json:"multi_choice"`
}
var req UpdatePollRequest
if err := c.ShouldBindJSON(&req); err != nil {
Respond.BadRequest(c, "Invalid JSON: "+err.Error())
return
}
if !Validator.ValidateAndRespond(c, req) {
return
}
// Apply updates
if req.Title != "" {
poll.Title = Validator.SanitizeString(req.Title)
}
if req.Description != "" {
poll.Description = Validator.SanitizeString(req.Description)
}
if req.Active != nil {
if *req.Active {
poll.Status = "active"
} else {
poll.Status = "draft"
}
}
if req.MultiChoice != nil {
poll.AllowMultiple = *req.MultiChoice
if poll.AllowMultiple && poll.MaxChoices < 1 {
poll.MaxChoices = len(poll.Options)
if poll.MaxChoices == 0 {
poll.MaxChoices = 1
}
}
if !poll.AllowMultiple {
poll.MaxChoices = 1
}
}
if err := pcr.DB.Save(&poll).Error; err != nil {
Respond.InternalError(c, "Failed to update poll")
return
}
// Log update
if AuditLogger != nil {
before := map[string]interface{}{
"title": oldTitle,
"status": oldStatus,
}
after := map[string]interface{}{
"title": poll.Title,
"status": poll.Status,
}
_ = AuditLogger.LogUpdate(c, "Poll", poll.ID, "Poll updated", before, after)
}
Respond.Success(c, poll, "Poll updated successfully")
}
// DeletePoll deletes a poll
// DELETE /api/v1/polls/:id
func (pcr *PollControllerRefactored) DeletePoll(c *gin.Context) {
id := c.Param("id")
var poll models.Poll
if err := pcr.DB.First(&poll, id).Error; err != nil {
if err == gorm.ErrRecordNotFound {
Respond.NotFound(c, "Poll not found")
return
}
Respond.InternalError(c, "Failed to retrieve poll")
return
}
title := poll.Title
pollID := poll.ID
// Delete associated options first
if err := pcr.DB.Where("poll_id = ?", poll.ID).Delete(&models.PollOption{}).Error; err != nil {
Respond.InternalError(c, "Failed to delete poll options")
return
}
// Delete poll
if err := pcr.DB.Delete(&poll).Error; err != nil {
Respond.InternalError(c, "Failed to delete poll")
return
}
// Log deletion
if AuditLogger != nil {
_ = AuditLogger.LogDelete(c, "Poll", pollID, "Poll deleted: "+title)
}
Respond.NoContent(c)
}
// BatchDeletePolls deletes multiple polls
// POST /api/v1/polls/batch-delete
// Body: {"ids": [1, 2, 3]}
func (pcr *PollControllerRefactored) BatchDeletePolls(c *gin.Context) {
if BatchOps == nil {
BatchOps = NewBatchOperationsController(pcr.DB)
}
BatchOps.BatchDelete(c, &models.Poll{}, "polls")
}
// BatchActivatePolls activates multiple polls
// POST /api/v1/polls/batch-activate
// Body: {"ids": [1, 2, 3]}
func (pcr *PollControllerRefactored) BatchActivatePolls(c *gin.Context) {
if BatchOps == nil {
BatchOps = NewBatchOperationsController(pcr.DB)
}
BatchOps.BatchUpdate(c, &models.Poll{}, "polls", []string{"active"})
}
// VotePoll records a vote on a poll
// POST /api/v1/polls/:id/vote
// Body: {"option_id": 1} or {"option_ids": [1, 2]} for multi-choice
func (pcr *PollControllerRefactored) VotePoll(c *gin.Context) {
id := c.Param("id")
var poll models.Poll
if err := pcr.DB.Preload("Options").First(&poll, id).Error; err != nil {
if err == gorm.ErrRecordNotFound {
Respond.NotFound(c, "Poll not found")
return
}
Respond.InternalError(c, "Failed to retrieve poll")
return
}
if !poll.IsActive() {
Respond.BadRequest(c, "This poll is not active")
return
}
var req struct {
OptionID *uint `json:"option_id"`
OptionIDs []uint `json:"option_ids"`
}
if err := c.ShouldBindJSON(&req); err != nil {
Respond.BadRequest(c, "Invalid JSON: "+err.Error())
return
}
// Determine which options to vote for
var optionIDs []uint
if req.OptionID != nil {
optionIDs = []uint{*req.OptionID}
} else if len(req.OptionIDs) > 0 {
optionIDs = req.OptionIDs
} else {
Respond.BadRequest(c, "No option selected")
return
}
// Validate multi-choice
if !poll.AllowMultiple && len(optionIDs) > 1 {
Respond.BadRequest(c, "This poll does not allow multiple choices")
return
}
if poll.AllowMultiple && poll.MaxChoices > 0 && len(optionIDs) > poll.MaxChoices {
Respond.BadRequest(c, "You have selected too many options")
return
}
// Get user ID (if authenticated)
var userID *uint
if user, exists := c.Get("user"); exists {
if u, ok := user.(*models.User); ok && u != nil {
userID = &u.ID
}
}
// Check if already voted (if user is authenticated)
if userID != nil {
var existingVote models.PollVote
if err := pcr.DB.Where("poll_id = ? AND user_id = ?", poll.ID, userID).First(&existingVote).Error; err == nil {
Respond.Conflict(c, "You have already voted on this poll")
return
}
}
// Record votes
for _, optionID := range optionIDs {
vote := models.PollVote{
PollID: poll.ID,
OptionID: optionID,
UserID: userID,
}
if err := pcr.DB.Create(&vote).Error; err != nil {
Respond.InternalError(c, "Failed to record vote")
return
}
}
// Log vote
if AuditLogger != nil {
_ = AuditLogger.LogEntry(c, "VOTE", "Poll", &poll.ID, "Vote recorded on poll: "+poll.Title, nil)
}
Respond.Success(c, gin.H{"voted": true}, "Vote recorded successfully")
}
// GetPollResults returns poll results
// GET /api/v1/polls/:id/results
func (pcr *PollControllerRefactored) GetPollResults(c *gin.Context) {
id := c.Param("id")
var poll models.Poll
if err := pcr.DB.Preload("Options").First(&poll, id).Error; err != nil {
if err == gorm.ErrRecordNotFound {
Respond.NotFound(c, "Poll not found")
return
}
Respond.InternalError(c, "Failed to retrieve poll")
return
}
// Count votes for each option
type OptionResult struct {
OptionID uint `json:"option_id"`
OptionText string `json:"option_text"`
VoteCount int64 `json:"vote_count"`
}
results := make([]OptionResult, 0, len(poll.Options))
var totalVotes int64
for _, option := range poll.Options {
var count int64
pcr.DB.Model(&models.PollVote{}).Where("option_id = ?", option.ID).Count(&count)
results = append(results, OptionResult{
OptionID: option.ID,
OptionText: option.Text,
VoteCount: count,
})
totalVotes += count
}
response := gin.H{
"poll_id": poll.ID,
"poll_title": poll.Title,
"total_votes": totalVotes,
"results": results,
}
Respond.Success(c, response, "Poll results retrieved successfully")
}