This commit is contained in:
Tomas Dvorak
2026-01-26 08:13:18 +01:00
parent aa036b6550
commit dfc079288f
505 changed files with 95755 additions and 5712 deletions
+220 -202
View File
@@ -1,243 +1,261 @@
package controllers
import (
"net/http"
"time"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
"fotbal-club/internal/models"
"fotbal-club/internal/services"
"net/http"
"time"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
type EventController struct{ DB *gorm.DB }
// GetEventByID returns a single event by its ID (public; returns only public events unless owner)
func (ctrl *EventController) GetEventByID(c *gin.Context) {
id := c.Param("id")
var ev models.Event
if err := ctrl.DB.Preload("Attachments").First(&ev, id).Error; err != nil {
if err == gorm.ErrRecordNotFound {
c.JSON(http.StatusNotFound, gin.H{"error": "Event not found"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": "Database error"})
return
}
// If not public, allow only owner (when identified upstream)
if !ev.IsPublic {
if roleVal, hasRole := c.Get("userRole"); hasRole {
if role, _ := roleVal.(string); role == "admin" {
c.JSON(http.StatusOK, ev)
return
}
}
if userID, exists := c.Get("userID"); !exists || ev.CreatedByID != userID {
c.JSON(http.StatusForbidden, gin.H{"error": "Not allowed"})
return
}
}
c.JSON(http.StatusOK, ev)
id := c.Param("id")
var ev models.Event
if err := ctrl.DB.Preload("Attachments").First(&ev, id).Error; err != nil {
if err == gorm.ErrRecordNotFound {
c.JSON(http.StatusNotFound, gin.H{"error": "Event not found"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": "Database error"})
return
}
// If not public, allow only owner (when identified upstream)
if !ev.IsPublic {
if roleVal, hasRole := c.Get("userRole"); hasRole {
if role, _ := roleVal.(string); role == "admin" {
c.JSON(http.StatusOK, ev)
return
}
}
if userID, exists := c.Get("userID"); !exists || ev.CreatedByID != userID {
c.JSON(http.StatusForbidden, gin.H{"error": "Not allowed"})
return
}
}
c.JSON(http.StatusOK, ev)
}
type EventInput struct {
Title string `json:"title" binding:"required"`
Description string `json:"description"`
StartTime time.Time `json:"start_time" binding:"required"`
EndTime *time.Time `json:"end_time"`
Location string `json:"location"`
Type string `json:"type" binding:"required,oneof=match training meeting other"`
IsPublic bool `json:"is_public"`
CategoryName string `json:"category_name"`
ImageURL string `json:"image_url"`
FileURL string `json:"file_url"`
YoutubeURL string `json:"youtube_url"`
Latitude *float64 `json:"latitude"`
Longitude *float64 `json:"longitude"`
Attachments []struct {
Name string `json:"name"`
URL string `json:"url"`
MimeType string `json:"mime_type"`
Size int64 `json:"size"`
} `json:"attachments"`
Title string `json:"title" binding:"required"`
Description string `json:"description"`
StartTime time.Time `json:"start_time" binding:"required"`
EndTime *time.Time `json:"end_time"`
Location string `json:"location"`
Type string `json:"type" binding:"required,oneof=match training meeting other"`
IsPublic bool `json:"is_public"`
CategoryName string `json:"category_name"`
ImageURL string `json:"image_url"`
FileURL string `json:"file_url"`
YoutubeURL string `json:"youtube_url"`
Latitude *float64 `json:"latitude"`
Longitude *float64 `json:"longitude"`
Attachments []struct {
Name string `json:"name"`
URL string `json:"url"`
MimeType string `json:"mime_type"`
Size int64 `json:"size"`
} `json:"attachments"`
}
func (ctrl *EventController) CreateEvent(c *gin.Context) {
// Ensure latest schema (adds columns if missing)
_ = ctrl.DB.AutoMigrate(&models.Event{}, &models.EventAttachment{})
var input EventInput
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// Ensure latest schema (adds columns if missing)
_ = ctrl.DB.AutoMigrate(&models.Event{}, &models.EventAttachment{})
var input EventInput
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
userID, _ := c.Get("userID")
event := models.Event{
Title: input.Title,
Description: input.Description,
StartTime: input.StartTime,
EndTime: input.EndTime,
Location: input.Location,
Type: models.EventType(input.Type),
IsPublic: input.IsPublic,
CreatedByID: userID.(uint),
CategoryName: input.CategoryName,
ImageURL: input.ImageURL,
FileURL: input.FileURL,
YoutubeURL: input.YoutubeURL,
Latitude: input.Latitude,
Longitude: input.Longitude,
}
userID, _ := c.Get("userID")
imageURL := input.ImageURL
if imageURL == "" && isXAIEnabled() {
prompt := "Titulní obrázek pro klubovou událost na oficiálním webu fotbalového klubu. Název události: \"" + input.Title + "\". Typ: " + string(models.EventType(input.Type)) + ". Zaměř se na prostředí klubu stadion, tréninkové hřiště a fanoušky v klubových barvách. Styl: realistický, moderní, sportovní, bez textu, široký banner v poměru 16:9."
if input.CategoryName != "" {
prompt += " Téma / soutěž: " + input.CategoryName + "."
}
if urls, _, err := callXAIImage(getXAIImageModel(), prompt, "1920x1080", 1); err == nil && len(urls) > 0 && urls[0] != "" {
imageURL = urls[0]
}
}
if imageURL == "" {
imageURL = "/dist/img/logo-club-empty.svg"
}
event := models.Event{
Title: input.Title,
Description: input.Description,
StartTime: input.StartTime,
EndTime: input.EndTime,
Location: input.Location,
Type: models.EventType(input.Type),
IsPublic: input.IsPublic,
CreatedByID: userID.(uint),
CategoryName: input.CategoryName,
ImageURL: imageURL,
FileURL: input.FileURL,
YoutubeURL: input.YoutubeURL,
Latitude: input.Latitude,
Longitude: input.Longitude,
}
if err := ctrl.DB.Create(&event).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create event"})
return
}
// Create attachments if any
if len(input.Attachments) > 0 {
var atts []models.EventAttachment
for _, a := range input.Attachments {
if a.URL == "" { continue }
atts = append(atts, models.EventAttachment{ EventID: event.ID, Name: a.Name, URL: a.URL, MimeType: a.MimeType, Size: a.Size })
}
if len(atts) > 0 {
if err := ctrl.DB.Create(&atts).Error; err != nil {
// non-fatal
}
}
}
if err := ctrl.DB.Create(&event).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create event"})
return
}
// Create attachments if any
if len(input.Attachments) > 0 {
var atts []models.EventAttachment
for _, a := range input.Attachments {
if a.URL == "" {
continue
}
atts = append(atts, models.EventAttachment{EventID: event.ID, Name: a.Name, URL: a.URL, MimeType: a.MimeType, Size: a.Size})
}
if len(atts) > 0 {
if err := ctrl.DB.Create(&atts).Error; err != nil {
// non-fatal
}
}
}
// Reload with attachments
var out models.Event
_ = ctrl.DB.Preload("Attachments").First(&out, event.ID).Error
// Track file usage
fileTracker := services.NewFileTracker(ctrl.DB)
go fileTracker.TrackEventFiles(&out)
c.JSON(http.StatusCreated, out)
// Reload with attachments
var out models.Event
_ = ctrl.DB.Preload("Attachments").First(&out, event.ID).Error
// Track file usage
fileTracker := services.NewFileTracker(ctrl.DB)
go fileTracker.TrackEventFiles(&out)
c.JSON(http.StatusCreated, out)
}
func (ctrl *EventController) GetEvents(c *gin.Context) {
var events []models.Event
query := ctrl.DB.Preload("Attachments")
// Admin sees all events
if roleVal, hasRole := c.Get("userRole"); hasRole {
if role, _ := roleVal.(string); role == "admin" {
if err := query.Find(&events).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch events"})
return
}
c.JSON(http.StatusOK, events)
return
}
}
if userID, exists := c.Get("userID"); !exists {
query = query.Where("is_public = ?", true)
} else {
query = query.Where("created_by_id = ? OR is_public = ?", userID, true)
}
var events []models.Event
query := ctrl.DB.Preload("Attachments")
// Admin sees all events
if roleVal, hasRole := c.Get("userRole"); hasRole {
if role, _ := roleVal.(string); role == "admin" {
if err := query.Find(&events).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch events"})
return
}
c.JSON(http.StatusOK, events)
return
}
}
if userID, exists := c.Get("userID"); !exists {
query = query.Where("is_public = ?", true)
} else {
query = query.Where("created_by_id = ? OR is_public = ?", userID, true)
}
if err := query.Find(&events).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch events"})
return
}
if err := query.Find(&events).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch events"})
return
}
c.JSON(http.StatusOK, events)
}
func (ctrl *EventController) GetUpcomingEvents(c *gin.Context) {
var events []models.Event
query := ctrl.DB.Preload("Attachments").Where("start_time >= ?", time.Now()).Order("start_time ASC").Limit(5)
// Admin sees all upcoming events
if roleVal, hasRole := c.Get("userRole"); hasRole {
if role, _ := roleVal.(string); role == "admin" {
if err := query.Find(&events).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch events"})
return
}
c.JSON(http.StatusOK, events)
return
}
}
if userID, exists := c.Get("userID"); !exists {
query = query.Where("is_public = ?", true)
} else {
query = query.Where("created_by_id = ? OR is_public = ?", userID, true)
}
var events []models.Event
query := ctrl.DB.Preload("Attachments").Where("start_time >= ?", time.Now()).Order("start_time ASC").Limit(5)
// Admin sees all upcoming events
if roleVal, hasRole := c.Get("userRole"); hasRole {
if role, _ := roleVal.(string); role == "admin" {
if err := query.Find(&events).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch events"})
return
}
c.JSON(http.StatusOK, events)
return
}
}
if userID, exists := c.Get("userID"); !exists {
query = query.Where("is_public = ?", true)
} else {
query = query.Where("created_by_id = ? OR is_public = ?", userID, true)
}
if err := query.Find(&events).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch events"})
return
}
if err := query.Find(&events).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch events"})
return
}
c.JSON(http.StatusOK, events)
}
// UpdateEvent updates an existing event (protected)
func (ctrl *EventController) UpdateEvent(c *gin.Context) {
// Ensure latest schema (adds columns if missing)
_ = ctrl.DB.AutoMigrate(&models.Event{}, &models.EventAttachment{})
id := c.Param("id")
var ev models.Event
if err := ctrl.DB.First(&ev, id).Error; err != nil {
if err == gorm.ErrRecordNotFound {
c.JSON(http.StatusNotFound, gin.H{"error": "Event not found"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": "Database error"})
return
}
var input EventInput
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
ev.Title = input.Title
ev.Description = input.Description
ev.StartTime = input.StartTime
ev.EndTime = input.EndTime
ev.Location = input.Location
ev.Type = models.EventType(input.Type)
ev.IsPublic = input.IsPublic
ev.CategoryName = input.CategoryName
ev.ImageURL = input.ImageURL
ev.FileURL = input.FileURL
ev.YoutubeURL = input.YoutubeURL
ev.Latitude = input.Latitude
ev.Longitude = input.Longitude
if err := ctrl.DB.Save(&ev).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update event"})
return
}
// Replace attachments (simple strategy)
if err := ctrl.DB.Where("event_id = ?", ev.ID).Delete(&models.EventAttachment{}).Error; err == nil {
if len(input.Attachments) > 0 {
var atts []models.EventAttachment
for _, a := range input.Attachments {
if a.URL == "" { continue }
atts = append(atts, models.EventAttachment{ EventID: ev.ID, Name: a.Name, URL: a.URL, MimeType: a.MimeType, Size: a.Size })
}
if len(atts) > 0 {
_ = ctrl.DB.Create(&atts).Error
}
}
}
var out models.Event
_ = ctrl.DB.Preload("Attachments").First(&out, ev.ID).Error
// Track file usage
fileTracker := services.NewFileTracker(ctrl.DB)
go fileTracker.TrackEventFiles(&out)
c.JSON(http.StatusOK, out)
// Ensure latest schema (adds columns if missing)
_ = ctrl.DB.AutoMigrate(&models.Event{}, &models.EventAttachment{})
id := c.Param("id")
var ev models.Event
if err := ctrl.DB.First(&ev, id).Error; err != nil {
if err == gorm.ErrRecordNotFound {
c.JSON(http.StatusNotFound, gin.H{"error": "Event not found"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": "Database error"})
return
}
var input EventInput
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
ev.Title = input.Title
ev.Description = input.Description
ev.StartTime = input.StartTime
ev.EndTime = input.EndTime
ev.Location = input.Location
ev.Type = models.EventType(input.Type)
ev.IsPublic = input.IsPublic
ev.CategoryName = input.CategoryName
ev.ImageURL = input.ImageURL
ev.FileURL = input.FileURL
ev.YoutubeURL = input.YoutubeURL
ev.Latitude = input.Latitude
ev.Longitude = input.Longitude
if err := ctrl.DB.Save(&ev).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update event"})
return
}
// Replace attachments (simple strategy)
if err := ctrl.DB.Where("event_id = ?", ev.ID).Delete(&models.EventAttachment{}).Error; err == nil {
if len(input.Attachments) > 0 {
var atts []models.EventAttachment
for _, a := range input.Attachments {
if a.URL == "" {
continue
}
atts = append(atts, models.EventAttachment{EventID: ev.ID, Name: a.Name, URL: a.URL, MimeType: a.MimeType, Size: a.Size})
}
if len(atts) > 0 {
_ = ctrl.DB.Create(&atts).Error
}
}
}
var out models.Event
_ = ctrl.DB.Preload("Attachments").First(&out, ev.ID).Error
// Track file usage
fileTracker := services.NewFileTracker(ctrl.DB)
go fileTracker.TrackEventFiles(&out)
c.JSON(http.StatusOK, out)
}
// DeleteEvent removes an event (protected)
func (ctrl *EventController) DeleteEvent(c *gin.Context) {
id := c.Param("id")
if err := ctrl.DB.Delete(&models.Event{}, id).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete event"})
return
}
c.JSON(http.StatusOK, gin.H{"ok": true})
id := c.Param("id")
if err := ctrl.DB.Delete(&models.Event{}, id).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete event"})
return
}
c.JSON(http.StatusOK, gin.H{"ok": true})
}