package useCases import ( "RenewCMS/internal/domain/audit" "RenewCMS/internal/domain/image" "context" "time" ) type PurgeVariantCacheCommand struct { Scope image.CachePurgeScope Variant string ImageID uint32 } type VariantCachePurgeResult struct { Scope image.CachePurgeScope Files int Bytes int64 Failed int } type PurgeVariantCacheUseCase struct { variants image.VariantRepository images image.Repository cache image.VariantCacheRepository files variantCache recorder audit.Recorder now func() time.Time } func NewPurgeVariantCacheUseCase(variants image.VariantRepository, images image.Repository, cache image.VariantCacheRepository, files image.VariantFileRepository, storage image.Storage, now func() time.Time, recorder audit.Recorder) *PurgeVariantCacheUseCase { return &PurgeVariantCacheUseCase{variants, images, cache, variantCache{files, storage}, recorder, now} } type cachePurgePlan struct { filter image.VariantFileFilter event audit.Event } func (u *PurgeVariantCacheUseCase) Purge(ctx context.Context, command PurgeVariantCacheCommand) (VariantCachePurgeResult, error) { plan, err := u.plan(command) if err != nil { return VariantCachePurgeResult{}, err } result, err := u.purgeMatching(plan.filter) result.Scope = command.Scope if err != nil { recordFailure(ctx, u.recorder, plan.event, err) return result, err } u.record(ctx, plan.event, result) return result, nil } func (u *PurgeVariantCacheUseCase) plan(command PurgeVariantCacheCommand) (cachePurgePlan, error) { switch command.Scope { case image.CacheScopeAll: return cachePurgePlan{event: audit.NewEvent(audit.ActionVariantPurgeAll, audit.CategoryMedia)}, nil case image.CacheScopeUnused: return u.unusedPlan() case image.CacheScopeVariant: return u.variantPlan(command.Variant) case image.CacheScopeImage: return u.imagePlan(command.ImageID) default: return cachePurgePlan{}, image.ErrCachePurgeScope } } func (u *PurgeVariantCacheUseCase) unusedPlan() (cachePurgePlan, error) { variants, err := u.variants.List() if err != nil { return cachePurgePlan{}, err } event := audit.NewEvent(audit.ActionVariantPurgeUnused, audit.CategoryMedia) filter := image.VariantFileFilter{Unused: &image.UnusedFilter{VariantNames: enabledNames(variants), Cutoff: image.UnusedCutoff(u.now())}} return cachePurgePlan{filter: filter, event: event}, nil } func (u *PurgeVariantCacheUseCase) variantPlan(name string) (cachePurgePlan, error) { variant, err := u.variants.Get(name) if err != nil { return cachePurgePlan{}, err } if variant.Name != name { return cachePurgePlan{}, image.ErrVariantNotFound } event := audit.NewEvent(audit.ActionVariantPurge, audit.CategoryMedia) event.Target = audit.VariantTarget(name) return cachePurgePlan{filter: image.VariantFileFilter{VariantName: name}, event: event}, nil } func (u *PurgeVariantCacheUseCase) imagePlan(id uint32) (cachePurgePlan, error) { img, err := u.images.Get(id) if err != nil { return cachePurgePlan{}, err } event := audit.NewEvent(audit.ActionVariantPurgeImage, audit.CategoryMedia) event.Target = imageTarget(img) return cachePurgePlan{filter: image.VariantFileFilter{SourceName: image.StoredName(img.Path)}, event: event}, nil } func (u *PurgeVariantCacheUseCase) purgeMatching(filter image.VariantFileFilter) (VariantCachePurgeResult, error) { var result VariantCachePurgeResult var after uint32 for { rows, err := u.cache.ListPage(filter, after, image.CachePurgeBatchSize) if err != nil { return result, err } for _, row := range rows { after = row.ID if err := u.files.purgeRow(row); err != nil { result.Failed++ continue } result.Files++ result.Bytes += row.Size } if len(rows) < image.CachePurgeBatchSize { return result, nil } } } func (u *PurgeVariantCacheUseCase) record(ctx context.Context, event audit.Event, result VariantCachePurgeResult) { if result.Files == 0 && result.Failed == 0 { return } event.Params = cachePurgeParams(event.Action, result) if result.Files == 0 { failFrom(ctx, u.recorder, event, GenericFailureReason) return } u.recorder.Record(ctx, event) } func cachePurgeParams(action string, result VariantCachePurgeResult) map[string]any { params := map[string]any{audit.ParamPurgedFiles: result.Files, audit.ParamPurgedBytes: result.Bytes} if action == audit.ActionVariantPurgeUnused { params[audit.ParamOlderThanDays] = image.UnusedVariantDays } if result.Failed > 0 { params[audit.ParamFailedFiles] = result.Failed } return params }