From e674dad3550a2aeee7b79a5a6ffb51f5438dfabe Mon Sep 17 00:00:00 2001 From: jvoisin Date: Tue, 8 Jul 2025 20:38:40 +0200 Subject: [PATCH 1/2] test(integration): add an integration test for feed removal --- internal/api/api_integration_test.go | 29 ++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/internal/api/api_integration_test.go b/internal/api/api_integration_test.go index c645809e..3cf5a603 100644 --- a/internal/api/api_integration_test.go +++ b/internal/api/api_integration_test.go @@ -2842,3 +2842,32 @@ func TestFlushHistoryEndpoint(t *testing.T) { t.Fatalf(`Invalid total, got %d`, removedEntries.Total) } } +func TestRemoveFeedAsRegularUser(t *testing.T) { + testConfig := newIntegrationTestConfig() + if !testConfig.isConfigured() { + t.Skip(skipIntegrationTestsMessage) + } + + adminClient := miniflux.NewClient(testConfig.testBaseURL, testConfig.testAdminUsername, testConfig.testAdminPassword) + regularTestUser, err := adminClient.CreateUser(testConfig.genRandomUsername(), testConfig.testRegularPassword, false) + if err != nil { + t.Fatal(err) + } + defer adminClient.DeleteUser(regularTestUser.ID) + + regularUserClient := miniflux.NewClient(testConfig.testBaseURL, regularTestUser.Username, testConfig.testRegularPassword) + feedID, err := regularUserClient.CreateFeed(&miniflux.FeedCreationRequest{ + FeedURL: testConfig.testFeedURL, + }) + if err != nil { + t.Fatal(err) + } + + if err := regularUserClient.DeleteFeed(feedID); err != nil { + t.Fatal(err) + } + + if _, err := regularUserClient.Feed(feedID); err == nil { + t.Errorf(`Got a feed (%d) even though it was deleted, was expecting an error instead.`, feedID) + } +} From 41f8348419f83bfadfc3deb527e71d762e163263 Mon Sep 17 00:00:00 2001 From: jvoisin Date: Tue, 8 Jul 2025 17:39:42 +0200 Subject: [PATCH 2/2] perf(storage): speed up feed deletion --- internal/storage/feed.go | 27 +++------------------------ 1 file changed, 3 insertions(+), 24 deletions(-) diff --git a/internal/storage/feed.go b/internal/storage/feed.go index 6300364b..a5ed3f1f 100644 --- a/internal/storage/feed.go +++ b/internal/storage/feed.go @@ -7,7 +7,6 @@ import ( "database/sql" "errors" "fmt" - "log/slog" "sort" "miniflux.app/v2/internal/config" @@ -458,29 +457,9 @@ func (s *Storage) UpdateFeedError(feed *model.Feed) (err error) { // RemoveFeed removes a feed and all entries. // This operation can takes time if the feed has lot of entries. func (s *Storage) RemoveFeed(userID, feedID int64) error { - rows, err := s.db.Query(`SELECT id FROM entries WHERE user_id=$1 AND feed_id=$2`, userID, feedID) - if err != nil { - return fmt.Errorf(`store: unable to get user feed entries: %v`, err) - } - defer rows.Close() - - for rows.Next() { - var entryID int64 - if err := rows.Scan(&entryID); err != nil { - return fmt.Errorf(`store: unable to read user feed entry ID: %v`, err) - } - - slog.Debug("Deleting entry", - slog.Int64("user_id", userID), - slog.Int64("feed_id", feedID), - slog.Int64("entry_id", entryID), - ) - - if _, err := s.db.Exec(`DELETE FROM entries WHERE id=$1 AND user_id=$2`, entryID, userID); err != nil { - return fmt.Errorf(`store: unable to delete user feed entries #%d: %v`, entryID, err) - } - } - + // Since entries have a `references` on `feeds(id)` with an `on delete cascade`, + // we can simply remove the correct `feeds(id)` and postgresql will take care of deleting the + // corresponding entries. if _, err := s.db.Exec(`DELETE FROM feeds WHERE id=$1 AND user_id=$2`, feedID, userID); err != nil { return fmt.Errorf(`store: unable to delete feed #%d: %v`, feedID, err) }