mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-04 10:42:57 +00:00
dev day #63
This commit is contained in:
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -326,6 +326,7 @@ func SetupRoutes(api *gin.RouterGroup, db *gorm.DB) {
|
||||
files.GET("/:id/usages", filesController.GetFileUsages)
|
||||
files.DELETE("/:id", filesController.DeleteFile)
|
||||
files.POST("/scan", filesController.ScanAndSyncFiles)
|
||||
files.POST("/refresh-tracking", filesController.RefreshFileTracking)
|
||||
}
|
||||
|
||||
// Navigation management (admin)
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fotbal-club/internal/models"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"gorm.io/gorm"
|
||||
@@ -120,19 +123,35 @@ func (ft *FileTracker) TrackArticleFiles(article *models.Article) error {
|
||||
|
||||
// Track attachments if present
|
||||
if article.Attachments != "" {
|
||||
// Attachments is a JSON array of URLs
|
||||
// For simplicity, we'll track each attachment URL separately
|
||||
// You might want to parse the JSON properly in production
|
||||
attachments := strings.Split(article.Attachments, ",")
|
||||
for i, attachment := range attachments {
|
||||
attachment = strings.Trim(attachment, `[]" `)
|
||||
if attachment != "" {
|
||||
fieldName := "attachments"
|
||||
if i > 0 {
|
||||
// If multiple attachments, differentiate them
|
||||
fieldName = "attachments"
|
||||
// Attachments is a JSON array of URLs - parse properly
|
||||
var attachmentURLs []string
|
||||
if err := json.Unmarshal([]byte(article.Attachments), &attachmentURLs); err == nil {
|
||||
// Successfully parsed as JSON array
|
||||
for i, attachmentURL := range attachmentURLs {
|
||||
if attachmentURL != "" {
|
||||
// Extract filename from URL for better field naming
|
||||
filename := filepath.Base(attachmentURL)
|
||||
fieldName := "attachment_" + filename
|
||||
// Ensure unique field names if same filename appears multiple times
|
||||
if _, exists := fieldURLMap[fieldName]; exists {
|
||||
fieldName = "attachment_" + filename + "_" + strconv.Itoa(i)
|
||||
}
|
||||
fieldURLMap[fieldName] = attachmentURL
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Fallback to simple comma-separated parsing
|
||||
attachments := strings.Split(article.Attachments, ",")
|
||||
for i, attachment := range attachments {
|
||||
attachment = strings.Trim(attachment, `[]" `)
|
||||
if attachment != "" {
|
||||
filename := filepath.Base(attachment)
|
||||
fieldName := "attachment_" + filename
|
||||
if _, exists := fieldURLMap[fieldName]; exists {
|
||||
fieldName = "attachment_" + filename + "_" + strconv.Itoa(i)
|
||||
}
|
||||
fieldURLMap[fieldName] = attachment
|
||||
}
|
||||
fieldURLMap[fieldName] = attachment
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -162,6 +181,39 @@ func (ft *FileTracker) TrackEventFiles(event *models.Event) error {
|
||||
"image_url": event.ImageURL,
|
||||
"file_url": event.FileURL,
|
||||
}
|
||||
|
||||
// Track each attachment separately
|
||||
for i, attachment := range event.Attachments {
|
||||
if attachment.URL != "" {
|
||||
// Generate field name from attachment name or filename
|
||||
fieldName := ""
|
||||
if attachment.Name != "" {
|
||||
// Use attachment name if available
|
||||
fieldName = "attachment_" + strings.ReplaceAll(attachment.Name, " ", "_")
|
||||
} else {
|
||||
// Fall back to filename from URL
|
||||
filename := filepath.Base(attachment.URL)
|
||||
fieldName = "attachment_" + filename
|
||||
}
|
||||
|
||||
// Ensure unique field names if duplicates exist
|
||||
originalFieldName := fieldName
|
||||
for counter := 0; ; counter++ {
|
||||
if _, exists := fieldURLMap[fieldName]; !exists {
|
||||
break
|
||||
}
|
||||
// Add counter suffix if field name already exists
|
||||
fieldName = originalFieldName + "_" + strconv.Itoa(counter)
|
||||
if counter > 999 { // Prevent infinite loop
|
||||
fieldName = originalFieldName + "_" + strconv.Itoa(i)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
fieldURLMap[fieldName] = attachment.URL
|
||||
}
|
||||
}
|
||||
|
||||
return ft.UpdateFileUsages("event", event.ID, fieldURLMap)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user