mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-03 18:22:57 +00:00
426 lines
10 KiB
Go
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")
|
|
}
|