This commit is contained in:
Tomas Dvorak
2025-10-17 17:39:11 +02:00
parent 35d0954afd
commit e9a63073e5
61 changed files with 3824 additions and 1061 deletions
+130 -1
View File
@@ -11,6 +11,7 @@ import (
"strings"
"fotbal-club/internal/models"
"fotbal-club/internal/services"
"fotbal-club/pkg/logger"
"github.com/gin-gonic/gin"
@@ -366,13 +367,37 @@ func calculateFileMD5(filePath string) (string, error) {
func detectMimeType(filePath string) string {
ext := strings.ToLower(filepath.Ext(filePath))
mimeTypes := map[string]string{
// Images
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
".png": "image/png",
".gif": "image/gif",
".svg": "image/svg+xml",
".pdf": "application/pdf",
".webp": "image/webp",
".bmp": "image/bmp",
".ico": "image/x-icon",
// Documents
".pdf": "application/pdf",
".doc": "application/msword",
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
".xls": "application/vnd.ms-excel",
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
".ppt": "application/vnd.ms-powerpoint",
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
".txt": "text/plain",
".csv": "text/csv",
// Archives
".zip": "application/zip",
".rar": "application/x-rar-compressed",
".7z": "application/x-7z-compressed",
".tar": "application/x-tar",
".gz": "application/gzip",
// Media
".mp4": "video/mp4",
".avi": "video/x-msvideo",
".mov": "video/quicktime",
".mp3": "audio/mpeg",
".wav": "audio/wav",
}
if mime, ok := mimeTypes[ext]; ok {
@@ -423,3 +448,107 @@ func getEntityInfo(db *gorm.DB, entityType string, entityID uint) map[string]int
return info
}
// RefreshFileTracking re-scans all entities and updates file usage tracking
func (fc *FilesController) RefreshFileTracking(c *gin.Context) {
entityType := c.Query("entity_type") // Optional: "article", "event", "player", etc.
stats := map[string]int{
"articles_scanned": 0,
"events_scanned": 0,
"players_scanned": 0,
"sponsors_scanned": 0,
"contacts_scanned": 0,
"teams_scanned": 0,
"settings_scanned": 0,
}
fileTracker := services.NewFileTracker(fc.DB)
// Refresh articles
if entityType == "" || entityType == "article" {
var articles []models.Article
if err := fc.DB.Find(&articles).Error; err == nil {
for _, article := range articles {
fileTracker.TrackArticleFiles(&article)
stats["articles_scanned"]++
}
logger.Info("Refreshed file tracking for %d articles", len(articles))
}
}
// Refresh events
if entityType == "" || entityType == "event" {
var events []models.Event
if err := fc.DB.Preload("Attachments").Find(&events).Error; err == nil {
for _, event := range events {
fileTracker.TrackEventFiles(&event)
stats["events_scanned"]++
}
logger.Info("Refreshed file tracking for %d events", len(events))
}
}
// Refresh players
if entityType == "" || entityType == "player" {
var players []models.Player
if err := fc.DB.Find(&players).Error; err == nil {
for _, player := range players {
fileTracker.TrackPlayerFiles(&player)
stats["players_scanned"]++
}
logger.Info("Refreshed file tracking for %d players", len(players))
}
}
// Refresh sponsors
if entityType == "" || entityType == "sponsor" {
var sponsors []models.Sponsor
if err := fc.DB.Find(&sponsors).Error; err == nil {
for _, sponsor := range sponsors {
fileTracker.TrackSponsorFiles(&sponsor)
stats["sponsors_scanned"]++
}
logger.Info("Refreshed file tracking for %d sponsors", len(sponsors))
}
}
// Refresh contacts
if entityType == "" || entityType == "contact" {
var contacts []models.Contact
if err := fc.DB.Find(&contacts).Error; err == nil {
for _, contact := range contacts {
fileTracker.TrackContactFiles(&contact)
stats["contacts_scanned"]++
}
logger.Info("Refreshed file tracking for %d contacts", len(contacts))
}
}
// Refresh teams
if entityType == "" || entityType == "team" {
var teams []models.Team
if err := fc.DB.Find(&teams).Error; err == nil {
for _, team := range teams {
fileTracker.TrackTeamFiles(&team)
stats["teams_scanned"]++
}
logger.Info("Refreshed file tracking for %d teams", len(teams))
}
}
// Refresh settings
if entityType == "" || entityType == "settings" {
var settings models.Settings
if err := fc.DB.First(&settings).Error; err == nil {
fileTracker.TrackSettingsFiles(&settings)
stats["settings_scanned"]++
logger.Info("Refreshed file tracking for settings")
}
}
c.JSON(http.StatusOK, gin.H{
"message": "File tracking refreshed successfully",
"stats": stats,
})
}
+22 -6
View File
@@ -108,15 +108,28 @@ func (nc *NavigationController) CreateNavigationItem(c *gin.Context) {
// If no display order is set, put it at the end
if item.DisplayOrder == 0 {
var maxOrder int
nc.DB.Model(&models.NavigationItem{}).
Where("parent_id IS NULL").
Select("COALESCE(MAX(display_order), -1) + 1").
Scan(&maxOrder)
query := nc.DB.Model(&models.NavigationItem{})
// Calculate max order for items at the same level (same parent) and same admin status
if item.ParentID == nil {
query = query.Where("parent_id IS NULL")
} else {
query = query.Where("parent_id = ?", *item.ParentID)
}
// Also consider requires_admin to keep frontend and admin items separate
query = query.Where("requires_admin = ?", item.RequiresAdmin)
query.Select("COALESCE(MAX(display_order), -1) + 1").Scan(&maxOrder)
item.DisplayOrder = maxOrder
}
if err := nc.DB.Create(&item).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create navigation item"})
// Log the actual error for debugging
c.JSON(http.StatusInternalServerError, gin.H{
"error": "Failed to create navigation item",
"details": err.Error(),
})
return
}
@@ -169,7 +182,10 @@ func (nc *NavigationController) UpdateNavigationItem(c *gin.Context) {
item.RequiresAdmin = updates.RequiresAdmin
if err := nc.DB.Save(&item).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update navigation item"})
c.JSON(http.StatusInternalServerError, gin.H{
"error": "Failed to update navigation item",
"details": err.Error(),
})
return
}