mirror of
https://github.com/truewhile/MeBox.git
synced 2026-09-29 03:26:37 +08:00
fix active subscription visibility after updates
This commit is contained in:
@@ -21,7 +21,11 @@ func (r *SubscriptionRepository) Create(ctx context.Context, s *model.Subscripti
|
||||
// intentionally excluded from scheduler polling and the active management list.
|
||||
func (r *SubscriptionRepository) List(ctx context.Context) ([]model.Subscription, error) {
|
||||
var rows []model.Subscription
|
||||
err := r.db.WithContext(ctx).Where("archived_at IS NULL").Order("created_at desc").Find(&rows).Error
|
||||
err := r.db.WithContext(ctx).Unscoped().
|
||||
Where("archived_at IS NULL").
|
||||
Where("deleted_at IS NULL OR enabled = ?", true).
|
||||
Order("created_at desc").
|
||||
Find(&rows).Error
|
||||
return rows, err
|
||||
}
|
||||
|
||||
|
||||
@@ -138,7 +138,12 @@ func (s *SubscriptionService) Delete(ctx context.Context, id string) error {
|
||||
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return err
|
||||
}
|
||||
return s.repo.DB.WithContext(ctx).Where("id = ?", id).Delete(&model.Subscription{}).Error
|
||||
if err := s.repo.DB.WithContext(ctx).Unscoped().Where("id = ?", id).First(&sub).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := s.deleteSubscriptionDownloads(ctx, &sub); err != nil {
|
||||
return err
|
||||
@@ -146,7 +151,15 @@ func (s *SubscriptionService) Delete(ctx context.Context, id string) error {
|
||||
if s.repo.Setting != nil {
|
||||
_ = s.repo.Setting.Delete(ctx, fmt.Sprintf("subscription.%s.seen", id))
|
||||
}
|
||||
return s.repo.DB.Where("id = ?", id).Delete(&model.Subscription{}).Error
|
||||
return s.repo.DB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Unscoped().Model(&model.Subscription{}).Where("id = ?", id).Update("enabled", false).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if sub.DeletedAt.Valid {
|
||||
return nil
|
||||
}
|
||||
return tx.Where("id = ?", id).Delete(&model.Subscription{}).Error
|
||||
})
|
||||
}
|
||||
|
||||
// RunNow forces a poll for one subscription, ignoring its schedule. Used
|
||||
|
||||
@@ -20,14 +20,16 @@ func (s *SubscriptionService) History(ctx context.Context) ([]model.Subscription
|
||||
// rule can match resources again when it is run next.
|
||||
func (s *SubscriptionService) Restore(ctx context.Context, id string) (*model.Subscription, error) {
|
||||
var sub model.Subscription
|
||||
if err := s.repo.DB.WithContext(ctx).Where("id = ?", id).First(&sub).Error; err != nil {
|
||||
if err := s.repo.DB.WithContext(ctx).Unscoped().Where("id = ?", id).First(&sub).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := s.repo.DB.WithContext(ctx).Model(&model.Subscription{}).
|
||||
if err := s.repo.DB.WithContext(ctx).Unscoped().Model(&model.Subscription{}).
|
||||
Where("id = ?", id).
|
||||
Updates(map[string]any{
|
||||
"enabled": true,
|
||||
"archived_at": nil,
|
||||
"archive_reason": "",
|
||||
"deleted_at": nil,
|
||||
// 重置为 0:此前可能被 feed 低估并锁死(updateSubscriptionTotalEpisodes
|
||||
// 只增不减,resolveSubscriptionTotalEpisodes 见 >0 即不再回查元数据)。
|
||||
// 归零后下次 run 会从 TMDb/豆瓣等权威源重算真实总集数,避免恢复后
|
||||
@@ -36,10 +38,6 @@ func (s *SubscriptionService) Restore(ctx context.Context, id string) (*model.Su
|
||||
}).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := s.repo.DB.WithContext(ctx).
|
||||
Exec("UPDATE subscriptions SET archived_at = NULL WHERE id = ?", id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if s.repo.Setting != nil {
|
||||
_ = s.repo.Setting.Delete(ctx, fmt.Sprintf("subscription.%s.seen", id))
|
||||
}
|
||||
|
||||
@@ -268,3 +268,50 @@ func TestRestoreArchivedSubscriptionReturnsToActiveAndClearsSeenState(t *testing
|
||||
t.Fatalf("seen state = %q, want cleared", seen)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRestoreSoftDeletedArchivedSubscriptionReturnsToActive(t *testing.T) {
|
||||
db := newServiceTestDB(t, &model.Subscription{}, &model.Setting{})
|
||||
repos := repository.New(db)
|
||||
svc := NewSubscriptionService(nil, zap.NewNop(), repos, nil, nil, NewHub(zap.NewNop()))
|
||||
sub := &model.Subscription{
|
||||
Name: "Legacy Hidden History 自动订阅",
|
||||
FeedURL: "https://rss.example/feed",
|
||||
Filter: "Legacy Hidden History",
|
||||
MediaType: "tv",
|
||||
TotalEpisodes: 12,
|
||||
}
|
||||
if err := repos.Subscription.Create(t.Context(), sub); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
archivedAt := time.Now()
|
||||
if err := repos.Subscription.Archive(t.Context(), sub.ID, "订阅完成:12/12", archivedAt); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := db.Where("id = ?", sub.ID).Delete(&model.Subscription{}).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
restored, err := svc.Restore(t.Context(), sub.ID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if restored.ArchivedAt != nil || restored.ArchiveReason != "" || !restored.Enabled || restored.TotalEpisodes != 0 {
|
||||
t.Fatalf("restored subscription not reset: %#v", restored)
|
||||
}
|
||||
active, err := repos.Subscription.List(t.Context())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(active) != 1 || active[0].ID != sub.ID {
|
||||
t.Fatalf("active subscriptions = %#v, want restored legacy subscription", active)
|
||||
}
|
||||
var deletedCount int64
|
||||
if err := db.Unscoped().Model(&model.Subscription{}).
|
||||
Where("id = ? AND deleted_at IS NOT NULL", sub.ID).
|
||||
Count(&deletedCount).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if deletedCount != 0 {
|
||||
t.Fatal("restored subscription kept deleted_at set")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,6 +90,20 @@ func TestDeleteSubscriptionRemovesDownloaderTaskAndSeenState(t *testing.T) {
|
||||
if count != 0 {
|
||||
t.Fatalf("active subscription count = %d, want 0", count)
|
||||
}
|
||||
var deleted model.Subscription
|
||||
if err := db.Unscoped().Where("id = ?", sub.ID).First(&deleted).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if deleted.Enabled {
|
||||
t.Fatal("deleted subscription stayed enabled; active legacy compatibility would show it again")
|
||||
}
|
||||
active, err := repos.Subscription.List(t.Context())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(active) != 0 {
|
||||
t.Fatalf("active subscriptions = %#v, want deleted subscription hidden", active)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeletedDownloadTaskDoesNotBlockSubscriptionReadd(t *testing.T) {
|
||||
@@ -100,3 +114,67 @@ func TestDeletedDownloadTaskDoesNotBlockSubscriptionReadd(t *testing.T) {
|
||||
t.Fatal("removed download task must not block subscription re-add")
|
||||
}
|
||||
}
|
||||
|
||||
func TestListIncludesEnabledSoftDeletedActiveSubscription(t *testing.T) {
|
||||
db := newServiceTestDB(t, &model.Subscription{})
|
||||
repos := repository.New(db)
|
||||
sub := &model.Subscription{
|
||||
Name: "Hidden Active 自动订阅",
|
||||
FeedURL: "site-search://search?keyword=Hidden%20Active",
|
||||
Filter: "Hidden Active",
|
||||
Enabled: true,
|
||||
}
|
||||
if err := repos.Subscription.Create(t.Context(), sub); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := db.Where("id = ?", sub.ID).Delete(&model.Subscription{}).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
active, err := repos.Subscription.List(t.Context())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(active) != 1 || active[0].ID != sub.ID {
|
||||
t.Fatalf("active subscriptions = %#v, want soft-deleted enabled subscription recovered", active)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteRecoveredSoftDeletedSubscriptionClearsSeenAndHidesIt(t *testing.T) {
|
||||
db := newServiceTestDB(t, &model.Subscription{}, &model.Setting{}, &model.DownloadTask{})
|
||||
repos := repository.New(db)
|
||||
svc := NewSubscriptionService(nil, zap.NewNop(), repos, nil, nil, NewHub(zap.NewNop()))
|
||||
sub := &model.Subscription{
|
||||
Name: "Recovered Hidden 自动订阅",
|
||||
FeedURL: "site-search://search?keyword=Recovered%20Hidden",
|
||||
Filter: "Recovered Hidden",
|
||||
Enabled: true,
|
||||
}
|
||||
if err := repos.Subscription.Create(t.Context(), sub); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := repos.Setting.Set(t.Context(), "subscription."+sub.ID+".seen", "old-guid"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := db.Where("id = ?", sub.ID).Delete(&model.Subscription{}).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := svc.Delete(t.Context(), sub.ID); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
active, err := repos.Subscription.List(t.Context())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(active) != 0 {
|
||||
t.Fatalf("active subscriptions = %#v, want recovered deleted subscription hidden", active)
|
||||
}
|
||||
seen, err := repos.Setting.Get(t.Context(), "subscription."+sub.ID+".seen")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if seen != "" {
|
||||
t.Fatalf("seen state = %q, want cleared", seen)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user