mirror of
https://github.com/miniflux/v2.git
synced 2025-07-02 16:38:37 +00:00
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"
|
branch = "master"
|
||||||
name = "github.com/miniflux/miniflux-go"
|
name = "github.com/miniflux/miniflux-go"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
revision = "887ba3b062946784f0e64edb1734f435beb204f9"
|
revision = "7939463a4e1a1c5392d026d8d28bf7732459abd7"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/tdewolff/minify"
|
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/context"
|
||||||
"github.com/miniflux/miniflux/http/request"
|
"github.com/miniflux/miniflux/http/request"
|
||||||
"github.com/miniflux/miniflux/http/response/json"
|
"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.
|
// 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)
|
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.
|
// GetFeed is the API handler to get a feed.
|
||||||
func (c *Controller) GetFeed(w http.ResponseWriter, r *http.Request) {
|
func (c *Controller) GetFeed(w http.ResponseWriter, r *http.Request) {
|
||||||
feedID, err := request.IntParam(r, "feedID")
|
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}", apiController.RemoveFeed).Methods("DELETE")
|
||||||
apiRouter.HandleFunc("/feeds/{feedID}/icon", apiController.FeedIcon).Methods("GET")
|
apiRouter.HandleFunc("/feeds/{feedID}/icon", apiController.FeedIcon).Methods("GET")
|
||||||
apiRouter.HandleFunc("/export", apiController.Export).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", apiController.GetFeedEntries).Methods("GET")
|
||||||
apiRouter.HandleFunc("/feeds/{feedID}/entries/{entryID}", apiController.GetFeedEntry).Methods("GET")
|
apiRouter.HandleFunc("/feeds/{feedID}/entries/{entryID}", apiController.GetFeedEntry).Methods("GET")
|
||||||
apiRouter.HandleFunc("/entries", apiController.GetEntries).Methods("GET")
|
apiRouter.HandleFunc("/entries", apiController.GetEntries).Methods("GET")
|
||||||
|
|
|
@ -7,6 +7,8 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io/ioutil"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"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) {
|
func TestUpdateFeed(t *testing.T) {
|
||||||
username := getRandomUsername()
|
username := getRandomUsername()
|
||||||
client := miniflux.NewClient(testBaseURL, testAdminUsername, testAdminPassword)
|
client := miniflux.NewClient(testBaseURL, testAdminUsername, testAdminPassword)
|
||||||
|
|
|
@ -23,8 +23,7 @@ type Handler struct {
|
||||||
func (h *Handler) Export(userID int64) (string, error) {
|
func (h *Handler) Export(userID int64) (string, error) {
|
||||||
feeds, err := h.store.Feeds(userID)
|
feeds, err := h.store.Feeds(userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("[OPML:Export] %v", err)
|
return "", err
|
||||||
return "", errors.New("unable to fetch feeds")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var subscriptions SubcriptionList
|
var subscriptions SubcriptionList
|
||||||
|
@ -74,7 +73,7 @@ func (h *Handler) Import(userID int64, data io.Reader) error {
|
||||||
err := h.store.CreateCategory(category)
|
err := h.store.CreateCategory(category)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("[OPML:Import] %v", err)
|
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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"github.com/miniflux/miniflux-go"
|
"github.com/miniflux/miniflux-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ func main() {
|
||||||
}
|
}
|
||||||
fmt.Println(feeds)
|
fmt.Println(feeds)
|
||||||
|
|
||||||
// Backup to opml file.
|
// Backup your feeds to an OPML file.
|
||||||
opml, err := client.Export()
|
opml, err := client.Export()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
|
@ -53,8 +53,8 @@ func main() {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
return
|
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 (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -230,6 +231,12 @@ func (c *Client) Export() ([]byte, error) {
|
||||||
return opml, nil
|
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.
|
// Feed gets a feed.
|
||||||
func (c *Client) Feed(feedID int64) (*Feed, error) {
|
func (c *Client) Feed(feedID int64) (*Feed, error) {
|
||||||
body, err := c.request.Get(fmt.Sprintf("/v1/feeds/%d", feedID))
|
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)")
|
errNotAuthorized = errors.New("miniflux: unauthorized (bad credentials)")
|
||||||
errForbidden = errors.New("miniflux: access forbidden")
|
errForbidden = errors.New("miniflux: access forbidden")
|
||||||
errServerError = errors.New("miniflux: internal server error")
|
errServerError = errors.New("miniflux: internal server error")
|
||||||
|
errNotFound = errors.New("miniflux: resource not found")
|
||||||
)
|
)
|
||||||
|
|
||||||
type errorResponse struct {
|
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)
|
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) {
|
func (r *request) Put(path string, data interface{}) (io.ReadCloser, error) {
|
||||||
return r.execute(http.MethodPut, path, data)
|
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)
|
request.SetBasicAuth(r.username, r.password)
|
||||||
|
|
||||||
if data != nil {
|
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()
|
client := r.buildClient()
|
||||||
|
@ -88,6 +98,8 @@ func (r *request) execute(method, path string, data interface{}) (io.ReadCloser,
|
||||||
return nil, errForbidden
|
return nil, errForbidden
|
||||||
case http.StatusInternalServerError:
|
case http.StatusInternalServerError:
|
||||||
return nil, errServerError
|
return nil, errServerError
|
||||||
|
case http.StatusNotFound:
|
||||||
|
return nil, errNotFound
|
||||||
case http.StatusBadRequest:
|
case http.StatusBadRequest:
|
||||||
defer response.Body.Close()
|
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)
|
return nil, fmt.Errorf("miniflux: bad request (%s)", resp.ErrorMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
if response.StatusCode >= 400 {
|
if response.StatusCode > 400 {
|
||||||
return nil, fmt.Errorf("miniflux: server error (statusCode=%d)", response.StatusCode)
|
return nil, fmt.Errorf("miniflux: status code=%d", response.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.Body, nil
|
return response.Body, nil
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue