Files
MyClub/internal/services/file_tracker.go
T
Tomas Dvorak e9a63073e5 dev day #63
2025-10-17 17:39:11 +02:00

244 lines
7.4 KiB
Go

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(&currentUsages)
// 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)
}