package photos import ( "sort" "strings" "time" "github.com/ultisuite/ulti-backend/internal/api/paginate" "github.com/ultisuite/ulti-backend/internal/api/query" photospkg "github.com/ultisuite/ulti-backend/internal/photos" ) var photosTimeFormats = []string{ time.RFC3339Nano, time.RFC3339, "2006-01-02T15:04:05.000Z", "2006-01-02", } func parsePhotosTime(raw string) (time.Time, bool) { raw = strings.TrimSpace(raw) if raw == "" { return time.Time{}, false } for _, layout := range photosTimeFormats { if t, err := time.Parse(layout, raw); err == nil { return t.UTC(), true } } return time.Time{}, false } func parseSortField(sort string) (field string, desc bool) { sort = strings.TrimSpace(sort) if sort == "" { return "created_at", true } if strings.HasPrefix(sort, "-") { return strings.TrimPrefix(sort, "-"), true } return sort, false } func assetMatchesDate(a photospkg.Asset, from, to *time.Time) bool { if from == nil && to == nil { return true } created, ok := parsePhotosTime(a.CreatedAt) if !ok { return false } if from != nil && created.Before(*from) { return false } if to != nil && created.After(*to) { return false } return true } func albumMatchesDate(a photospkg.Album, from, to *time.Time) bool { if from == nil && to == nil { return true } created, ok := parsePhotosTime(a.CreatedAt) if !ok { return false } if from != nil && created.Before(*from) { return false } if to != nil && created.After(*to) { return false } return true } func filterAssets(assets []photospkg.Asset, params query.ListParams) []photospkg.Asset { q := strings.ToLower(strings.TrimSpace(params.Q)) out := make([]photospkg.Asset, 0, len(assets)) for _, a := range assets { if q != "" { if !strings.Contains(strings.ToLower(a.OriginalName), q) && !strings.Contains(strings.ToLower(a.MimeType), q) { continue } } if !assetMatchesDate(a, params.From, params.To) { continue } out = append(out, a) } return out } func filterAlbums(albums []photospkg.Album, params query.ListParams) []photospkg.Album { q := strings.ToLower(strings.TrimSpace(params.Q)) out := make([]photospkg.Album, 0, len(albums)) for _, a := range albums { if q != "" && !strings.Contains(strings.ToLower(a.Name), q) { continue } if !albumMatchesDate(a, params.From, params.To) { continue } out = append(out, a) } return out } func sortAssets(assets []photospkg.Asset, sortParam string) { field, desc := parseSortField(sortParam) sort.SliceStable(assets, func(i, j int) bool { if desc { return compareAssets(assets[j], assets[i], field) } return compareAssets(assets[i], assets[j], field) }) } func compareAssets(a, b photospkg.Asset, field string) bool { switch field { case "name": return strings.ToLower(a.OriginalName) < strings.ToLower(b.OriginalName) case "size": return a.FileSize < b.FileSize default: // created_at ti, okI := parsePhotosTime(a.CreatedAt) tj, okJ := parsePhotosTime(b.CreatedAt) if okI && okJ { return ti.Before(tj) } return a.CreatedAt < b.CreatedAt } } func sortAlbums(albums []photospkg.Album, sortParam string) { field, desc := parseSortField(sortParam) sort.SliceStable(albums, func(i, j int) bool { if desc { return compareAlbums(albums[j], albums[i], field) } return compareAlbums(albums[i], albums[j], field) }) } func compareAlbums(a, b photospkg.Album, field string) bool { switch field { case "name": return strings.ToLower(a.Name) < strings.ToLower(b.Name) case "size": return a.AssetCount < b.AssetCount default: // created_at ti, okI := parsePhotosTime(a.CreatedAt) tj, okJ := parsePhotosTime(b.CreatedAt) if okI && okJ { return ti.Before(tj) } return a.CreatedAt < b.CreatedAt } } func applyAssetList(assets []photospkg.Asset, params query.ListParams) ([]photospkg.Asset, int64) { filtered := filterAssets(assets, params) sortAssets(filtered, params.Sort) return paginate.Slice(filtered, params.Offset(), params.Limit()) } func applyAlbumList(albums []photospkg.Album, params query.ListParams) ([]photospkg.Album, int64) { filtered := filterAlbums(albums, params) sortAlbums(filtered, params.Sort) return paginate.Slice(filtered, params.Offset(), params.Limit()) }