Parcourir la source

feat: add API routes `/v1/enclosures/{enclosureID}`

Pontus Jensen Karlsson il y a 1 an
Parent
commit
810b351772

+ 24 - 0
client/client.go

@@ -613,6 +613,30 @@ func (c *Client) Icon(iconID int64) (*FeedIcon, error) {
 	return feedIcon, nil
 }
 
+func (c *Client) Enclosure(enclosureID int64) (*Enclosure, error) {
+	body, err := c.request.Get(fmt.Sprintf("/v1/enclosures/%d", enclosureID))
+
+	if err != nil {
+		return nil, err
+	}
+
+	defer body.Close()
+
+	var enclosure *Enclosure
+
+	if err := json.NewDecoder(body).Decode(&enclosure); err != nil {
+		return nil, fmt.Errorf("miniflux: response error(%v)", err)
+	}
+
+	return enclosure, nil
+}
+
+func (c *Client) UpdateEnclosure(enclosureID int64, enclosureUpdate *EnclosureUpdateRequest) error {
+	_, err := c.request.Put(fmt.Sprintf("/v1/enclosures/%d", enclosureID), enclosureUpdate)
+
+	return err
+}
+
 func buildFilterQueryString(path string, filter *Filter) string {
 	if filter != nil {
 		values := url.Values{}

+ 11 - 6
client/model.go

@@ -242,12 +242,17 @@ type Entries []*Entry
 
 // Enclosure represents an attachment.
 type Enclosure struct {
-	ID       int64  `json:"id"`
-	UserID   int64  `json:"user_id"`
-	EntryID  int64  `json:"entry_id"`
-	URL      string `json:"url"`
-	MimeType string `json:"mime_type"`
-	Size     int    `json:"size"`
+	ID               int64  `json:"id"`
+	UserID           int64  `json:"user_id"`
+	EntryID          int64  `json:"entry_id"`
+	URL              string `json:"url"`
+	MimeType         string `json:"mime_type"`
+	Size             int    `json:"size"`
+	MediaProgression int64  `json:"media_progression"`
+}
+
+type EnclosureUpdateRequest struct {
+	MediaProgression int64 `json:"media_progression"`
 }
 
 // Enclosures represents a list of attachments.

+ 2 - 0
internal/api/api.go

@@ -72,6 +72,8 @@ func Serve(router *mux.Router, store *storage.Storage, pool *worker.Pool) {
 	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)
+	sr.HandleFunc("/enclosures/{enclosureID}", handler.getEnclosureById).Methods(http.MethodGet)
+	sr.HandleFunc("/enclosures/{enclosureID}", handler.updateEnclosureById).Methods(http.MethodPut)
 	sr.HandleFunc("/version", handler.versionHandler).Methods(http.MethodGet)
 }
 

+ 112 - 0
internal/api/api_integration_test.go

@@ -2044,6 +2044,118 @@ func TestGetGlobalEntriesEndpoint(t *testing.T) {
 	}
 }
 
