mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-04 10:42:57 +00:00
dev day #65
This commit is contained in:
@@ -0,0 +1,425 @@
|
||||
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")
|
||||
}
|
||||
Reference in New Issue
Block a user