package services import ( "encoding/json" "fotbal-club/internal/models" "path/filepath" "strconv" "strings" "gorm.io/gorm" ) // FileTracker provides utilities for tracking file usage type FileTracker struct { DB *gorm.DB } // NewFileTracker creates a new file tracker instance func NewFileTracker(db *gorm.DB) *FileTracker { return &FileTracker{DB: db} } // TrackFileUpload records a new uploaded file func (ft *FileTracker) TrackFileUpload(filePath, fileURL, filename, mimeType string, fileSize int64, uploadedByID *uint) error { file := models.UploadedFile{ Filename: filename, FilePath: filePath, FileURL: fileURL, FileSize: fileSize, MimeType: mimeType, UploadedByID: uploadedByID, } return ft.DB.Create(&file).Error } // TrackFileUsage records or updates file usage for an entity func (ft *FileTracker) TrackFileUsage(fileURL, entityType string, entityID uint, fieldName string) error { if fileURL == "" { return nil } // Find the file by URL var file models.UploadedFile if err := ft.DB.Where("file_url = ?", fileURL).First(&file).Error; err != nil { // File not found in database - skip tracking return nil } // Check if usage already exists var existingUsage models.FileUsage err := ft.DB.Where("file_id = ? AND entity_type = ? AND entity_id = ? AND field_name = ?", file.ID, entityType, entityID, fieldName).First(&existingUsage).Error if err == gorm.ErrRecordNotFound { // Create new usage record usage := models.FileUsage{ FileID: file.ID, EntityType: entityType, EntityID: entityID, FieldName: fieldName, } return ft.DB.Create(&usage).Error } return err } // RemoveFileUsage removes a file usage record func (ft *FileTracker) RemoveFileUsage(fileURL, entityType string, entityID uint, fieldName string) error { if fileURL == "" { return nil } // Find the file by URL var file models.UploadedFile if err := ft.DB.Where("file_url = ?", fileURL).First(&file).Error; err != nil { return nil } return ft.DB.Where("file_id = ? AND entity_type = ? AND entity_id = ? AND field_name = ?", file.ID, entityType, entityID, fieldName).Delete(&models.FileUsage{}).Error } // UpdateFileUsages updates all file usages for an entity (removes old, adds new) func (ft *FileTracker) UpdateFileUsages(entityType string, entityID uint, fieldURLMap map[string]string) error { // Get all current usages for this entity var currentUsages []models.FileUsage ft.DB.Where("entity_type = ? AND entity_id = ?", entityType, entityID).Find(¤tUsages) // Create a map of current usages currentMap := make(map[string]models.FileUsage) for _, usage := range currentUsages { key := usage.FieldName currentMap[key] = usage } // Track new usages for fieldName, fileURL := range fieldURLMap { if fileURL != "" { // Check if already tracked if _, exists := currentMap[fieldName]; !exists { ft.TrackFileUsage(fileURL, entityType, entityID, fieldName) } delete(currentMap, fieldName) // Remove from current map } } // Remove usages that are no longer present for fieldName := range currentMap { ft.DB.Where("entity_type = ? AND entity_id = ? AND field_name = ?", entityType, entityID, fieldName).Delete(&models.FileUsage{}) } return nil } // TrackArticleFiles tracks all file usages in an article func (ft *FileTracker) TrackArticleFiles(article *models.Article) error { fieldURLMap := map[string]string{ "image_url": article.ImageURL, "og_image_url": article.OGImageURL, } // Track attachments if present if article.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 } } } } return ft.UpdateFileUsages("article", article.ID, fieldURLMap) } // TrackPlayerFiles tracks all file usages in a player func (ft *FileTracker) TrackPlayerFiles(player *models.Player) error { fieldURLMap := map[string]string{ "image_url": player.ImageURL, } return ft.UpdateFileUsages("player", player.ID, fieldURLMap) } // TrackSponsorFiles tracks all file usages in a sponsor func (ft *FileTracker) TrackSponsorFiles(sponsor *models.Sponsor) error { fieldURLMap := map[string]string{ "logo_url": sponsor.LogoURL, } return ft.UpdateFileUsages("sponsor", sponsor.ID, fieldURLMap) } // TrackEventFiles tracks all file usages in an event func (ft *FileTracker) TrackEventFiles(event *models.Event) error { fieldURLMap := map[string]string{ "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) } // TrackContactFiles tracks all file usages in a contact func (ft *FileTracker) TrackContactFiles(contact *models.Contact) error { fieldURLMap := map[string]string{ "image_url": contact.ImageURL, } return ft.UpdateFileUsages("contact", contact.ID, fieldURLMap) } // TrackSettingsFiles tracks all file usages in settings func (ft *FileTracker) TrackSettingsFiles(settings *models.Settings) error { fieldURLMap := map[string]string{ "default_og_image_url": settings.DefaultOGImageURL, "club_logo_url": settings.ClubLogoURL, } return ft.UpdateFileUsages("settings", settings.ID, fieldURLMap) } // TrackTeamFiles tracks all file usages in a team func (ft *FileTracker) TrackTeamFiles(team *models.Team) error { fieldURLMap := map[string]string{ "logo_url": team.LogoURL, } return ft.UpdateFileUsages("team", team.ID, fieldURLMap) }