+func TestUpdateEnclosureEndpoint(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)
+	}
+
+	result, err := regularUserClient.FeedEntries(feedID, nil)
+	if err != nil {
+		t.Fatalf(`Failed to get entries: %v`, err)
+	}
+
+	var enclosure *miniflux.Enclosure
+
+	for _, entry := range result.Entries {
+		if len(entry.Enclosures) > 0 {
+			enclosure = entry.Enclosures[0]
+			break
+		}
+	}
+
+	if enclosure == nil {
+		t.Skip(`Skipping test, missing enclosure in feed.`)
+	}
+
+	err = regularUserClient.UpdateEnclosure(enclosure.ID, &miniflux.EnclosureUpdateRequest{
+		MediaProgression: 20,
+	})
+
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	updatedEnclosure, err := regularUserClient.Enclosure(enclosure.ID)
+
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if updatedEnclosure.MediaProgression != 20 {
+		t.Fatalf(`Failed to update media_progression, expected %d but got %d`, 20, updatedEnclosure.MediaProgression)
+	}
+}
+
+func TestGetEnclosureEndpoint(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)
+	}
+
+	result, err := regularUserClient.FeedEntries(feedID, nil)
+	if err != nil {
+		t.Fatalf(`Failed to get entries: %v`, err)
+	}
+
+	var expectedEnclosure *miniflux.Enclosure
+
+	for _, entry := range result.Entries {
+		if len(entry.Enclosures) > 0 {
+			expectedEnclosure = entry.Enclosures[0]
+			break
+		}
+	}
+
+	if expectedEnclosure == nil {
+		t.Skip(`Skipping test, missing enclosure in feed.`)
+	}
+
+	enclosure, err := regularUserClient.Enclosure(expectedEnclosure.ID)
+
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if enclosure.ID != expectedEnclosure.ID {
+		t.Fatalf(`Invalid enclosureID, got %d while expecting %d`, enclosure.ID, expectedEnclosure.ID)
+	}
+}
 func TestGetEntryEndpoints(t *testing.T) {
 	testConfig := newIntegrationTestConfig()
 	if !testConfig.isConfigured() {

+ 80 - 0
internal/api/enclosure.go

@@ -0,0 +1,80 @@
+// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+package api // import "miniflux.app/v2/internal/api"
+
+import (
+	json_parser "encoding/json"
+	"net/http"
+
+	"miniflux.app/v2/internal/http/request"
+	"miniflux.app/v2/internal/http/response/json"
+	"miniflux.app/v2/internal/model"
+	"miniflux.app/v2/internal/validator"
+)
+
+func (h *handler) getEnclosureById(w http.ResponseWriter, r *http.Request) {
+	enclosureID := request.RouteInt64Param(r, "enclosureID")
+
+	enclosure, err := h.store.GetEnclosure(enclosureID)
+
+	if err != nil {
+		json.NotFound(w, r)
+		return
+	}
+
+	userID := request.UserID(r)
+
+	if enclosure.UserID != userID {
+		json.NotFound(w, r)
+		return
+	}
+
+	enclosure.ProxifyEnclosureURL(h.router)
+
+	json.OK(w, r, enclosure)
+}
+
+func (h *handler) updateEnclosureById(w http.ResponseWriter, r *http.Request) {
+	enclosureID := request.RouteInt64Param(r, "enclosureID")
+
+	var enclosureUpdateRequest model.EnclosureUpdateRequest
+
+	if err := json_parser.NewDecoder(r.Body).Decode(&enclosureUpdateRequest); err != nil {
+		json.BadRequest(w, r, err)
+		return
+	}
+
+	if err := validator.ValidateEnclosureUpdateRequest(&enclosureUpdateRequest); err != nil {
+		json.BadRequest(w, r, err)
+		return
+	}
+
+	enclosure, err := h.store.GetEnclosure(enclosureID)
+
+	if err != nil {
+		json.BadRequest(w, r, err)
+		return
+	}
+
+	if enclosure == nil {
+		json.NotFound(w, r)
+		return
+	}
+
+	userID := request.UserID(r)
+
+	if enclosure.UserID != userID {
+		json.NotFound(w, r)
+		return
+	}
+
+	enclosure.MediaProgression = enclosureUpdateRequest.MediaProgression
+
+	if err := h.store.UpdateEnclosure(enclosure); err != nil {
+		json.ServerError(w, r, err)
+		return
+	}
+
+	json.NoContent(w, r)
+}

+ 2 - 15
internal/api/entry.go

@@ -8,10 +8,8 @@ import (
 	"errors"
 	"net/http"
 	"strconv"
-	"strings"
 	"time"
 
-	"miniflux.app/v2/internal/config"
 	"miniflux.app/v2/internal/http/request"
 	"miniflux.app/v2/internal/http/response/json"
 	"miniflux.app/v2/internal/integration"
@@ -20,7 +18,6 @@ import (
 	"miniflux.app/v2/internal/reader/processor"
 	"miniflux.app/v2/internal/reader/readingtime"
 	"miniflux.app/v2/internal/storage"
-	"miniflux.app/v2/internal/urllib"
 	"miniflux.app/v2/internal/validator"
 )
 
@@ -37,18 +34,8 @@ func (h *handler) getEntryFromBuilder(w http.ResponseWriter, r *http.Request, b
 	}
 
 	entry.Content = mediaproxy.RewriteDocumentWithAbsoluteProxyURL(h.router, entry.Content)
-	proxyOption := config.Opts.MediaProxyMode()
-
-	for i := range entry.Enclosures {
-		if proxyOption == "all" || proxyOption != "none" && !urllib.IsHTTPS(entry.Enclosures[i].URL) {
-			for _, mediaType := range config.Opts.MediaProxyResourceTypes() {
-				if strings.HasPrefix(entry.Enclosures[i].MimeType, mediaType+"/") {
-					entry.Enclosures[i].URL = mediaproxy.ProxifyAbsoluteURL(h.router, entry.Enclosures[i].URL)
-					break
-				}
-			}
-		}
-	}
+
+	entry.Enclosures.ProxifyEnclosureURL(h.router)
 
 	json.OK(w, r, entry)
 }

+ 2 - 13
internal/googlereader/handler.go

@@ -24,7 +24,6 @@ import (
 	mff "miniflux.app/v2/internal/reader/handler"
 	mfs "miniflux.app/v2/internal/reader/subscription"
 	"miniflux.app/v2/internal/storage"
-	"miniflux.app/v2/internal/urllib"
 	"miniflux.app/v2/internal/validator"
 
 	"github.com/gorilla/mux"
@@ -1004,18 +1003,8 @@ func (h *handler) streamItemContentsHandler(w http.ResponseWriter, r *http.Reque
 		}
 
 		entry.Content = mediaproxy.RewriteDocumentWithAbsoluteProxyURL(h.router, entry.Content)
-		proxyOption := config.Opts.MediaProxyMode()
-
-		for i := range entry.Enclosures {
-			if proxyOption == "all" || proxyOption != "none" && !urllib.IsHTTPS(entry.Enclosures[i].URL) {
-				for _, mediaType := range config.Opts.MediaProxyResourceTypes() {
-					if strings.HasPrefix(entry.Enclosures[i].MimeType, mediaType+"/") {
-						entry.Enclosures[i].URL = mediaproxy.ProxifyAbsoluteURL(h.router, entry.Enclosures[i].URL)
-						break
-					}
-				}
-			}
-		}
+
+		entry.Enclosures.ProxifyEnclosureURL(h.router)
 
 		contentItems[i] = contentItem{
 			ID:            fmt.Sprintf(EntryIDLong, entry.ID),

+ 42 - 1
internal/model/enclosure.go

@@ -2,7 +2,14 @@
 // SPDX-License-Identifier: Apache-2.0
 
 package model // import "miniflux.app/v2/internal/model"
-import "strings"
+import (
+	"strings"
+
+	"github.com/gorilla/mux"
+	"miniflux.app/v2/internal/config"
+	"miniflux.app/v2/internal/mediaproxy"
+	"miniflux.app/v2/internal/urllib"
+)
 
 // Enclosure represents an attachment.
 type Enclosure struct {
@@ -15,6 +22,10 @@ type Enclosure struct {
 	MediaProgression int64  `json:"media_progression"`
 }
 
+type EnclosureUpdateRequest struct {
+	MediaProgression int64 `json:"media_progression"`
+}
+
 // Html5MimeType will modify the actual MimeType to allow direct playback from HTML5 player for some kind of MimeType
 func (e Enclosure) Html5MimeType() string {
 	if e.MimeType == "video/m4v" {
@@ -34,3 +45,33 @@ func (el EnclosureList) ContainsAudioOrVideo() bool {
 	}
 	return false
 }
+
+func (el EnclosureList) ProxifyEnclosureURL(router *mux.Router) {
+	proxyOption := config.Opts.MediaProxyMode()
+
+	if proxyOption == "all" || proxyOption != "none" {
+		for i := range el {
+			if urllib.IsHTTPS(el[i].URL) {
+				for _, mediaType := range config.Opts.MediaProxyResourceTypes() {
+					if strings.HasPrefix(el[i].MimeType, mediaType+"/") {
+						el[i].URL = mediaproxy.ProxifyAbsoluteURL(router, el[i].URL)
+						break
+					}
+				}
+			}
+		}
+	}
+}
+
+func (e *Enclosure) ProxifyEnclosureURL(router *mux.Router) {
+	proxyOption := config.Opts.MediaProxyMode()
+
+	if proxyOption == "all" || proxyOption != "none" && !urllib.IsHTTPS(e.URL) {
+		for _, mediaType := range config.Opts.MediaProxyResourceTypes() {
+			if strings.HasPrefix(e.MimeType, mediaType+"/") {
+				e.URL = mediaproxy.ProxifyAbsoluteURL(router, e.URL)
+				break
+			}
+		}
+	}
+}

+ 18 - 0
internal/validator/enclosure.go

@@ -0,0 +1,18 @@
+// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+package validator
+
+import (
+	"fmt"
+
+	"miniflux.app/v2/internal/model"
+)
+
+func ValidateEnclosureUpdateRequest(request *model.EnclosureUpdateRequest) error {
+	if request.MediaProgression < 0 {
+		return fmt.Errorf(`media progression must an positive integer`)
+	}
+
+	return nil
+}