瀏覽代碼

Add new API endpoint /icons/{iconID}

Frédéric Guillot 2 年之前
父節點
當前提交
2002d60fbe
共有 6 個文件被更改,包括 62 次插入10 次删除
  1. 17 1
      client/client.go
  2. 2 1
      internal/api/api.go
  3. 22 1
      internal/api/icon.go
  4. 1 1
      internal/model/icon.go
  5. 2 2
      internal/storage/icon.go
  6. 18 4
      internal/tests/feed_test.go

+ 17 - 1
client/client.go

@@ -496,7 +496,7 @@ func (c *Client) SaveEntry(entryID int64) error {
 	return err
 }
 
-// FetchCounters
+// FetchCounters fetches feed counters.
 func (c *Client) FetchCounters() (*FeedCounters, error) {
 	body, err := c.request.Get("/v1/feeds/counters")
 	if err != nil {
@@ -518,6 +518,22 @@ func (c *Client) FlushHistory() error {
 	return err
 }
 
+// Icon fetches a feed icon.
+func (c *Client) Icon(iconID int64) (*FeedIcon, error) {
+	body, err := c.request.Get(fmt.Sprintf("/v1/icons/%d", iconID))
+	if err != nil {
+		return nil, err
+	}
+	defer body.Close()
+
+	var feedIcon *FeedIcon
+	if err := json.NewDecoder(body).Decode(&feedIcon); err != nil {
+		return nil, fmt.Errorf("miniflux: response error (%v)", err)
+	}
+
+	return feedIcon, nil
+}
+
 func buildFilterQueryString(path string, filter *Filter) string {
 	if filter != nil {
 		values := url.Values{}

+ 2 - 1
internal/api/api.go

@@ -54,7 +54,7 @@ func Serve(router *mux.Router, store *storage.Storage, pool *worker.Pool) {
 	sr.HandleFunc("/feeds/{feedID}", handler.getFeed).Methods(http.MethodGet)
 	sr.HandleFunc("/feeds/{feedID}", handler.updateFeed).Methods(http.MethodPut)
 	sr.HandleFunc("/feeds/{feedID}", handler.removeFeed).Methods(http.MethodDelete)
-	sr.HandleFunc("/feeds/{feedID}/icon", handler.feedIcon).Methods(http.MethodGet)
+	sr.HandleFunc("/feeds/{feedID}/icon", handler.getIconByFeedID).Methods(http.MethodGet)
 	sr.HandleFunc("/feeds/{feedID}/mark-all-as-read", handler.markFeedAsRead).Methods(http.MethodPut)
 	sr.HandleFunc("/export", handler.exportFeeds).Methods(http.MethodGet)
 	sr.HandleFunc("/import", handler.importFeeds).Methods(http.MethodPost)
@@ -67,4 +67,5 @@ func Serve(router *mux.Router, store *storage.Storage, pool *worker.Pool) {
 	sr.HandleFunc("/entries/{entryID}/save", handler.saveEntry).Methods(http.MethodPost)
 	sr.HandleFunc("/entries/{entryID}/fetch-content", handler.fetchContent).Methods(http.MethodGet)
 	sr.HandleFunc("/flush-history", handler.flushHistory).Methods(http.MethodPut, http.MethodDelete)
+	sr.HandleFunc("/icons/{iconID}", handler.getIconByIconID).Methods(http.MethodGet)
 }

+ 22 - 1
internal/api/icon.go

@@ -10,7 +10,7 @@ import (
 	"miniflux.app/v2/internal/http/response/json"
 )
 
-func (h *handler) feedIcon(w http.ResponseWriter, r *http.Request) {
+func (h *handler) getIconByFeedID(w http.ResponseWriter, r *http.Request) {
 	feedID := request.RouteInt64Param(r, "feedID")
 
 	if !h.store.HasIcon(feedID) {
@@ -35,3 +35,24 @@ func (h *handler) feedIcon(w http.ResponseWriter, r *http.Request) {
 		Data:     icon.DataURL(),
 	})
 }
+
+func (h *handler) getIconByIconID(w http.ResponseWriter, r *http.Request) {
+	iconID := request.RouteInt64Param(r, "iconID")
+
+	icon, err := h.store.IconByID(iconID)
+	if err != nil {
+		json.ServerError(w, r, err)
+		return
+	}
+
+	if icon == nil {
+		json.NotFound(w, r)
+		return
+	}
+
+	json.OK(w, r, &feedIconResponse{
+		ID:       icon.ID,
+		MimeType: icon.MimeType,
+		Data:     icon.DataURL(),
+	})
+}

+ 1 - 1
internal/model/icon.go

@@ -13,7 +13,7 @@ type Icon struct {
 	ID       int64  `json:"id"`
 	Hash     string `json:"hash"`
 	MimeType string `json:"mime_type"`
-	Content  []byte `json:"content"`
+	Content  []byte `json:"-"`
 }
 
 // DataURL returns the data URL of the icon.

+ 2 - 2
internal/storage/icon.go

@@ -27,7 +27,7 @@ func (s *Storage) IconByID(iconID int64) (*model.Icon, error) {
 	if err == sql.ErrNoRows {
 		return nil, nil
 	} else if err != nil {
-		return nil, fmt.Errorf("store: unable to fetch icon by hash: %v", err)
+		return nil, fmt.Errorf("store: unable to fetch icon #%d: %w", iconID, err)
 	}
 
 	return &icon, nil
@@ -63,7 +63,7 @@ func (s *Storage) IconByHash(icon *model.Icon) error {
 	if err == sql.ErrNoRows {
 		return nil
 	} else if err != nil {
-		return fmt.Errorf(`store: unable to fetch icon by hash: %v`, err)
+		return fmt.Errorf(`store: unable to fetch icon by hash %q: %v`, icon.Hash, err)
 	}
 
 	return nil

+ 18 - 4
internal/tests/feed_test.go

@@ -762,14 +762,28 @@ func TestGetFeedIcon(t *testing.T) {
 	}
 
 	if feedIcon.ID == 0 {
-		t.Fatalf(`Invalid feed icon ID, got "%v"`, feedIcon.ID)
+		t.Fatalf(`Invalid feed icon ID, got "%d"`, feedIcon.ID)
 	}
 
-	if feedIcon.MimeType != "image/x-icon" {
-		t.Fatalf(`Invalid feed icon mime type, got "%v" instead of "%v"`, feedIcon.MimeType, "image/x-icon")
+	expectedMimeType := "image/x-icon"
+	if feedIcon.MimeType != expectedMimeType {
+		t.Fatalf(`Invalid feed icon mime type, got %q instead of %q`, feedIcon.MimeType, expectedMimeType)
 	}
 
-	if !strings.Contains(feedIcon.Data, "image/x-icon") {
+	if !strings.HasPrefix(feedIcon.Data, expectedMimeType) {
+		t.Fatalf(`Invalid feed icon data, got "%v"`, feedIcon.Data)
+	}
+
+	feedIcon, err = client.Icon(feedIcon.ID)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if feedIcon.MimeType != expectedMimeType {
+		t.Fatalf(`Invalid feed icon mime type, got %q instead of %q`, feedIcon.MimeType, expectedMimeType)
+	}
+
+	if !strings.HasPrefix(feedIcon.Data, expectedMimeType) {
 		t.Fatalf(`Invalid feed icon data, got "%v"`, feedIcon.Data)
 	}
 }