aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Pontus Jensen Karlsson <wolfhechel@users.noreply.github.com> 2024-08-18 20:53:19 +0200
committerGravatar GitHub <noreply@github.com> 2024-08-18 11:53:19 -0700
commit810b3517725b7e1c4f66ec9e40400bf1d1cf24dd (patch)
tree90586698031cb913a44f487641aca4081d120c8c
parent89ff33ddd0125782ee6dbe075a3e21c0eacda15e (diff)
downloadv2-810b3517725b7e1c4f66ec9e40400bf1d1cf24dd.tar.gz
v2-810b3517725b7e1c4f66ec9e40400bf1d1cf24dd.tar.zst
v2-810b3517725b7e1c4f66ec9e40400bf1d1cf24dd.zip
feat: add API routes `/v1/enclosures/{enclosureID}`
-rw-r--r--client/client.go24
-rw-r--r--client/model.go17
-rw-r--r--internal/api/api.go2
-rw-r--r--internal/api/api_integration_test.go112
-rw-r--r--internal/api/enclosure.go80
-rw-r--r--internal/api/entry.go17
-rw-r--r--internal/googlereader/handler.go15
-rw-r--r--internal/model/enclosure.go43
-rw-r--r--internal/validator/enclosure.go18
9 files changed, 293 insertions, 35 deletions
diff --git a/client/client.go b/client/client.go
index 2840c4e0..71ced695 100644
--- a/client/client.go
+++ b/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{}
diff --git a/client/model.go b/client/model.go
index 57b03d6e..69f2c227 100644
--- a/client/model.go
+++ b/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.
diff --git a/internal/api/api.go b/internal/api/api.go
index 2963a990..1109acda 100644
--- a/internal/api/api.go
+++ b/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)
}
diff --git a/internal/api/api_integration_test.go b/internal/api/api_integration_test.go
index c4bef142..3fd98119 100644
--- a/internal/api/api_integration_test.go
+++ b/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() {
diff --git a/internal/api/enclosure.go b/internal/api/enclosure.go
new file mode 100644
index 00000000..45222b33
--- /dev/null
+++ b/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)
+}
diff --git a/internal/api/entry.go b/internal/api/entry.go
index 121d2701..508bbee4 100644
--- a/internal/api/entry.go
+++ b/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)
}
diff --git a/internal/googlereader/handler.go b/internal/googlereader/handler.go
index 7cce675c..c3eb70af 100644
--- a/internal/googlereader/handler.go
+++ b/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),
diff --git a/internal/model/enclosure.go b/internal/model/enclosure.go
index c9ec485f..0e794575 100644
--- a/internal/model/enclosure.go
+++ b/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
+ }
+ }
+ }
+}
diff --git a/internal/validator/enclosure.go b/internal/validator/enclosure.go
new file mode 100644
index 00000000..b33ac22e
--- /dev/null
+++ b/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
+}