mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-04 02:32:57 +00:00
upload
This commit is contained in:
@@ -0,0 +1,622 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"fotbal-club/internal/models"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type PollController struct {
|
||||
DB *gorm.DB
|
||||
}
|
||||
|
||||
func NewPollController(db *gorm.DB) *PollController {
|
||||
return &PollController{DB: db}
|
||||
}
|
||||
|
||||
// GetPolls returns list of polls (admin or public)
|
||||
func (pc *PollController) GetPolls(c *gin.Context) {
|
||||
var polls []models.Poll
|
||||
query := pc.DB.Preload("Options").Preload("Category").Preload("Creator").
|
||||
Preload("RelatedArticle").Preload("RelatedEvent")
|
||||
|
||||
// Check if admin request
|
||||
if c.GetBool("isAdmin") {
|
||||
// Admin sees all polls
|
||||
status := c.Query("status")
|
||||
if status != "" {
|
||||
query = query.Where("status = ?", status)
|
||||
}
|
||||
} else {
|
||||
// Public sees only active polls
|
||||
query = query.Where("status = ?", "active")
|
||||
now := time.Now()
|
||||
query = query.Where("(start_date IS NULL OR start_date <= ?) AND (end_date IS NULL OR end_date >= ?)", now, now)
|
||||
}
|
||||
|
||||
// Featured filter
|
||||
if c.Query("featured") == "true" {
|
||||
query = query.Where("featured = ?", true)
|
||||
}
|
||||
|
||||
// Filter by relationships
|
||||
if articleID := c.Query("article_id"); articleID != "" {
|
||||
query = query.Where("related_article_id = ?", articleID)
|
||||
}
|
||||
if eventID := c.Query("event_id"); eventID != "" {
|
||||
query = query.Where("related_event_id = ?", eventID)
|
||||
}
|
||||
if videoURL := c.Query("video_url"); videoURL != "" {
|
||||
query = query.Where("related_video_url = ?", videoURL)
|
||||
}
|
||||
|
||||
// Order
|
||||
query = query.Order("created_at DESC")
|
||||
|
||||
if err := query.Find(&polls).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch polls"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, polls)
|
||||
}
|
||||
|
||||
// GetPoll returns a single poll by ID
|
||||
func (pc *PollController) GetPoll(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
var poll models.Poll
|
||||
query := pc.DB.Preload("Options", func(db *gorm.DB) *gorm.DB {
|
||||
return db.Order("display_order ASC, id ASC")
|
||||
}).Preload("Options.Player").Preload("Category")
|
||||
|
||||
if err := query.First(&poll, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Poll not found"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch poll"})
|
||||
return
|
||||
}
|
||||
|
||||
// Check if user has voted
|
||||
hasVoted := false
|
||||
if userID, exists := c.Get("userID"); exists && userID != nil {
|
||||
var count int64
|
||||
pc.DB.Model(&models.PollVote{}).Where("poll_id = ? AND user_id = ?", poll.ID, userID).Count(&count)
|
||||
hasVoted = count > 0
|
||||
} else {
|
||||
// Check by IP hash or session token
|
||||
ipHash := pc.hashIP(c.ClientIP())
|
||||
sessionToken := c.GetHeader("X-Session-Token")
|
||||
if sessionToken == "" {
|
||||
sessionToken = c.Query("session_token")
|
||||
}
|
||||
|
||||
var count int64
|
||||
query := pc.DB.Model(&models.PollVote{}).Where("poll_id = ?", poll.ID)
|
||||
if sessionToken != "" {
|
||||
query = query.Where("session_token = ?", sessionToken)
|
||||
} else {
|
||||
query = query.Where("ip_hash = ?", ipHash)
|
||||
}
|
||||
query.Count(&count)
|
||||
hasVoted = count > 0
|
||||
}
|
||||
|
||||
// Add metadata
|
||||
response := gin.H{
|
||||
"poll": poll,
|
||||
"has_voted": hasVoted,
|
||||
"is_active": poll.IsActive(),
|
||||
"can_show_results": poll.CanShowResults(hasVoted),
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
// CreatePoll creates a new poll (admin only)
|
||||
func (pc *PollController) CreatePoll(c *gin.Context) {
|
||||
var input struct {
|
||||
Title string `json:"title" binding:"required"`
|
||||
Description string `json:"description"`
|
||||
Type string `json:"type"`
|
||||
Status string `json:"status"`
|
||||
StartDate *time.Time `json:"start_date"`
|
||||
EndDate *time.Time `json:"end_date"`
|
||||
AllowMultiple bool `json:"allow_multiple"`
|
||||
MaxChoices int `json:"max_choices"`
|
||||
ShowResults string `json:"show_results"`
|
||||
RequireAuth bool `json:"require_auth"`
|
||||
AllowGuestVote bool `json:"allow_guest_vote"`
|
||||
Featured bool `json:"featured"`
|
||||
CategoryID *uint `json:"category_id"`
|
||||
RelatedMatchID *uint `json:"related_match_id"`
|
||||
RelatedArticleID *uint `json:"related_article_id"`
|
||||
RelatedEventID *uint `json:"related_event_id"`
|
||||
RelatedVideoURL string `json:"related_video_url"`
|
||||
ImageURL string `json:"image_url"`
|
||||
Options []struct {
|
||||
Text string `json:"text" binding:"required"`
|
||||
Description string `json:"description"`
|
||||
ImageURL string `json:"image_url"`
|
||||
DisplayOrder int `json:"display_order"`
|
||||
PlayerID *uint `json:"player_id"`
|
||||
} `json:"options" binding:"required,min=2"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&input); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Get user ID
|
||||
userID, _ := c.Get("userID")
|
||||
|
||||
// Create poll
|
||||
poll := models.Poll{
|
||||
Title: input.Title,
|
||||
Description: input.Description,
|
||||
Type: input.Type,
|
||||
Status: input.Status,
|
||||
StartDate: input.StartDate,
|
||||
EndDate: input.EndDate,
|
||||
AllowMultiple: input.AllowMultiple,
|
||||
MaxChoices: input.MaxChoices,
|
||||
ShowResults: input.ShowResults,
|
||||
RequireAuth: input.RequireAuth,
|
||||
AllowGuestVote: input.AllowGuestVote,
|
||||
Featured: input.Featured,
|
||||
CategoryID: input.CategoryID,
|
||||
RelatedMatchID: input.RelatedMatchID,
|
||||
RelatedArticleID: input.RelatedArticleID,
|
||||
RelatedEventID: input.RelatedEventID,
|
||||
RelatedVideoURL: input.RelatedVideoURL,
|
||||
ImageURL: input.ImageURL,
|
||||
CreatedBy: userID.(uint),
|
||||
}
|
||||
|
||||
// Set defaults
|
||||
if poll.Type == "" {
|
||||
poll.Type = "single"
|
||||
}
|
||||
if poll.Status == "" {
|
||||
poll.Status = "draft"
|
||||
}
|
||||
if poll.ShowResults == "" {
|
||||
poll.ShowResults = "after_vote"
|
||||
}
|
||||
if poll.MaxChoices == 0 {
|
||||
poll.MaxChoices = 1
|
||||
}
|
||||
|
||||
// Start transaction
|
||||
tx := pc.DB.Begin()
|
||||
|
||||
if err := tx.Create(&poll).Error; err != nil {
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create poll"})
|
||||
return
|
||||
}
|
||||
|
||||
// Create options
|
||||
for _, opt := range input.Options {
|
||||
option := models.PollOption{
|
||||
PollID: poll.ID,
|
||||
Text: opt.Text,
|
||||
Description: opt.Description,
|
||||
ImageURL: opt.ImageURL,
|
||||
DisplayOrder: opt.DisplayOrder,
|
||||
PlayerID: opt.PlayerID,
|
||||
}
|
||||
if err := tx.Create(&option).Error; err != nil {
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create poll option"})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
tx.Commit()
|
||||
|
||||
// Reload with relations
|
||||
pc.DB.Preload("Options").Preload("Category").First(&poll, poll.ID)
|
||||
|
||||
c.JSON(http.StatusCreated, poll)
|
||||
}
|
||||
|
||||
// UpdatePoll updates an existing poll (admin only)
|
||||
func (pc *PollController) UpdatePoll(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
var poll models.Poll
|
||||
if err := pc.DB.First(&poll, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Poll not found"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch poll"})
|
||||
return
|
||||
}
|
||||
|
||||
var input struct {
|
||||
Title *string `json:"title"`
|
||||
Description *string `json:"description"`
|
||||
Type *string `json:"type"`
|
||||
Status *string `json:"status"`
|
||||
StartDate *time.Time `json:"start_date"`
|
||||
EndDate *time.Time `json:"end_date"`
|
||||
AllowMultiple *bool `json:"allow_multiple"`
|
||||
MaxChoices *int `json:"max_choices"`
|
||||
ShowResults *string `json:"show_results"`
|
||||
RequireAuth *bool `json:"require_auth"`
|
||||
AllowGuestVote *bool `json:"allow_guest_vote"`
|
||||
Featured *bool `json:"featured"`
|
||||
CategoryID *uint `json:"category_id"`
|
||||
RelatedMatchID *uint `json:"related_match_id"`
|
||||
RelatedArticleID *uint `json:"related_article_id"`
|
||||
RelatedEventID *uint `json:"related_event_id"`
|
||||
RelatedVideoURL *string `json:"related_video_url"`
|
||||
ImageURL *string `json:"image_url"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&input); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Update fields
|
||||
if input.Title != nil {
|
||||
poll.Title = *input.Title
|
||||
}
|
||||
if input.Description != nil {
|
||||
poll.Description = *input.Description
|
||||
}
|
||||
if input.Type != nil {
|
||||
poll.Type = *input.Type
|
||||
}
|
||||
if input.Status != nil {
|
||||
poll.Status = *input.Status
|
||||
}
|
||||
if input.StartDate != nil {
|
||||
poll.StartDate = input.StartDate
|
||||
}
|
||||
if input.EndDate != nil {
|
||||
poll.EndDate = input.EndDate
|
||||
}
|
||||
if input.AllowMultiple != nil {
|
||||
poll.AllowMultiple = *input.AllowMultiple
|
||||
}
|
||||
if input.MaxChoices != nil {
|
||||
poll.MaxChoices = *input.MaxChoices
|
||||
}
|
||||
if input.ShowResults != nil {
|
||||
poll.ShowResults = *input.ShowResults
|
||||
}
|
||||
if input.RequireAuth != nil {
|
||||
poll.RequireAuth = *input.RequireAuth
|
||||
}
|
||||
if input.AllowGuestVote != nil {
|
||||
poll.AllowGuestVote = *input.AllowGuestVote
|
||||
}
|
||||
if input.Featured != nil {
|
||||
poll.Featured = *input.Featured
|
||||
}
|
||||
if input.CategoryID != nil {
|
||||
poll.CategoryID = input.CategoryID
|
||||
}
|
||||
// For relationships, directly set the values (including nil to unlink)
|
||||
// GORM's Save will handle NULL values correctly
|
||||
poll.RelatedMatchID = input.RelatedMatchID
|
||||
poll.RelatedArticleID = input.RelatedArticleID
|
||||
poll.RelatedEventID = input.RelatedEventID
|
||||
if input.RelatedVideoURL != nil {
|
||||
poll.RelatedVideoURL = *input.RelatedVideoURL
|
||||
}
|
||||
if input.ImageURL != nil {
|
||||
poll.ImageURL = *input.ImageURL
|
||||
}
|
||||
|
||||
if err := pc.DB.Save(&poll).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update poll"})
|
||||
return
|
||||
}
|
||||
|
||||
// Reload with relations
|
||||
pc.DB.Preload("Options").Preload("Category").First(&poll, poll.ID)
|
||||
|
||||
c.JSON(http.StatusOK, poll)
|
||||
}
|
||||
|
||||
// DeletePoll deletes a poll (admin only)
|
||||
func (pc *PollController) DeletePoll(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
var poll models.Poll
|
||||
if err := pc.DB.First(&poll, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Poll not found"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch poll"})
|
||||
return
|
||||
}
|
||||
|
||||
// Soft delete
|
||||
if err := pc.DB.Delete(&poll).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete poll"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Poll deleted successfully"})
|
||||
}
|
||||
|
||||
// Vote handles vote submission
|
||||
func (pc *PollController) Vote(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
var input struct {
|
||||
OptionIDs []uint `json:"option_ids" binding:"required,min=1"`
|
||||
SessionToken string `json:"session_token"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&input); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid input: " + err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Get poll
|
||||
var poll models.Poll
|
||||
if err := pc.DB.Preload("Options").First(&poll, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Poll not found"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch poll"})
|
||||
return
|
||||
}
|
||||
|
||||
// Check if poll is active
|
||||
if !poll.IsActive() {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Poll is not currently accepting votes"})
|
||||
return
|
||||
}
|
||||
|
||||
// Check authentication requirement
|
||||
userID, hasUser := c.Get("userID")
|
||||
if poll.RequireAuth && !hasUser {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Login required to vote"})
|
||||
return
|
||||
}
|
||||
|
||||
if !poll.AllowGuestVote && !hasUser {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Guest voting not allowed"})
|
||||
return
|
||||
}
|
||||
|
||||
// Check multiple choice limits
|
||||
if !poll.AllowMultiple && len(input.OptionIDs) > 1 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Only one choice allowed"})
|
||||
return
|
||||
}
|
||||
|
||||
if poll.AllowMultiple && len(input.OptionIDs) > poll.MaxChoices {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("Maximum %d choices allowed", poll.MaxChoices)})
|
||||
return
|
||||
}
|
||||
|
||||
// Check if already voted
|
||||
ipHash := pc.hashIP(c.ClientIP())
|
||||
sessionToken := input.SessionToken
|
||||
if sessionToken == "" {
|
||||
sessionToken = c.GetHeader("X-Session-Token")
|
||||
}
|
||||
|
||||
var existingVoteCount int64
|
||||
query := pc.DB.Model(&models.PollVote{}).Where("poll_id = ?", poll.ID)
|
||||
|
||||
if hasUser {
|
||||
query = query.Where("user_id = ?", userID)
|
||||
} else if sessionToken != "" {
|
||||
query = query.Where("session_token = ?", sessionToken)
|
||||
} else {
|
||||
query = query.Where("ip_hash = ?", ipHash)
|
||||
}
|
||||
|
||||
query.Count(&existingVoteCount)
|
||||
|
||||
if existingVoteCount > 0 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "You have already voted in this poll"})
|
||||
return
|
||||
}
|
||||
|
||||
// Validate option IDs belong to this poll
|
||||
validOptions := make(map[uint]bool)
|
||||
for _, opt := range poll.Options {
|
||||
validOptions[opt.ID] = true
|
||||
}
|
||||
|
||||
for _, optID := range input.OptionIDs {
|
||||
if !validOptions[optID] {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid option ID"})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Start transaction
|
||||
tx := pc.DB.Begin()
|
||||
|
||||
// Create votes
|
||||
userAgent := c.Request.UserAgent()
|
||||
for _, optionID := range input.OptionIDs {
|
||||
vote := models.PollVote{
|
||||
PollID: poll.ID,
|
||||
OptionID: optionID,
|
||||
IPHash: ipHash,
|
||||
UserAgent: userAgent,
|
||||
SessionToken: sessionToken,
|
||||
}
|
||||
|
||||
if hasUser {
|
||||
uid := userID.(uint)
|
||||
vote.UserID = &uid
|
||||
}
|
||||
|
||||
if err := tx.Create(&vote).Error; err != nil {
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to record vote"})
|
||||
return
|
||||
}
|
||||
|
||||
// Increment option vote count
|
||||
if err := tx.Model(&models.PollOption{}).Where("id = ?", optionID).UpdateColumn("vote_count", gorm.Expr("vote_count + ?", 1)).Error; err != nil {
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update vote count"})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Increment poll total votes
|
||||
if err := tx.Model(&models.Poll{}).Where("id = ?", poll.ID).UpdateColumn("total_votes", gorm.Expr("total_votes + ?", 1)).Error; err != nil {
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update total votes"})
|
||||
return
|
||||
}
|
||||
|
||||
tx.Commit()
|
||||
|
||||
// Reload poll with updated counts
|
||||
pc.DB.Preload("Options", func(db *gorm.DB) *gorm.DB {
|
||||
return db.Order("display_order ASC, id ASC")
|
||||
}).First(&poll, poll.ID)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "Vote recorded successfully",
|
||||
"poll": poll,
|
||||
})
|
||||
}
|
||||
|
||||
// GetPollResults returns poll results
|
||||
func (pc *PollController) GetPollResults(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
var poll models.Poll
|
||||
if err := pc.DB.Preload("Options", func(db *gorm.DB) *gorm.DB {
|
||||
return db.Order("display_order ASC, id ASC")
|
||||
}).First(&poll, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Poll not found"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch poll"})
|
||||
return
|
||||
}
|
||||
|
||||
// Check if user can see results
|
||||
hasVoted := false
|
||||
if userID, exists := c.Get("userID"); exists && userID != nil {
|
||||
var count int64
|
||||
pc.DB.Model(&models.PollVote{}).Where("poll_id = ? AND user_id = ?", poll.ID, userID).Count(&count)
|
||||
hasVoted = count > 0
|
||||
} else {
|
||||
ipHash := pc.hashIP(c.ClientIP())
|
||||
sessionToken := c.GetHeader("X-Session-Token")
|
||||
if sessionToken == "" {
|
||||
sessionToken = c.Query("session_token")
|
||||
}
|
||||
|
||||
var count int64
|
||||
query := pc.DB.Model(&models.PollVote{}).Where("poll_id = ?", poll.ID)
|
||||
if sessionToken != "" {
|
||||
query = query.Where("session_token = ?", sessionToken)
|
||||
} else {
|
||||
query = query.Where("ip_hash = ?", ipHash)
|
||||
}
|
||||
query.Count(&count)
|
||||
hasVoted = count > 0
|
||||
}
|
||||
|
||||
if !poll.CanShowResults(hasVoted) && !c.GetBool("isAdmin") {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "Results are not available yet"})
|
||||
return
|
||||
}
|
||||
|
||||
// Calculate percentages
|
||||
results := make([]gin.H, len(poll.Options))
|
||||
for i, option := range poll.Options {
|
||||
percentage := 0.0
|
||||
if poll.TotalVotes > 0 {
|
||||
percentage = float64(option.VoteCount) / float64(poll.TotalVotes) * 100
|
||||
}
|
||||
|
||||
results[i] = gin.H{
|
||||
"option_id": option.ID,
|
||||
"text": option.Text,
|
||||
"vote_count": option.VoteCount,
|
||||
"percentage": percentage,
|
||||
"image_url": option.ImageURL,
|
||||
"player_id": option.PlayerID,
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"poll_id": poll.ID,
|
||||
"title": poll.Title,
|
||||
"total_votes": poll.TotalVotes,
|
||||
"results": results,
|
||||
})
|
||||
}
|
||||
|
||||
// Helper function to hash IP addresses
|
||||
func (pc *PollController) hashIP(ip string) string {
|
||||
hash := sha256.Sum256([]byte(ip + "poll-salt-2025"))
|
||||
return hex.EncodeToString(hash[:])
|
||||
}
|
||||
|
||||
// GetPollStats returns statistics for admin (admin only)
|
||||
func (pc *PollController) GetPollStats(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
pollID, err := strconv.Atoi(id)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid poll ID"})
|
||||
return
|
||||
}
|
||||
|
||||
var poll models.Poll
|
||||
if err := pc.DB.Preload("Options").First(&poll, pollID).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Poll not found"})
|
||||
return
|
||||
}
|
||||
|
||||
// Get vote distribution over time
|
||||
var votesByDay []struct {
|
||||
Date string `json:"date"`
|
||||
Count int `json:"count"`
|
||||
}
|
||||
|
||||
pc.DB.Model(&models.PollVote{}).
|
||||
Select("DATE(created_at) as date, COUNT(*) as count").
|
||||
Where("poll_id = ?", pollID).
|
||||
Group("DATE(created_at)").
|
||||
Order("date ASC").
|
||||
Scan(&votesByDay)
|
||||
|
||||
// Get authenticated vs guest votes
|
||||
var authVotes, guestVotes int64
|
||||
pc.DB.Model(&models.PollVote{}).Where("poll_id = ? AND user_id IS NOT NULL", pollID).Count(&authVotes)
|
||||
pc.DB.Model(&models.PollVote{}).Where("poll_id = ? AND user_id IS NULL", pollID).Count(&guestVotes)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"poll": poll,
|
||||
"votes_by_day": votesByDay,
|
||||
"authenticated_votes": authVotes,
|
||||
"guest_votes": guestVotes,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user