package useCases import ( "RenewCMS/internal/domain/article" "RenewCMS/internal/domain/author" "RenewCMS/internal/domain/image" "RenewCMS/internal/domain/site" "RenewCMS/internal/domain/user" "strings" ) type MediaItem struct { Image image.Image UploadedBy string UsedIn int Duplicate bool VariantCount int VariantSize int64 } type MediaCounts struct { All int Unused int Duplicates int } type MediaPage struct { Items []MediaItem Total int Page int Size int Counts MediaCounts } type ListMediaUseCase struct { imageRepository image.Repository articleRepository article.Repository userRepository user.Repository variantFiles image.VariantFileRepository sites site.Repository authors author.Repository } func NewListMediaUseCase(imageRepository image.Repository, articleRepository article.Repository, userRepository user.Repository, variantFiles image.VariantFileRepository, sites site.Repository, authors author.Repository) *ListMediaUseCase { return &ListMediaUseCase{imageRepository: imageRepository, articleRepository: articleRepository, userRepository: userRepository, variantFiles: variantFiles, sites: sites, authors: authors} } func (l *ListMediaUseCase) List(query image.ListQuery) (MediaPage, error) { if err := query.Validate(); err != nil { return MediaPage{}, err } usage, err := l.usageCounter() if err != nil { return MediaPage{}, err } duplicateHashes, err := l.duplicateHashes() if err != nil { return MediaPage{}, err } counts, err := l.counts(usage) if err != nil { return MediaPage{}, err } variantTotals, err := l.variantFiles.TotalsBySource() if err != nil { return MediaPage{}, err } images, err := l.imageRepository.List(image.SearchQuery{Search: strings.TrimSpace(query.Search), Hash: query.Hash}) if err != nil { return MediaPage{}, err } sortImages(images, query.Sort, variantTotals) matching := make([]image.Image, 0, len(images)) for _, img := range images { if matchesVariants(query.Variants, variantTotals[image.StoredName(img.Path)].Files) && matchesFilter(query.Filter, usage(img), duplicateHashes[img.SHA256] && img.SHA256 != "") { matching = append(matching, img) } } offset := (query.Page - 1) * query.Size pageStart := min(offset, len(matching)) pageEnd := min(pageStart+query.Size, len(matching)) names := newUploaderNames(l.userRepository) items := []MediaItem{} for _, img := range matching[pageStart:pageEnd] { uploadedBy, err := names.nameOf(img.UploadedByID) if err != nil { return MediaPage{}, err } own := variantTotals[image.StoredName(img.Path)] items = append(items, MediaItem{ Image: img, UploadedBy: uploadedBy, UsedIn: usage(img), Duplicate: img.SHA256 != "" && duplicateHashes[img.SHA256], VariantCount: own.Files, VariantSize: own.Bytes, }) } return MediaPage{Items: items, Total: len(matching), Page: query.Page, Size: query.Size, Counts: counts}, nil } func (l *ListMediaUseCase) Describe(img image.Image) (MediaItem, error) { articles, err := l.articleRepository.FindByBodyContaining(image.StoredName(img.Path)) if err != nil { return MediaItem{}, err } siteUsage, err := imageUsageCount(l.sites, l.authors) if err != nil { return MediaItem{}, err } uploadedBy, err := newUploaderNames(l.userRepository).nameOf(img.UploadedByID) if err != nil { return MediaItem{}, err } duplicate := false if img.SHA256 != "" { sameContent, err := l.imageRepository.FindByHash(img.SHA256) if err != nil { return MediaItem{}, err } duplicate = len(sameContent) > 1 } rows, err := l.variantFiles.ListBySource(image.StoredName(img.Path)) if err != nil { return MediaItem{}, err } var variantSize int64 for _, row := range rows { variantSize += row.Size } return MediaItem{Image: img, UploadedBy: uploadedBy, UsedIn: len(articles) + siteUsage[img.ID], Duplicate: duplicate, VariantCount: len(rows), VariantSize: variantSize}, nil } func (l *ListMediaUseCase) usageCounter() (func(image.Image) int, error) { articles, err := l.articleRepository.FindByFilters(article.Filters{}) if err != nil { return nil, err } siteUsage, err := imageUsageCount(l.sites, l.authors) if err != nil { return nil, err } return func(img image.Image) int { storedName := image.StoredName(img.Path) used := siteUsage[img.ID] for _, a := range articles { if strings.Contains(a.Body, storedName) { used++ } } return used }, nil } func (l *ListMediaUseCase) duplicateHashes() (map[string]bool, error) { duplicates, err := l.imageRepository.ListDuplicates() if err != nil { return nil, err } hashes := make(map[string]bool, len(duplicates)) for _, img := range duplicates { hashes[img.SHA256] = true } return hashes, nil } func (l *ListMediaUseCase) counts(usage func(image.Image) int) (MediaCounts, error) { libraryCounts, err := l.imageRepository.Counts() if err != nil { return MediaCounts{}, err } everything, err := l.imageRepository.List(image.SearchQuery{}) if err != nil { return MediaCounts{}, err } unused := 0 for _, img := range everything { if usage(img) == 0 { unused++ } } return MediaCounts{All: libraryCounts.All, Unused: unused, Duplicates: libraryCounts.DuplicateGroups}, nil } func sortImages(images []image.Image, order image.Sort, totals map[string]image.VariantFileTotals) { if order == image.SortTotalSize { image.SortImagesByTotalSize(images, totals) return } image.SortImages(images, order) } func matchesVariants(filter image.VariantsFilter, variantFiles int) bool { switch filter { case image.VariantsWith: return variantFiles > 0 case image.VariantsWithout: return variantFiles == 0 default: return true } } func matchesFilter(filter image.Filter, usedIn int, duplicate bool) bool { switch filter { case image.FilterUnused: return usedIn == 0 case image.FilterDuplicates: return duplicate default: return true } }