From dd438d1ca43722930ee8a63a72cf28f6daefb9a2 Mon Sep 17 00:00:00 2001 From: ShukeBta <272197458+ShukeBta@users.noreply.github.com> Date: Fri, 26 Jun 2026 23:59:12 +0800 Subject: [PATCH] split media recycle service --- internal/service/media.go | 98 --------------------------- internal/service/media_recycle.go | 106 ++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 98 deletions(-) create mode 100644 internal/service/media_recycle.go diff --git a/internal/service/media.go b/internal/service/media.go index 4c15b95..1b398b5 100644 --- a/internal/service/media.go +++ b/internal/service/media.go @@ -6,7 +6,6 @@ import ( "time" "go.uber.org/zap" - "gorm.io/gorm" "github.com/ShukeBta/MediaStationGo/internal/config" "github.com/ShukeBta/MediaStationGo/internal/model" @@ -268,100 +267,3 @@ func (s *MediaService) GetMedia(ctx context.Context, id string) (*model.Media, e *media = items[0] return media, nil } - -const maxRecycleBinRecords = 200 - -// SoftDelete moves a media row to the recycle bin (gorm soft delete). -// The on-disk file is kept; admins can purge it later. -func (s *MediaService) SoftDelete(ctx context.Context, id string) error { - media, err := s.repo.Media.FindByID(ctx, id) - if err != nil { - return err - } - if media != nil && isCloudMediaPath(media.Path) { - err := s.repo.DB.WithContext(ctx).Unscoped().Where("id = ?", id).Delete(&model.Media{}).Error - if err == nil { - s.invalidateMediaCache(ctx) - } - return err - } - err = s.repo.DB.WithContext(ctx).Where("id = ?", id).Delete(&model.Media{}).Error - if err == nil { - if pruneErr := pruneRecycleBinRows(ctx, s.repo.DB, maxRecycleBinRecords); pruneErr != nil { - return pruneErr - } - s.invalidateMediaCache(ctx) - } - return err -} - -// RestoreDeleted unsets DeletedAt for a single media row. -func (s *MediaService) RestoreDeleted(ctx context.Context, id string) error { - err := s.repo.DB.WithContext(ctx).Unscoped().Model(&model.Media{}). - Where("id = ?", id).Update("deleted_at", nil).Error - if err == nil { - s.invalidateMediaCache(ctx) - } - return err -} - -// ListRecycleBin returns every soft-deleted row, newest first. -func (s *MediaService) ListRecycleBin(ctx context.Context, limit int) ([]model.Media, error) { - if err := pruneRecycleBinRows(ctx, s.repo.DB, maxRecycleBinRecords); err != nil { - return nil, err - } - if limit <= 0 || limit > maxRecycleBinRecords { - limit = maxRecycleBinRecords - } - var rows []model.Media - err := s.repo.DB.Unscoped(). - Where("deleted_at IS NOT NULL"). - Order("deleted_at desc"). - Limit(limit). - Find(&rows).Error - return rows, err -} - -func pruneRecycleBinRows(ctx context.Context, db *gorm.DB, keep int) error { - if db == nil { - return nil - } - if keep <= 0 { - keep = maxRecycleBinRecords - } - var rows []struct { - ID string - } - if err := db.WithContext(ctx).Unscoped(). - Model(&model.Media{}). - Select("id"). - Where("deleted_at IS NOT NULL"). - Order("deleted_at desc"). - Limit(100000). - Offset(keep). - Find(&rows).Error; err != nil { - return err - } - if len(rows) == 0 { - return nil - } - ids := make([]string, 0, len(rows)) - for _, row := range rows { - if row.ID != "" { - ids = append(ids, row.ID) - } - } - if len(ids) == 0 { - return nil - } - return db.WithContext(ctx).Unscoped().Where("id IN ?", ids).Delete(&model.Media{}).Error -} - -// PurgeDeleted permanently removes a soft-deleted row from the database. -func (s *MediaService) PurgeDeleted(ctx context.Context, id string) error { - err := s.repo.DB.WithContext(ctx).Unscoped().Where("id = ?", id).Delete(&model.Media{}).Error - if err == nil { - s.invalidateMediaCache(ctx) - } - return err -} diff --git a/internal/service/media_recycle.go b/internal/service/media_recycle.go new file mode 100644 index 0000000..9c959fe --- /dev/null +++ b/internal/service/media_recycle.go @@ -0,0 +1,106 @@ +package service + +import ( + "context" + + "gorm.io/gorm" + + "github.com/ShukeBta/MediaStationGo/internal/model" +) + +const maxRecycleBinRecords = 200 + +// SoftDelete moves a media row to the recycle bin (gorm soft delete). +// The on-disk file is kept; admins can purge it later. +func (s *MediaService) SoftDelete(ctx context.Context, id string) error { + media, err := s.repo.Media.FindByID(ctx, id) + if err != nil { + return err + } + if media != nil && isCloudMediaPath(media.Path) { + err := s.repo.DB.WithContext(ctx).Unscoped().Where("id = ?", id).Delete(&model.Media{}).Error + if err == nil { + s.invalidateMediaCache(ctx) + } + return err + } + err = s.repo.DB.WithContext(ctx).Where("id = ?", id).Delete(&model.Media{}).Error + if err == nil { + if pruneErr := pruneRecycleBinRows(ctx, s.repo.DB, maxRecycleBinRecords); pruneErr != nil { + return pruneErr + } + s.invalidateMediaCache(ctx) + } + return err +} + +// RestoreDeleted unsets DeletedAt for a single media row. +func (s *MediaService) RestoreDeleted(ctx context.Context, id string) error { + err := s.repo.DB.WithContext(ctx).Unscoped().Model(&model.Media{}). + Where("id = ?", id).Update("deleted_at", nil).Error + if err == nil { + s.invalidateMediaCache(ctx) + } + return err +} + +// ListRecycleBin returns every soft-deleted row, newest first. +func (s *MediaService) ListRecycleBin(ctx context.Context, limit int) ([]model.Media, error) { + if err := pruneRecycleBinRows(ctx, s.repo.DB, maxRecycleBinRecords); err != nil { + return nil, err + } + if limit <= 0 || limit > maxRecycleBinRecords { + limit = maxRecycleBinRecords + } + var rows []model.Media + err := s.repo.DB.Unscoped(). + Where("deleted_at IS NOT NULL"). + Order("deleted_at desc"). + Limit(limit). + Find(&rows).Error + return rows, err +} + +func pruneRecycleBinRows(ctx context.Context, db *gorm.DB, keep int) error { + if db == nil { + return nil + } + if keep <= 0 { + keep = maxRecycleBinRecords + } + var rows []struct { + ID string + } + if err := db.WithContext(ctx).Unscoped(). + Model(&model.Media{}). + Select("id"). + Where("deleted_at IS NOT NULL"). + Order("deleted_at desc"). + Limit(100000). + Offset(keep). + Find(&rows).Error; err != nil { + return err + } + if len(rows) == 0 { + return nil + } + ids := make([]string, 0, len(rows)) + for _, row := range rows { + if row.ID != "" { + ids = append(ids, row.ID) + } + } + if len(ids) == 0 { + return nil + } + return db.WithContext(ctx).Unscoped().Where("id IN ?", ids).Delete(&model.Media{}).Error +} + +// PurgeDeleted permanently removes a soft-deleted row from the database. +func (s *MediaService) PurgeDeleted(ctx context.Context, id string) error { + err := s.repo.DB.WithContext(ctx).Unscoped().Where("id = ?", id).Delete(&model.Media{}).Error + if err == nil { + s.invalidateMediaCache(ctx) + } + return err +}