diff --git a/api/api.go b/api/api.go index 4a186d6c..9d44aacd 100644 --- a/api/api.go +++ b/api/api.go @@ -47,6 +47,7 @@ func Serve(router *mux.Router, store *storage.Storage, pool *worker.Pool) { sr.HandleFunc("/discover", handler.discoverSubscriptions).Methods(http.MethodPost) sr.HandleFunc("/feeds", handler.createFeed).Methods(http.MethodPost) sr.HandleFunc("/feeds", handler.getFeeds).Methods(http.MethodGet) + sr.HandleFunc("/feeds/counters", handler.fetchCounters).Methods(http.MethodGet) sr.HandleFunc("/feeds/refresh", handler.refreshAllFeeds).Methods(http.MethodPut) sr.HandleFunc("/feeds/{feedID}/refresh", handler.refreshFeed).Methods(http.MethodPut) sr.HandleFunc("/feeds/{feedID}", handler.getFeed).Methods(http.MethodGet) diff --git a/api/feed.go b/api/feed.go index 87270ef0..725ed801 100644 --- a/api/feed.go +++ b/api/feed.go @@ -170,6 +170,16 @@ func (h *handler) getFeeds(w http.ResponseWriter, r *http.Request) { json.OK(w, r, feeds) } +func (h *handler) fetchCounters(w http.ResponseWriter, r *http.Request) { + counters, err := h.store.FetchCounters(request.UserID(r)) + if err != nil { + json.ServerError(w, r, err) + return + } + + json.OK(w, r, counters) +} + func (h *handler) getFeed(w http.ResponseWriter, r *http.Request) { feedID := request.RouteInt64Param(r, "feedID") feed, err := h.store.FeedByID(request.UserID(r), feedID) diff --git a/client/client.go b/client/client.go index 895c5886..1f4d11a8 100644 --- a/client/client.go +++ b/client/client.go @@ -502,6 +502,23 @@ func (c *Client) ToggleBookmark(entryID int64) error { return err } +// FetchCounters +func (c *Client) FetchCounters() (*FeedCounters, error) { + body, err := c.request.Get("/v1/feeds/counters") + if err != nil { + return nil, err + } + defer body.Close() + + var result FeedCounters + decoder := json.NewDecoder(body) + if err := decoder.Decode(&result); err != nil { + return nil, fmt.Errorf("miniflux: response error (%v)", err) + } + + return &result, nil +} + func buildFilterQueryString(path string, filter *Filter) string { if filter != nil { values := url.Values{} diff --git a/client/model.go b/client/model.go index 56b0c5f0..660b2829 100644 --- a/client/model.go +++ b/client/model.go @@ -180,6 +180,11 @@ type FeedIcon struct { Data string `json:"data"` } +type FeedCounters struct { + ReadCounters map[int64]int `json:"reads"` + UnreadCounters map[int64]int `json:"unreads"` +} + // Feeds represents a list of feeds. type Feeds []*Feed diff --git a/model/feed.go b/model/feed.go index 519a1b59..f515988f 100644 --- a/model/feed.go +++ b/model/feed.go @@ -56,6 +56,11 @@ type Feed struct { ReadCount int `json:"-"` } +type FeedCounters struct { + ReadCounters map[int64]int `json:"reads"` + UnreadCounters map[int64]int `json:"unreads"` +} + func (f *Feed) String() string { return fmt.Sprintf("ID=%d, UserID=%d, FeedURL=%s, SiteURL=%s, Title=%s, Category={%s}", f.ID, diff --git a/storage/feed.go b/storage/feed.go index 89b9b0ec..0c662950 100644 --- a/storage/feed.go +++ b/storage/feed.go @@ -158,6 +158,14 @@ func (s *Storage) FeedsWithCounters(userID int64) (model.Feeds, error) { return getFeedsSorted(builder) } +// Return read and unread count. +func (s *Storage) FetchCounters(userID int64) (model.FeedCounters, error) { + builder := NewFeedQueryBuilder(s, userID) + builder.WithCounters() + reads, unreads, err := builder.fetchFeedCounter() + return model.FeedCounters{ReadCounters: reads, UnreadCounters: unreads}, err +} + // FeedsByCategoryWithCounters returns all feeds of the given user/category with counters of read and unread entries. func (s *Storage) FeedsByCategoryWithCounters(userID, categoryID int64) (model.Feeds, error) { builder := NewFeedQueryBuilder(s, userID) diff --git a/tests/feed_test.go b/tests/feed_test.go index 14b4be11..289f6b91 100644 --- a/tests/feed_test.go +++ b/tests/feed_test.go @@ -677,6 +677,37 @@ func TestMarkFeedAsRead(t *testing.T) { } } +func TestFetchCounters(t *testing.T) { + client := createClient(t) + + feed, _ := createFeed(t, client) + + results, err := client.FeedEntries(feed.ID, nil) + if err != nil { + t.Fatalf(`Failed to get entries: %v`, err) + } + + counters, err := client.FetchCounters() + if err != nil { + t.Fatalf(`Failed to fetch unread count: %v`, err) + } + unreadCounter, exists := counters.UnreadCounters[feed.ID] + if !exists { + unreadCounter = 0 + } + + unreadExpected := 0 + for _, entry := range results.Entries { + if entry.Status == miniflux.EntryStatusUnread { + unreadExpected++ + } + } + + if unreadExpected != unreadCounter { + t.Errorf(`Expected %d unread entries but %d instead`, unreadExpected, unreadCounter) + } +} + func TestDeleteFeed(t *testing.T) { client := createClient(t) feed, _ := createFeed(t, client)