Add API endpoint to import OPML file
This commit is contained in:
parent
7a1653a2e9
commit
5cacae6cf2
9 changed files with 96 additions and 23 deletions
2
Gopkg.lock
generated
2
Gopkg.lock
generated
|
@ -45,7 +45,7 @@
|
|||
branch = "master"
|
||||
name = "github.com/miniflux/miniflux-go"
|
||||
packages = ["."]
|
||||
revision = "887ba3b062946784f0e64edb1734f435beb204f9"
|
||||
revision = "7939463a4e1a1c5392d026d8d28bf7732459abd7"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/tdewolff/minify"
|
||||
|
|
13
api/feed.go
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
api/opml.go
Normal file
39
api/opml.go
Normal file
|
@ -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"})
|
||||
}
|
|
@ -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")
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
6
vendor/github.com/miniflux/miniflux-go/README.md
generated
vendored
6
vendor/github.com/miniflux/miniflux-go/README.md
generated
vendored
|
@ -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
vendor/github.com/miniflux/miniflux-go/client.go
generated
vendored
7
vendor/github.com/miniflux/miniflux-go/client.go
generated
vendored
|
@ -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))
|
||||
|
|
18
vendor/github.com/miniflux/miniflux-go/request.go
generated
vendored
18
vendor/github.com/miniflux/miniflux-go/request.go
generated
vendored
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue