فهرست منبع

Add API endpoint to import OPML file

Frédéric Guillot 8 سال پیش
والد
کامیت
5cacae6cf2

+ 1 - 1
Gopkg.lock

@@ -45,7 +45,7 @@
   branch = "master"
   name = "github.com/miniflux/miniflux-go"
   packages = ["."]
-  revision = "887ba3b062946784f0e64edb1734f435beb204f9"
+  revision = "7939463a4e1a1c5392d026d8d28bf7732459abd7"
 
 [[projects]]
   name = "github.com/tdewolff/minify"

+ 0 - 13
api/feed.go

@@ -11,8 +11,6 @@ import (
 	"github.com/miniflux/miniflux/http/context"
 	"github.com/miniflux/miniflux/http/request"
 	"github.com/miniflux/miniflux/http/response/json"
-	"github.com/miniflux/miniflux/http/response/xml"
-	"github.com/miniflux/miniflux/reader/opml"
 )
 
 // CreateFeed is the API handler to create a new feed.
@@ -143,17 +141,6 @@ func (c *Controller) GetFeeds(w http.ResponseWriter, r *http.Request) {
 	json.OK(w, feeds)
 }
 
-// Export is the API handler that incoves an OPML export.
-func (c *Controller) Export(w http.ResponseWriter, r *http.Request) {
-	opmlHandler := opml.NewHandler(c.store)
-	opml, err := opmlHandler.Export(context.New(r).UserID())
-	if err != nil {
-		json.ServerError(w, errors.New("unable to export feeds to OPML"))
-	}
-
-	xml.OK(w, opml)
-}
-
 // GetFeed is the API handler to get a feed.
 func (c *Controller) GetFeed(w http.ResponseWriter, r *http.Request) {
 	feedID, err := request.IntParam(r, "feedID")

+ 39 - 0
api/opml.go

@@ -0,0 +1,39 @@
+// 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 (
+	"net/http"
+
+	"github.com/miniflux/miniflux/http/context"
+	"github.com/miniflux/miniflux/http/response/json"
+	"github.com/miniflux/miniflux/http/response/xml"
+	"github.com/miniflux/miniflux/reader/opml"
+)
+
+// Export is the API handler that export feeds to OPML.
+func (c *Controller) Export(w http.ResponseWriter, r *http.Request) {
+	opmlHandler := opml.NewHandler(c.store)
+	opml, err := opmlHandler.Export(context.New(r).UserID())
+	if err != nil {
+		json.ServerError(w, err)
+		return
+	}
+
+	xml.OK(w, opml)
+}
+
+// Import is the API handler that import an OPML file.
+func (c *Controller) Import(w http.ResponseWriter, r *http.Request) {
+	opmlHandler := opml.NewHandler(c.store)
+	err := opmlHandler.Import(context.New(r).UserID(), r.Body)
+	defer r.Body.Close()
+	if err != nil {
+		json.ServerError(w, err)
+		return
+	}
+
+	json.Created(w, map[string]string{"message": "Feeds imported successfully"})
+}

+ 1 - 0
daemon/routes.go

@@ -71,6 +71,7 @@ func routes(cfg *config.Config, store *storage.Storage, feedHandler *feed.Handle
 	apiRouter.HandleFunc("/feeds/{feedID}", apiController.RemoveFeed).Methods("DELETE")
 	apiRouter.HandleFunc("/feeds/{feedID}/icon", apiController.FeedIcon).Methods("GET")
 	apiRouter.HandleFunc("/export", apiController.Export).Methods("GET")
+	apiRouter.HandleFunc("/import", apiController.Import).Methods("POST")
 	apiRouter.HandleFunc("/feeds/{feedID}/entries", apiController.GetFeedEntries).Methods("GET")
 	apiRouter.HandleFunc("/feeds/{feedID}/entries/{entryID}", apiController.GetFeedEntry).Methods("GET")
 	apiRouter.HandleFunc("/entries", apiController.GetEntries).Methods("GET")

+ 28 - 0
integration_test.go

@@ -7,6 +7,8 @@
 package main
 
 import (
+	"bytes"
+	"io/ioutil"
 	"math/rand"
 	"strconv"
 	"strings"
@@ -653,6 +655,32 @@ func TestExport(t *testing.T) {
 	}
 }
 
+func TestImport(t *testing.T) {
+	username := getRandomUsername()
+	client := miniflux.NewClient(testBaseURL, testAdminUsername, testAdminPassword)
+	_, err := client.CreateUser(username, testStandardPassword, false)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	client = miniflux.NewClient(testBaseURL, username, testStandardPassword)
+
+	data := `<?xml version="1.0" encoding="UTF-8"?>
+    <opml version="2.0">
+        <body>
+            <outline text="Test Category">
+				<outline title="Test" text="Test" xmlUrl="` + testFeedURL + `" htmlUrl="` + testWebsiteURL + `"></outline>
+			</outline>
+		</body>
+	</opml>`
+
+	b := bytes.NewReader([]byte(data))
+	err = client.Import(ioutil.NopCloser(b))
+	if err != nil {
+		t.Fatal(err)
+	}
+}
+
 func TestUpdateFeed(t *testing.T) {
 	username := getRandomUsername()
 	client := miniflux.NewClient(testBaseURL, testAdminUsername, testAdminPassword)

+ 2 - 3
reader/opml/handler.go

@@ -23,8 +23,7 @@ type Handler struct {
 func (h *Handler) Export(userID int64) (string, error) {
 	feeds, err := h.store.Feeds(userID)
 	if err != nil {
-		logger.Error("[OPML:Export] %v", err)
-		return "", errors.New("unable to fetch feeds")
+		return "", err
 	}
 
 	var subscriptions SubcriptionList
@@ -74,7 +73,7 @@ func (h *Handler) Import(userID int64, data io.Reader) error {
 					err := h.store.CreateCategory(category)
 					if err != nil {
 						logger.Error("[OPML:Import] %v", err)
-						return fmt.Errorf(`unable to create this category: "%s"`, subscription.CategoryName)
+						return fmt.Errorf(`unable to create this category: %q`, subscription.CategoryName)
 					}
 				}
 			}

+ 3 - 3
vendor/github.com/miniflux/miniflux-go/README.md

@@ -26,7 +26,7 @@ package main
 
 import (
 	"fmt"
-
+	"io/ioutil"
 	"github.com/miniflux/miniflux-go"
 )
 
@@ -41,7 +41,7 @@ func main() {
     }
     fmt.Println(feeds)
 
-    // Backup to opml file.
+    // Backup your feeds to an OPML file.
     opml, err := client.Export()
     if err != nil {
         fmt.Println(err)
@@ -53,8 +53,8 @@ func main() {
         fmt.Println(err)
         return
     }
-    fmt.Println("backup done!")
 
+    fmt.Println("backup done!")
 }
 ```
 

+ 7 - 0
vendor/github.com/miniflux/miniflux-go/client.go

@@ -7,6 +7,7 @@ package miniflux
 import (
 	"encoding/json"
 	"fmt"
+	"io"
 	"io/ioutil"
 	"net/url"
 	"strconv"
@@ -230,6 +231,12 @@ func (c *Client) Export() ([]byte, error) {
 	return opml, nil
 }
 
+// Import imports an OPML file.
+func (c *Client) Import(f io.ReadCloser) error {
+	_, err := c.request.PostFile("/v1/import", f)
+	return err
+}
+
 // Feed gets a feed.
 func (c *Client) Feed(feedID int64) (*Feed, error) {
 	body, err := c.request.Get(fmt.Sprintf("/v1/feeds/%d", feedID))

+ 15 - 3
vendor/github.com/miniflux/miniflux-go/request.go

@@ -26,6 +26,7 @@ var (
 	errNotAuthorized = errors.New("miniflux: unauthorized (bad credentials)")
 	errForbidden     = errors.New("miniflux: access forbidden")
 	errServerError   = errors.New("miniflux: internal server error")
+	errNotFound      = errors.New("miniflux: resource not found")
 )
 
 type errorResponse struct {
@@ -46,6 +47,10 @@ func (r *request) Post(path string, data interface{}) (io.ReadCloser, error) {
 	return r.execute(http.MethodPost, path, data)
 }
 
+func (r *request) PostFile(path string, f io.ReadCloser) (io.ReadCloser, error) {
+	return r.execute(http.MethodPost, path, f)
+}
+
 func (r *request) Put(path string, data interface{}) (io.ReadCloser, error) {
 	return r.execute(http.MethodPut, path, data)
 }
@@ -72,7 +77,12 @@ func (r *request) execute(method, path string, data interface{}) (io.ReadCloser,
 	request.SetBasicAuth(r.username, r.password)
 
 	if data != nil {
-		request.Body = ioutil.NopCloser(bytes.NewBuffer(r.toJSON(data)))
+		switch data.(type) {
+		case io.ReadCloser:
+			request.Body = data.(io.ReadCloser)
+		default:
+			request.Body = ioutil.NopCloser(bytes.NewBuffer(r.toJSON(data)))
+		}
 	}
 
 	client := r.buildClient()
@@ -88,6 +98,8 @@ func (r *request) execute(method, path string, data interface{}) (io.ReadCloser,
 		return nil, errForbidden
 	case http.StatusInternalServerError:
 		return nil, errServerError
+	case http.StatusNotFound:
+		return nil, errNotFound
 	case http.StatusBadRequest:
 		defer response.Body.Close()
 
@@ -100,8 +112,8 @@ func (r *request) execute(method, path string, data interface{}) (io.ReadCloser,
 		return nil, fmt.Errorf("miniflux: bad request (%s)", resp.ErrorMessage)
 	}
 
-	if response.StatusCode >= 400 {
-		return nil, fmt.Errorf("miniflux: server error (statusCode=%d)", response.StatusCode)
+	if response.StatusCode > 400 {
+		return nil, fmt.Errorf("miniflux: status code=%d", response.StatusCode)
 	}
 
 	return response.Body, nil