From 5c4df28dee0e946d61ad794d3f68084b30e250d0 Mon Sep 17 00:00:00 2001 From: Pontus Jensen Karlsson Date: Tue, 6 Aug 2024 05:39:15 +0000 Subject: [PATCH] feat: Add API route /v1/enclosures/{enclosureId} The GET method exposes individual enclosures by their ID. The PUT method allows external clients to update media_progression, similiar to the internal API used by the UI. The media proxy code was refactored to a method on the Enclosure and EnclosureList structures to avoid duplicate code and risking diverging behavior between googlereader and the API. --- internal/api/api.go | 2 + internal/api/enclosure.go | 85 ++++++++++++++++++++++++++++++++ internal/api/entry.go | 17 +------ internal/googlereader/handler.go | 15 +----- internal/model/enclosure.go | 43 +++++++++++++++- internal/validator/enclosure.go | 18 +++++++ 6 files changed, 151 insertions(+), 29 deletions(-) create mode 100644 internal/api/enclosure.go create mode 100644 internal/validator/enclosure.go diff --git a/internal/api/api.go b/internal/api/api.go index 2963a990970..1109acda334 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/enclosure.go b/internal/api/enclosure.go new file mode 100644 index 00000000000..6b5e53a3206 --- /dev/null +++ b/internal/api/enclosure.go @@ -0,0 +1,85 @@ +// 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.ServerError(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.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 d8fc01f6849..fd06c70a418 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 7cce675c1b7..c3eb70afc46 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 c9ec485f7b6..0e794575505 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 00000000000..b33ac22e92b --- /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 +}