1
0
Fork 0
mirror of https://github.com/miniflux/v2.git synced 2025-09-15 18:57:04 +00:00

Refactor feed validator

This commit is contained in:
Frédéric Guillot 2021-01-04 13:49:28 -08:00 committed by fguillot
parent b35fece3d5
commit 806b9545a9
32 changed files with 588 additions and 521 deletions

View file

@ -36,7 +36,7 @@ func Serve(router *mux.Router, store *storage.Storage, pool *worker.Pool) {
sr.HandleFunc("/categories/{categoryID}", handler.updateCategory).Methods(http.MethodPut)
sr.HandleFunc("/categories/{categoryID}", handler.removeCategory).Methods(http.MethodDelete)
sr.HandleFunc("/categories/{categoryID}/mark-all-as-read", handler.markCategoryAsRead).Methods(http.MethodPut)
sr.HandleFunc("/discover", handler.getSubscriptions).Methods(http.MethodPost)
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/refresh", handler.refreshAllFeeds).Methods(http.MethodPut)

View file

@ -5,60 +5,32 @@
package api // import "miniflux.app/api"
import (
"errors"
json_parser "encoding/json"
"net/http"
"time"
"miniflux.app/http/request"
"miniflux.app/http/response/json"
"miniflux.app/model"
feedHandler "miniflux.app/reader/handler"
"miniflux.app/validator"
)
func (h *handler) createFeed(w http.ResponseWriter, r *http.Request) {
feedInfo, err := decodeFeedCreationRequest(r.Body)
if err != nil {
userID := request.UserID(r)
var feedCreationRequest model.FeedCreationRequest
if err := json_parser.NewDecoder(r.Body).Decode(&feedCreationRequest); err != nil {
json.BadRequest(w, r, err)
return
}
if feedInfo.FeedURL == "" {
json.BadRequest(w, r, errors.New("The feed_url is required"))
if validationErr := validator.ValidateFeedCreation(h.store, userID, &feedCreationRequest); validationErr != nil {
json.BadRequest(w, r, validationErr.Error())
return
}
if feedInfo.CategoryID <= 0 {
json.BadRequest(w, r, errors.New("The category_id is required"))
return
}
userID := request.UserID(r)
if h.store.FeedURLExists(userID, feedInfo.FeedURL) {
json.BadRequest(w, r, errors.New("This feed_url already exists"))
return
}
if !h.store.CategoryIDExists(userID, feedInfo.CategoryID) {
json.BadRequest(w, r, errors.New("This category_id doesn't exists or doesn't belongs to this user"))
return
}
feed, err := feedHandler.CreateFeed(h.store, &feedHandler.FeedCreationArgs{
UserID: userID,
CategoryID: feedInfo.CategoryID,
FeedURL: feedInfo.FeedURL,
UserAgent: feedInfo.UserAgent,
Username: feedInfo.Username,
Password: feedInfo.Password,
Crawler: feedInfo.Crawler,
Disabled: feedInfo.Disabled,
IgnoreHTTPCache: feedInfo.IgnoreHTTPCache,
FetchViaProxy: feedInfo.FetchViaProxy,
ScraperRules: feedInfo.ScraperRules,
RewriteRules: feedInfo.RewriteRules,
BlocklistRules: feedInfo.BlocklistRules,
KeeplistRules: feedInfo.KeeplistRules,
})
feed, err := feedHandler.CreateFeed(h.store, userID, &feedCreationRequest)
if err != nil {
json.ServerError(w, r, err)
return
@ -101,14 +73,14 @@ func (h *handler) refreshAllFeeds(w http.ResponseWriter, r *http.Request) {
}
func (h *handler) updateFeed(w http.ResponseWriter, r *http.Request) {
feedID := request.RouteInt64Param(r, "feedID")
feedChanges, err := decodeFeedModificationRequest(r.Body)
if err != nil {
var feedModificationRequest model.FeedModificationRequest
if err := json_parser.NewDecoder(r.Body).Decode(&feedModificationRequest); err != nil {
json.BadRequest(w, r, err)
return
}
userID := request.UserID(r)
feedID := request.RouteInt64Param(r, "feedID")
originalFeed, err := h.store.FeedByID(userID, feedID)
if err != nil {
@ -121,13 +93,12 @@ func (h *handler) updateFeed(w http.ResponseWriter, r *http.Request) {
return
}
feedChanges.Update(originalFeed)
if !h.store.CategoryIDExists(userID, originalFeed.Category.ID) {
json.BadRequest(w, r, errors.New("This category_id doesn't exists or doesn't belongs to this user"))
if validationErr := validator.ValidateFeedModification(h.store, userID, &feedModificationRequest); validationErr != nil {
json.BadRequest(w, r, validationErr.Error())
return
}
feedModificationRequest.Patch(originalFeed)
if err := h.store.UpdateFeed(originalFeed); err != nil {
json.ServerError(w, r, err)
return

View file

@ -23,150 +23,10 @@ type entriesResponse struct {
Entries model.Entries `json:"entries"`
}
type subscriptionDiscoveryRequest struct {
URL string `json:"url"`
UserAgent string `json:"user_agent"`
Username string `json:"username"`
Password string `json:"password"`
FetchViaProxy bool `json:"fetch_via_proxy"`
}
func decodeSubscriptionDiscoveryRequest(r io.ReadCloser) (*subscriptionDiscoveryRequest, error) {
defer r.Close()
var s subscriptionDiscoveryRequest
decoder := json.NewDecoder(r)
if err := decoder.Decode(&s); err != nil {
return nil, fmt.Errorf("invalid JSON payload: %v", err)
}
return &s, nil
}
type feedCreationResponse struct {
FeedID int64 `json:"feed_id"`
}
type feedCreationRequest struct {
FeedURL string `json:"feed_url"`
CategoryID int64 `json:"category_id"`
UserAgent string `json:"user_agent"`
Username string `json:"username"`
Password string `json:"password"`
Crawler bool `json:"crawler"`
Disabled bool `json:"disabled"`
IgnoreHTTPCache bool `json:"ignore_http_cache"`
FetchViaProxy bool `json:"fetch_via_proxy"`
ScraperRules string `json:"scraper_rules"`
RewriteRules string `json:"rewrite_rules"`
BlocklistRules string `json:"blocklist_rules"`
KeeplistRules string `json:"keeplist_rules"`
}
func decodeFeedCreationRequest(r io.ReadCloser) (*feedCreationRequest, error) {
defer r.Close()
var fc feedCreationRequest
decoder := json.NewDecoder(r)
if err := decoder.Decode(&fc); err != nil {
return nil, fmt.Errorf("Invalid JSON payload: %v", err)
}
return &fc, nil
}
type feedModificationRequest struct {
FeedURL *string `json:"feed_url"`
SiteURL *string `json:"site_url"`
Title *string `json:"title"`
ScraperRules *string `json:"scraper_rules"`
RewriteRules *string `json:"rewrite_rules"`
BlocklistRules *string `json:"blocklist_rules"`
KeeplistRules *string `json:"keeplist_rules"`
Crawler *bool `json:"crawler"`
UserAgent *string `json:"user_agent"`
Username *string `json:"username"`
Password *string `json:"password"`
CategoryID *int64 `json:"category_id"`
Disabled *bool `json:"disabled"`
IgnoreHTTPCache *bool `json:"ignore_http_cache"`
FetchViaProxy *bool `json:"fetch_via_proxy"`
}
func (f *feedModificationRequest) Update(feed *model.Feed) {
if f.FeedURL != nil && *f.FeedURL != "" {
feed.FeedURL = *f.FeedURL
}
if f.SiteURL != nil && *f.SiteURL != "" {
feed.SiteURL = *f.SiteURL
}
if f.Title != nil && *f.Title != "" {
feed.Title = *f.Title
}
if f.ScraperRules != nil {
feed.ScraperRules = *f.ScraperRules
}
if f.RewriteRules != nil {
feed.RewriteRules = *f.RewriteRules
}
if f.KeeplistRules != nil {
feed.KeeplistRules = *f.KeeplistRules
}
if f.BlocklistRules != nil {
feed.BlocklistRules = *f.BlocklistRules
}
if f.Crawler != nil {
feed.Crawler = *f.Crawler
}
if f.UserAgent != nil {
feed.UserAgent = *f.UserAgent
}
if f.Username != nil {
feed.Username = *f.Username
}
if f.Password != nil {
feed.Password = *f.Password
}
if f.CategoryID != nil && *f.CategoryID > 0 {
feed.Category.ID = *f.CategoryID
}
if f.Disabled != nil {
feed.Disabled = *f.Disabled
}
if f.IgnoreHTTPCache != nil {
feed.IgnoreHTTPCache = *f.IgnoreHTTPCache
}
if f.FetchViaProxy != nil {
feed.FetchViaProxy = *f.FetchViaProxy
}
}
func decodeFeedModificationRequest(r io.ReadCloser) (*feedModificationRequest, error) {
defer r.Close()
var feed feedModificationRequest
decoder := json.NewDecoder(r)
if err := decoder.Decode(&feed); err != nil {
return nil, fmt.Errorf("Unable to decode feed modification JSON object: %v", err)
}
return &feed, nil
}
func decodeEntryStatusRequest(r io.ReadCloser) ([]int64, string, error) {
type payload struct {
EntryIDs []int64 `json:"entry_ids"`

View file

@ -1,220 +0,0 @@
// Copyright 2018 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
package api // import "miniflux.app/api"
import (
"testing"
"miniflux.app/model"
)
func TestUpdateFeedURL(t *testing.T) {
feedURL := "http://example.com/"
changes := &feedModificationRequest{FeedURL: &feedURL}
feed := &model.Feed{FeedURL: "http://example.org/"}
changes.Update(feed)
if feed.FeedURL != feedURL {
t.Errorf(`Unexpected value, got %q instead of %q`, feed.FeedURL, feedURL)
}
}
func TestUpdateFeedURLWithEmptyString(t *testing.T) {
feedURL := ""
changes := &feedModificationRequest{FeedURL: &feedURL}
feed := &model.Feed{FeedURL: "http://example.org/"}
changes.Update(feed)
if feed.FeedURL == feedURL {
t.Error(`The FeedURL should not be modified`)
}
}
func TestUpdateFeedURLWhenNotSet(t *testing.T) {
changes := &feedModificationRequest{}
feed := &model.Feed{FeedURL: "http://example.org/"}
changes.Update(feed)
if feed.FeedURL != "http://example.org/" {
t.Error(`The FeedURL should not be modified`)
}
}
func TestUpdateFeedSiteURL(t *testing.T) {
siteURL := "http://example.com/"
changes := &feedModificationRequest{SiteURL: &siteURL}
feed := &model.Feed{SiteURL: "http://example.org/"}
changes.Update(feed)
if feed.SiteURL != siteURL {
t.Errorf(`Unexpected value, got %q instead of %q`, feed.SiteURL, siteURL)
}
}
func TestUpdateFeedSiteURLWithEmptyString(t *testing.T) {
siteURL := ""
changes := &feedModificationRequest{FeedURL: &siteURL}
feed := &model.Feed{SiteURL: "http://example.org/"}
changes.Update(feed)
if feed.SiteURL == siteURL {
t.Error(`The FeedURL should not be modified`)
}
}
func TestUpdateFeedSiteURLWhenNotSet(t *testing.T) {
changes := &feedModificationRequest{}
feed := &model.Feed{SiteURL: "http://example.org/"}
changes.Update(feed)
if feed.SiteURL != "http://example.org/" {
t.Error(`The SiteURL should not be modified`)
}
}
func TestUpdateFeedTitle(t *testing.T) {
title := "Example 2"
changes := &feedModificationRequest{Title: &title}
feed := &model.Feed{Title: "Example"}
changes.Update(feed)
if feed.Title != title {
t.Errorf(`Unexpected value, got %q instead of %q`, feed.Title, title)
}
}
func TestUpdateFeedTitleWithEmptyString(t *testing.T) {
title := ""
changes := &feedModificationRequest{Title: &title}
feed := &model.Feed{Title: "Example"}
changes.Update(feed)
if feed.Title == title {
t.Error(`The Title should not be modified`)
}
}
func TestUpdateFeedTitleWhenNotSet(t *testing.T) {
changes := &feedModificationRequest{}
feed := &model.Feed{Title: "Example"}
changes.Update(feed)
if feed.Title != "Example" {
t.Error(`The Title should not be modified`)
}
}
func TestUpdateFeedUsername(t *testing.T) {
username := "Alice"
changes := &feedModificationRequest{Username: &username}
feed := &model.Feed{Username: "Bob"}
changes.Update(feed)
if feed.Username != username {
t.Errorf(`Unexpected value, got %q instead of %q`, feed.Username, username)
}
}
func TestUpdateFeedUsernameWithEmptyString(t *testing.T) {
username := ""
changes := &feedModificationRequest{Username: &username}
feed := &model.Feed{Username: "Bob"}
changes.Update(feed)
if feed.Username != "" {
t.Error(`The Username should be empty now`)
}
}
func TestUpdateFeedUsernameWhenNotSet(t *testing.T) {
changes := &feedModificationRequest{}
feed := &model.Feed{Username: "Alice"}
changes.Update(feed)
if feed.Username != "Alice" {
t.Error(`The Username should not be modified`)
}
}
func TestUpdateFeedDisabled(t *testing.T) {
valueTrue := true
valueFalse := false
scenarios := []struct {
changes *feedModificationRequest
feed *model.Feed
expected bool
}{
{&feedModificationRequest{}, &model.Feed{Disabled: true}, true},
{&feedModificationRequest{Disabled: &valueTrue}, &model.Feed{Disabled: true}, true},
{&feedModificationRequest{Disabled: &valueFalse}, &model.Feed{Disabled: true}, false},
{&feedModificationRequest{}, &model.Feed{Disabled: false}, false},
{&feedModificationRequest{Disabled: &valueTrue}, &model.Feed{Disabled: false}, true},
{&feedModificationRequest{Disabled: &valueFalse}, &model.Feed{Disabled: false}, false},
}
for _, scenario := range scenarios {
scenario.changes.Update(scenario.feed)
if scenario.feed.Disabled != scenario.expected {
t.Errorf(`Unexpected result, got %v, want: %v`,
scenario.feed.Disabled,
scenario.expected,
)
}
}
}
func TestUpdateFeedCategory(t *testing.T) {
categoryID := int64(1)
changes := &feedModificationRequest{CategoryID: &categoryID}
feed := &model.Feed{Category: &model.Category{ID: 42}}
changes.Update(feed)
if feed.Category.ID != categoryID {
t.Errorf(`Unexpected value, got %q instead of %q`, feed.Username, categoryID)
}
}
func TestUpdateFeedCategoryWithZero(t *testing.T) {
categoryID := int64(0)
changes := &feedModificationRequest{CategoryID: &categoryID}
feed := &model.Feed{Category: &model.Category{ID: 42}}
changes.Update(feed)
if feed.Category.ID != 42 {
t.Error(`The CategoryID should not be modified`)
}
}
func TestUpdateFeedCategoryWhenNotSet(t *testing.T) {
changes := &feedModificationRequest{}
feed := &model.Feed{Category: &model.Category{ID: 42}}
changes.Update(feed)
if feed.Category.ID != 42 {
t.Error(`The CategoryID should not be modified`)
}
}
func TestUpdateFeedToIgnoreCache(t *testing.T) {
value := true
changes := &feedModificationRequest{IgnoreHTTPCache: &value}
feed := &model.Feed{IgnoreHTTPCache: false}
changes.Update(feed)
if feed.IgnoreHTTPCache != value {
t.Errorf(`The field IgnoreHTTPCache should be %v`, value)
}
}
func TestUpdateFeedToFetchViaProxy(t *testing.T) {
value := true
changes := &feedModificationRequest{FetchViaProxy: &value}
feed := &model.Feed{FetchViaProxy: false}
changes.Update(feed)
if feed.FetchViaProxy != value {
t.Errorf(`The field FetchViaProxy should be %v`, value)
}
}

View file

@ -5,25 +5,33 @@
package api // import "miniflux.app/api"
import (
json_parser "encoding/json"
"net/http"
"miniflux.app/http/response/json"
"miniflux.app/model"
"miniflux.app/reader/subscription"
"miniflux.app/validator"
)
func (h *handler) getSubscriptions(w http.ResponseWriter, r *http.Request) {
subscriptionRequest, bodyErr := decodeSubscriptionDiscoveryRequest(r.Body)
if bodyErr != nil {
json.BadRequest(w, r, bodyErr)
func (h *handler) discoverSubscriptions(w http.ResponseWriter, r *http.Request) {
var subscriptionDiscoveryRequest model.SubscriptionDiscoveryRequest
if err := json_parser.NewDecoder(r.Body).Decode(&subscriptionDiscoveryRequest); err != nil {
json.BadRequest(w, r, err)
return
}
if validationErr := validator.ValidateSubscriptionDiscovery(&subscriptionDiscoveryRequest); validationErr != nil {
json.BadRequest(w, r, validationErr.Error())
return
}
subscriptions, finderErr := subscription.FindSubscriptions(
subscriptionRequest.URL,
subscriptionRequest.UserAgent,
subscriptionRequest.Username,
subscriptionRequest.Password,
subscriptionRequest.FetchViaProxy,
subscriptionDiscoveryRequest.URL,
subscriptionDiscoveryRequest.UserAgent,
subscriptionDiscoveryRequest.Username,
subscriptionDiscoveryRequest.Password,
subscriptionDiscoveryRequest.FetchViaProxy,
)
if finderErr != nil {
json.ServerError(w, r, finderErr)