Add first integration test
This commit is contained in:
parent
71bf7e4358
commit
142e8b3e0c
14 changed files with 891 additions and 12 deletions
|
@ -1,5 +1,9 @@
|
||||||
notifications:
|
notifications:
|
||||||
email: false
|
email: false
|
||||||
|
services:
|
||||||
|
- postgresql
|
||||||
|
addons:
|
||||||
|
postgresql: "9.4"
|
||||||
language: go
|
language: go
|
||||||
go:
|
go:
|
||||||
- 1.9
|
- 1.9
|
||||||
|
@ -7,4 +11,5 @@ before_install:
|
||||||
- npm install -g jshint
|
- npm install -g jshint
|
||||||
script:
|
script:
|
||||||
- jshint server/static/js/app.js
|
- jshint server/static/js/app.js
|
||||||
- go test -cover -race ./...
|
- make test
|
||||||
|
- make integration-test
|
||||||
|
|
8
Gopkg.lock
generated
8
Gopkg.lock
generated
|
@ -37,6 +37,12 @@
|
||||||
packages = [".","hstore","oid"]
|
packages = [".","hstore","oid"]
|
||||||
revision = "8c6ee72f3e6bcb1542298dd5f76cb74af9742cec"
|
revision = "8c6ee72f3e6bcb1542298dd5f76cb74af9742cec"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/miniflux/miniflux-go"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "a2caa9187ebe4378f36c8e680825586a890154c9"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/tdewolff/minify"
|
name = "github.com/tdewolff/minify"
|
||||||
packages = [".","css","js"]
|
packages = [".","css","js"]
|
||||||
|
@ -94,6 +100,6 @@
|
||||||
[solve-meta]
|
[solve-meta]
|
||||||
analyzer-name = "dep"
|
analyzer-name = "dep"
|
||||||
analyzer-version = 1
|
analyzer-version = 1
|
||||||
inputs-digest = "62e971001374c00b358ba709b9694393eec4a1a84d94a946b21251fa81d98af7"
|
inputs-digest = "ade513f4a86a1f49ce3508aa55f40cf2b1f172d6f3679649e48de67a82715431"
|
||||||
solver-name = "gps-cdcl"
|
solver-name = "gps-cdcl"
|
||||||
solver-version = 1
|
solver-version = 1
|
||||||
|
|
19
Makefile
19
Makefile
|
@ -1,8 +1,9 @@
|
||||||
APP = miniflux
|
APP = miniflux
|
||||||
VERSION = $(shell git rev-parse --short HEAD)
|
VERSION = $(shell git rev-parse --short HEAD)
|
||||||
BUILD_DATE = `date +%FT%T%z`
|
BUILD_DATE = `date +%FT%T%z`
|
||||||
|
DB_URL = postgres://postgres:postgres@localhost/miniflux_test?sslmode=disable
|
||||||
|
|
||||||
.PHONY: build-linux build-darwin build run clean test
|
.PHONY: build-linux build-darwin build run clean test integration-test clean-integration-test
|
||||||
|
|
||||||
build-linux:
|
build-linux:
|
||||||
@ go generate
|
@ go generate
|
||||||
|
@ -23,3 +24,19 @@ clean:
|
||||||
|
|
||||||
test:
|
test:
|
||||||
go test -cover -race ./...
|
go test -cover -race ./...
|
||||||
|
|
||||||
|
integration-test:
|
||||||
|
psql -U postgres -c 'drop database if exists miniflux_test;'
|
||||||
|
psql -U postgres -c 'create database miniflux_test;'
|
||||||
|
DATABASE_URL=$(DB_URL) go run main.go -migrate
|
||||||
|
DATABASE_URL=$(DB_URL) ADMIN_USERNAME=admin ADMIN_PASSWORD=test123 go run main.go -create-admin
|
||||||
|
go build -o miniflux-test main.go
|
||||||
|
DATABASE_URL=$(DB_URL) ./miniflux-test >/tmp/miniflux.log 2>&1 & echo "$$!" > "/tmp/miniflux.pid"
|
||||||
|
while ! echo exit | nc localhost 8080; do sleep 1; done >/dev/null
|
||||||
|
go test -v -tags=integration || cat /tmp/miniflux.log
|
||||||
|
|
||||||
|
clean-integration-test:
|
||||||
|
@ kill -9 `cat /tmp/miniflux.pid`
|
||||||
|
@ rm -f /tmp/miniflux.pid /tmp/miniflux.log
|
||||||
|
@ rm miniflux-test
|
||||||
|
@ psql -U postgres -c 'drop database if exists miniflux_test;'
|
||||||
|
|
101
integration_test.go
Normal file
101
integration_test.go
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
// Copyright 2017 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.
|
||||||
|
|
||||||
|
// +build integration
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/miniflux/miniflux-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
testBaseURL = "http://127.0.0.1:8080"
|
||||||
|
testUsername = "admin"
|
||||||
|
testPassword = "test123"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetUsers(t *testing.T) {
|
||||||
|
client := miniflux.NewClient(testBaseURL, testUsername, testPassword)
|
||||||
|
users, err := client.Users()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(users) == 0 {
|
||||||
|
t.Fatal("The list of users is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
if users[0].ID == 0 {
|
||||||
|
t.Fatalf(`Invalid userID, got %v`, users[0].ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if users[0].Username != testUsername {
|
||||||
|
t.Fatalf(`Invalid username, got %v`, users[0].Username)
|
||||||
|
}
|
||||||
|
|
||||||
|
if users[0].Password != "" {
|
||||||
|
t.Fatalf(`Invalid password, got %v`, users[0].Password)
|
||||||
|
}
|
||||||
|
|
||||||
|
if users[0].Language != "en_US" {
|
||||||
|
t.Fatalf(`Invalid language, got %v`, users[0].Language)
|
||||||
|
}
|
||||||
|
|
||||||
|
if users[0].Theme != "default" {
|
||||||
|
t.Fatalf(`Invalid theme, got %v`, users[0].Theme)
|
||||||
|
}
|
||||||
|
|
||||||
|
if users[0].Timezone != "UTC" {
|
||||||
|
t.Fatalf(`Invalid timezone, got %v`, users[0].Timezone)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !users[0].IsAdmin {
|
||||||
|
t.Fatalf(`Invalid role, got %v`, users[0].IsAdmin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateStandardUser(t *testing.T) {
|
||||||
|
client := miniflux.NewClient(testBaseURL, testUsername, testPassword)
|
||||||
|
user, err := client.CreateUser("test", "test123", false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.ID == 0 {
|
||||||
|
t.Fatalf(`Invalid userID, got %v`, user.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.Username != "test" {
|
||||||
|
t.Fatalf(`Invalid username, got %v`, user.Username)
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.Password != "" {
|
||||||
|
t.Fatalf(`Invalid password, got %v`, user.Password)
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.Language != "en_US" {
|
||||||
|
t.Fatalf(`Invalid language, got %v`, user.Language)
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.Theme != "default" {
|
||||||
|
t.Fatalf(`Invalid theme, got %v`, user.Theme)
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.Timezone != "UTC" {
|
||||||
|
t.Fatalf(`Invalid timezone, got %v`, user.Timezone)
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.IsAdmin {
|
||||||
|
t.Fatalf(`Invalid role, got %v`, user.IsAdmin)
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.LastLoginAt != nil {
|
||||||
|
t.Fatalf(`Invalid last login date, got %v`, user.LastLoginAt)
|
||||||
|
}
|
||||||
|
}
|
12
main.go
12
main.go
|
@ -116,8 +116,16 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if *flagCreateAdmin {
|
if *flagCreateAdmin {
|
||||||
user := &model.User{IsAdmin: true}
|
user := &model.User{
|
||||||
user.Username, user.Password = askCredentials()
|
Username: os.Getenv("ADMIN_USERNAME"),
|
||||||
|
Password: os.Getenv("ADMIN_PASSWORD"),
|
||||||
|
IsAdmin: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.Username == "" || user.Password == "" {
|
||||||
|
user.Username, user.Password = askCredentials()
|
||||||
|
}
|
||||||
|
|
||||||
if err := user.ValidateUserCreation(); err != nil {
|
if err := user.ValidateUserCreation(); err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|
|
@ -14,11 +14,11 @@ type User struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
Password string `json:"password,omitempty"`
|
Password string `json:"password,omitempty"`
|
||||||
IsAdmin bool `json:"is_admin"`
|
IsAdmin bool `json:"is_admin,omitempty"`
|
||||||
Theme string `json:"theme"`
|
Theme string `json:"theme,omitempty"`
|
||||||
Language string `json:"language"`
|
Language string `json:"language,omitempty"`
|
||||||
Timezone string `json:"timezone"`
|
Timezone string `json:"timezone,omitempty"`
|
||||||
LastLoginAt *time.Time `json:"last_login_at"`
|
LastLoginAt *time.Time `json:"last_login_at,omitempty"`
|
||||||
Extra map[string]string `json:"-"`
|
Extra map[string]string `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -64,8 +64,18 @@ func (s *Storage) CreateUser(user *model.User) (err error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
query := "INSERT INTO users (username, password, is_admin, extra) VALUES ($1, $2, $3, $4) RETURNING id"
|
query := `INSERT INTO users
|
||||||
err = s.db.QueryRow(query, strings.ToLower(user.Username), password, user.IsAdmin, extra).Scan(&user.ID)
|
(username, password, is_admin, extra)
|
||||||
|
VALUES
|
||||||
|
($1, $2, $3, $4)
|
||||||
|
RETURNING id, language, theme, timezone`
|
||||||
|
|
||||||
|
err = s.db.QueryRow(query, strings.ToLower(user.Username), password, user.IsAdmin, extra).Scan(
|
||||||
|
&user.ID,
|
||||||
|
&user.Language,
|
||||||
|
&user.Theme,
|
||||||
|
&user.Timezone,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to create user: %v", err)
|
return fmt.Errorf("unable to create user: %v", err)
|
||||||
}
|
}
|
||||||
|
|
9
vendor/github.com/miniflux/miniflux-go/.travis.yml
generated
vendored
Normal file
9
vendor/github.com/miniflux/miniflux-go/.travis.yml
generated
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
notifications:
|
||||||
|
email: false
|
||||||
|
language: go
|
||||||
|
go:
|
||||||
|
- 1.9
|
||||||
|
before_install:
|
||||||
|
- go get -u github.com/golang/lint/golint
|
||||||
|
script:
|
||||||
|
- golint *.go
|
21
vendor/github.com/miniflux/miniflux-go/LICENSE
generated
vendored
Normal file
21
vendor/github.com/miniflux/miniflux-go/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2017 Frédéric Guillot
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
50
vendor/github.com/miniflux/miniflux-go/README.md
generated
vendored
Normal file
50
vendor/github.com/miniflux/miniflux-go/README.md
generated
vendored
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
Go Library for Miniflux
|
||||||
|
=======================
|
||||||
|
[![Build Status](https://travis-ci.org/miniflux/miniflux-go.svg?branch=master)](https://travis-ci.org/miniflux/miniflux-go)
|
||||||
|
[![GoDoc](https://godoc.org/github.com/miniflux/miniflux-go?status.svg)](https://godoc.org/github.com/miniflux/miniflux-go)
|
||||||
|
|
||||||
|
Client library for Miniflux REST API.
|
||||||
|
|
||||||
|
Requirements
|
||||||
|
------------
|
||||||
|
|
||||||
|
- Miniflux >= 2.0.0
|
||||||
|
- Go >= 1.9
|
||||||
|
|
||||||
|
Installation
|
||||||
|
------------
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go get -u github.com/miniflux/miniflux-go
|
||||||
|
```
|
||||||
|
|
||||||
|
Example
|
||||||
|
-------
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/miniflux/miniflux-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
client := miniflux.NewClient("https://api.example.org", "admin", "secret")
|
||||||
|
|
||||||
|
// Fetch all feeds.
|
||||||
|
feeds, err := userClient.Feeds()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println(feeds)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Credits
|
||||||
|
-------
|
||||||
|
|
||||||
|
- Author: Frédéric Guillot
|
||||||
|
- Distributed under MIT License
|
354
vendor/github.com/miniflux/miniflux-go/client.go
generated
vendored
Normal file
354
vendor/github.com/miniflux/miniflux-go/client.go
generated
vendored
Normal file
|
@ -0,0 +1,354 @@
|
||||||
|
// Copyright 2017 Frédéric Guillot. All rights reserved.
|
||||||
|
// Use of this source code is governed by the MIT license
|
||||||
|
// that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package miniflux
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Client represents a Miniflux client.
|
||||||
|
type Client struct {
|
||||||
|
request *request
|
||||||
|
}
|
||||||
|
|
||||||
|
// Users returns all users.
|
||||||
|
func (c *Client) Users() (Users, error) {
|
||||||
|
body, err := c.request.Get("/v1/users")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer body.Close()
|
||||||
|
|
||||||
|
var users Users
|
||||||
|
decoder := json.NewDecoder(body)
|
||||||
|
if err := decoder.Decode(&users); err != nil {
|
||||||
|
return nil, fmt.Errorf("miniflux: response error (%v)", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return users, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// User returns a single user.
|
||||||
|
func (c *Client) User(userID int64) (*User, error) {
|
||||||
|
body, err := c.request.Get(fmt.Sprintf("/v1/users/%d", userID))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer body.Close()
|
||||||
|
|
||||||
|
var user User
|
||||||
|
decoder := json.NewDecoder(body)
|
||||||
|
if err := decoder.Decode(&user); err != nil {
|
||||||
|
return nil, fmt.Errorf("miniflux: response error (%v)", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateUser creates a new user in the system.
|
||||||
|
func (c *Client) CreateUser(username, password string, isAdmin bool) (*User, error) {
|
||||||
|
body, err := c.request.Post("/v1/users", &User{Username: username, Password: password, IsAdmin: isAdmin})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer body.Close()
|
||||||
|
|
||||||
|
var user *User
|
||||||
|
decoder := json.NewDecoder(body)
|
||||||
|
if err := decoder.Decode(&user); err != nil {
|
||||||
|
return nil, fmt.Errorf("miniflux: response error (%v)", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateUser updates a user in the system.
|
||||||
|
func (c *Client) UpdateUser(user *User) (*User, error) {
|
||||||
|
body, err := c.request.Put(fmt.Sprintf("/v1/users/%d", user.ID), user)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer body.Close()
|
||||||
|
|
||||||
|
var u *User
|
||||||
|
decoder := json.NewDecoder(body)
|
||||||
|
if err := decoder.Decode(&u); err != nil {
|
||||||
|
return nil, fmt.Errorf("miniflux: response error (%v)", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return u, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteUser removes a user from the system.
|
||||||
|
func (c *Client) DeleteUser(userID int64) error {
|
||||||
|
body, err := c.request.Delete(fmt.Sprintf("/v1/users/%d", userID))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
body.Close()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Discover try to find subscriptions from a website.
|
||||||
|
func (c *Client) Discover(url string) (Subscriptions, error) {
|
||||||
|
body, err := c.request.Post("/v1/discover", map[string]string{"url": url})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer body.Close()
|
||||||
|
|
||||||
|
var subscriptions Subscriptions
|
||||||
|
decoder := json.NewDecoder(body)
|
||||||
|
if err := decoder.Decode(&subscriptions); err != nil {
|
||||||
|
return nil, fmt.Errorf("miniflux: response error (%v)", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return subscriptions, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Categories gets the list of categories.
|
||||||
|
func (c *Client) Categories() (Categories, error) {
|
||||||
|
body, err := c.request.Get("/v1/categories")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer body.Close()
|
||||||
|
|
||||||
|
var categories Categories
|
||||||
|
decoder := json.NewDecoder(body)
|
||||||
|
if err := decoder.Decode(&categories); err != nil {
|
||||||
|
return nil, fmt.Errorf("miniflux: response error (%v)", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return categories, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateCategory creates a new category.
|
||||||
|
func (c *Client) CreateCategory(title string) (*Category, error) {
|
||||||
|
body, err := c.request.Post("/v1/categories", map[string]interface{}{
|
||||||
|
"title": title,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer body.Close()
|
||||||
|
|
||||||
|
var category *Category
|
||||||
|
decoder := json.NewDecoder(body)
|
||||||
|
if err := decoder.Decode(&category); err != nil {
|
||||||
|
return nil, fmt.Errorf("miniflux: response error (%v)", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return category, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateCategory updates a category.
|
||||||
|
func (c *Client) UpdateCategory(categoryID int64, title string) (*Category, error) {
|
||||||
|
body, err := c.request.Put(fmt.Sprintf("/v1/categories/%d", categoryID), map[string]interface{}{
|
||||||
|
"title": title,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer body.Close()
|
||||||
|
|
||||||
|
var category *Category
|
||||||
|
decoder := json.NewDecoder(body)
|
||||||
|
if err := decoder.Decode(&category); err != nil {
|
||||||
|
return nil, fmt.Errorf("miniflux: response error (%v)", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return category, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteCategory removes a category.
|
||||||
|
func (c *Client) DeleteCategory(categoryID int64) error {
|
||||||
|
body, err := c.request.Delete(fmt.Sprintf("/v1/categories/%d", categoryID))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer body.Close()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Feeds gets all feeds.
|
||||||
|
func (c *Client) Feeds() (Feeds, error) {
|
||||||
|
body, err := c.request.Get("/v1/feeds")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer body.Close()
|
||||||
|
|
||||||
|
var feeds Feeds
|
||||||
|
decoder := json.NewDecoder(body)
|
||||||
|
if err := decoder.Decode(&feeds); err != nil {
|
||||||
|
return nil, fmt.Errorf("miniflux: response error (%v)", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return feeds, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Feed gets a new feed.
|
||||||
|
func (c *Client) Feed(feedID int64) (*Feed, error) {
|
||||||
|
body, err := c.request.Get(fmt.Sprintf("/v1/feeds/%d", feedID))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer body.Close()
|
||||||
|
|
||||||
|
var feed *Feed
|
||||||
|
decoder := json.NewDecoder(body)
|
||||||
|
if err := decoder.Decode(&feed); err != nil {
|
||||||
|
return nil, fmt.Errorf("miniflux: response error (%v)", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return feed, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateFeed creates a new feed.
|
||||||
|
func (c *Client) CreateFeed(url string, categoryID int64) (*Feed, error) {
|
||||||
|
body, err := c.request.Post("/v1/feeds", map[string]interface{}{
|
||||||
|
"feed_url": url,
|
||||||
|
"category_id": categoryID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer body.Close()
|
||||||
|
|
||||||
|
var feed *Feed
|
||||||
|
decoder := json.NewDecoder(body)
|
||||||
|
if err := decoder.Decode(&feed); err != nil {
|
||||||
|
return nil, fmt.Errorf("miniflux: response error (%v)", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return feed, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateFeed updates a feed.
|
||||||
|
func (c *Client) UpdateFeed(feed *Feed) (*Feed, error) {
|
||||||
|
body, err := c.request.Put(fmt.Sprintf("/v1/feeds/%d", feed.ID), feed)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer body.Close()
|
||||||
|
|
||||||
|
var f *Feed
|
||||||
|
decoder := json.NewDecoder(body)
|
||||||
|
if err := decoder.Decode(&f); err != nil {
|
||||||
|
return nil, fmt.Errorf("miniflux: response error (%v)", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RefreshFeed refresh a feed.
|
||||||
|
func (c *Client) RefreshFeed(feedID int64) error {
|
||||||
|
body, err := c.request.Put(fmt.Sprintf("/v1/feeds/%d/refresh", feedID), nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
body.Close()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteFeed removes a feed.
|
||||||
|
func (c *Client) DeleteFeed(feedID int64) error {
|
||||||
|
body, err := c.request.Delete(fmt.Sprintf("/v1/feeds/%d", feedID))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
body.Close()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entry gets a single feed entry.
|
||||||
|
func (c *Client) Entry(feedID, entryID int64) (*Entry, error) {
|
||||||
|
body, err := c.request.Get(fmt.Sprintf("/v1/feeds/%d/entries/%d", feedID, entryID))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer body.Close()
|
||||||
|
|
||||||
|
var entry *Entry
|
||||||
|
decoder := json.NewDecoder(body)
|
||||||
|
if err := decoder.Decode(&entry); err != nil {
|
||||||
|
return nil, fmt.Errorf("miniflux: response error (%v)", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return entry, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entries gets feed entries.
|
||||||
|
func (c *Client) Entries(feedID int64, filter *Filter) (*EntryResultSet, error) {
|
||||||
|
path := fmt.Sprintf("/v1/feeds/%d/entries", feedID)
|
||||||
|
|
||||||
|
if filter != nil {
|
||||||
|
values := url.Values{}
|
||||||
|
|
||||||
|
if filter.Status != "" {
|
||||||
|
values.Set("status", filter.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
if filter.Direction != "" {
|
||||||
|
values.Set("direction", filter.Direction)
|
||||||
|
}
|
||||||
|
|
||||||
|
if filter.Order != "" {
|
||||||
|
values.Set("order", filter.Order)
|
||||||
|
}
|
||||||
|
|
||||||
|
if filter.Limit != 0 {
|
||||||
|
values.Set("limit", strconv.Itoa(filter.Limit))
|
||||||
|
}
|
||||||
|
|
||||||
|
if filter.Offset != 0 {
|
||||||
|
values.Set("offset", strconv.Itoa(filter.Offset))
|
||||||
|
}
|
||||||
|
|
||||||
|
path = fmt.Sprintf("%s?%s", path, values.Encode())
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := c.request.Get(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer body.Close()
|
||||||
|
|
||||||
|
var result EntryResultSet
|
||||||
|
decoder := json.NewDecoder(body)
|
||||||
|
if err := decoder.Decode(&result); err != nil {
|
||||||
|
return nil, fmt.Errorf("miniflux: response error (%v)", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateEntries updates the status of a list of entries.
|
||||||
|
func (c *Client) UpdateEntries(entryIDs []int64, status string) error {
|
||||||
|
type payload struct {
|
||||||
|
EntryIDs []int64 `json:"entry_ids"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := c.request.Put("/v1/entries", &payload{EntryIDs: entryIDs, Status: status})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
body.Close()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClient returns a new Client.
|
||||||
|
func NewClient(endpoint, username, password string) *Client {
|
||||||
|
return &Client{request: &request{endpoint: endpoint, username: username, password: password}}
|
||||||
|
}
|
31
vendor/github.com/miniflux/miniflux-go/doc.go
generated
vendored
Normal file
31
vendor/github.com/miniflux/miniflux-go/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
// Copyright 2017 Frédéric Guillot. All rights reserved.
|
||||||
|
// Use of this source code is governed by the MIT license
|
||||||
|
// that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Package miniflux implements a client library for the Miniflux REST API.
|
||||||
|
|
||||||
|
Examples
|
||||||
|
|
||||||
|
This code snippet fetch the list of users.
|
||||||
|
|
||||||
|
client := miniflux.NewClient("https://api.example.org", "admin", "secret")
|
||||||
|
users, err := client.Users()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println(users, err)
|
||||||
|
|
||||||
|
This one discover subscriptions on a website.
|
||||||
|
|
||||||
|
subscriptions, err := client.Discover("https://example.org/")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println(subscriptions)
|
||||||
|
|
||||||
|
*/
|
||||||
|
package miniflux
|
131
vendor/github.com/miniflux/miniflux-go/miniflux.go
generated
vendored
Normal file
131
vendor/github.com/miniflux/miniflux-go/miniflux.go
generated
vendored
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
// Copyright 2017 Frédéric Guillot. All rights reserved.
|
||||||
|
// Use of this source code is governed by the MIT license
|
||||||
|
// that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package miniflux
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Entry statuses.
|
||||||
|
const (
|
||||||
|
EntryStatusUnread = "unread"
|
||||||
|
EntryStatusRead = "read"
|
||||||
|
EntryStatusRemoved = "removed"
|
||||||
|
)
|
||||||
|
|
||||||
|
// User represents a user in the system.
|
||||||
|
type User struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password,omitempty"`
|
||||||
|
IsAdmin bool `json:"is_admin"`
|
||||||
|
Theme string `json:"theme"`
|
||||||
|
Language string `json:"language"`
|
||||||
|
Timezone string `json:"timezone"`
|
||||||
|
LastLoginAt *time.Time `json:"last_login_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u User) String() string {
|
||||||
|
return fmt.Sprintf("#%d - %s (admin=%v)", u.ID, u.Username, u.IsAdmin)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Users represents a list of users.
|
||||||
|
type Users []User
|
||||||
|
|
||||||
|
// Category represents a category in the system.
|
||||||
|
type Category struct {
|
||||||
|
ID int64 `json:"id,omitempty"`
|
||||||
|
Title string `json:"title,omitempty"`
|
||||||
|
UserID int64 `json:"user_id,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Category) String() string {
|
||||||
|
return fmt.Sprintf("#%d %s", c.ID, c.Title)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Categories represents a list of categories.
|
||||||
|
type Categories []*Category
|
||||||
|
|
||||||
|
// Subscription represents a feed subscription.
|
||||||
|
type Subscription struct {
|
||||||
|
Title string `json:"title"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Subscription) String() string {
|
||||||
|
return fmt.Sprintf(`Title="%s", URL="%s", Type="%s"`, s.Title, s.URL, s.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subscriptions represents a list of subscriptions.
|
||||||
|
type Subscriptions []*Subscription
|
||||||
|
|
||||||
|
// Feed represents a Miniflux feed.
|
||||||
|
type Feed struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
UserID int64 `json:"user_id"`
|
||||||
|
FeedURL string `json:"feed_url"`
|
||||||
|
SiteURL string `json:"site_url"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
CheckedAt time.Time `json:"checked_at,omitempty"`
|
||||||
|
EtagHeader string `json:"etag_header,omitempty"`
|
||||||
|
LastModifiedHeader string `json:"last_modified_header,omitempty"`
|
||||||
|
ParsingErrorMsg string `json:"parsing_error_message,omitempty"`
|
||||||
|
ParsingErrorCount int `json:"parsing_error_count,omitempty"`
|
||||||
|
Category *Category `json:"category,omitempty"`
|
||||||
|
Entries Entries `json:"entries,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Feeds represents a list of feeds.
|
||||||
|
type Feeds []*Feed
|
||||||
|
|
||||||
|
// Entry represents a subscription item in the system.
|
||||||
|
type Entry struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
UserID int64 `json:"user_id"`
|
||||||
|
FeedID int64 `json:"feed_id"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Hash string `json:"hash"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
Date time.Time `json:"published_at"`
|
||||||
|
Content string `json:"content"`
|
||||||
|
Author string `json:"author"`
|
||||||
|
Enclosures Enclosures `json:"enclosures,omitempty"`
|
||||||
|
Feed *Feed `json:"feed,omitempty"`
|
||||||
|
Category *Category `json:"category,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entries represents a list of entries.
|
||||||
|
type Entries []*Entry
|
||||||
|
|
||||||
|
// Enclosure represents an attachment.
|
||||||
|
type Enclosure struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
UserID int64 `json:"user_id"`
|
||||||
|
EntryID int64 `json:"entry_id"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
MimeType string `json:"mime_type"`
|
||||||
|
Size int `json:"size"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enclosures represents a list of attachments.
|
||||||
|
type Enclosures []*Enclosure
|
||||||
|
|
||||||
|
// Filter is used to filter entries.
|
||||||
|
type Filter struct {
|
||||||
|
Status string
|
||||||
|
Offset int
|
||||||
|
Limit int
|
||||||
|
Order string
|
||||||
|
Direction string
|
||||||
|
}
|
||||||
|
|
||||||
|
// EntryResultSet represents the response when fetching entries.
|
||||||
|
type EntryResultSet struct {
|
||||||
|
Total int `json:"total"`
|
||||||
|
Entries Entries `json:"entries"`
|
||||||
|
}
|
136
vendor/github.com/miniflux/miniflux-go/request.go
generated
vendored
Normal file
136
vendor/github.com/miniflux/miniflux-go/request.go
generated
vendored
Normal file
|
@ -0,0 +1,136 @@
|
||||||
|
// Copyright 2017 Frédéric Guillot. All rights reserved.
|
||||||
|
// Use of this source code is governed by the MIT license
|
||||||
|
// that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package miniflux
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
userAgent = "Miniflux Client Library <https://github.com/miniflux/miniflux-go>"
|
||||||
|
defaultTimeout = 80
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errNotAuthorized = errors.New("miniflux: unauthorized (bad credentials)")
|
||||||
|
errForbidden = errors.New("miniflux: access forbidden")
|
||||||
|
errServerError = errors.New("miniflux: internal server error")
|
||||||
|
)
|
||||||
|
|
||||||
|
type errorResponse struct {
|
||||||
|
ErrorMessage string `json:"error_message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type request struct {
|
||||||
|
endpoint string
|
||||||
|
username string
|
||||||
|
password string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *request) Get(path string) (io.ReadCloser, error) {
|
||||||
|
return r.execute(http.MethodGet, path, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *request) Post(path string, data interface{}) (io.ReadCloser, error) {
|
||||||
|
return r.execute(http.MethodPost, path, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *request) Put(path string, data interface{}) (io.ReadCloser, error) {
|
||||||
|
return r.execute(http.MethodPut, path, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *request) Delete(path string) (io.ReadCloser, error) {
|
||||||
|
return r.execute(http.MethodDelete, path, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *request) execute(method, path string, data interface{}) (io.ReadCloser, error) {
|
||||||
|
u, err := url.Parse(r.endpoint + path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
request := &http.Request{
|
||||||
|
URL: u,
|
||||||
|
Method: method,
|
||||||
|
Header: r.buildHeaders(),
|
||||||
|
}
|
||||||
|
request.SetBasicAuth(r.username, r.password)
|
||||||
|
|
||||||
|
if data != nil {
|
||||||
|
request.Body = ioutil.NopCloser(bytes.NewBuffer(r.toJSON(data)))
|
||||||
|
}
|
||||||
|
|
||||||
|
client := r.buildClient()
|
||||||
|
response, err := client.Do(request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch response.StatusCode {
|
||||||
|
case http.StatusUnauthorized:
|
||||||
|
return nil, errNotAuthorized
|
||||||
|
case http.StatusForbidden:
|
||||||
|
return nil, errForbidden
|
||||||
|
case http.StatusInternalServerError:
|
||||||
|
return nil, errServerError
|
||||||
|
case http.StatusBadRequest:
|
||||||
|
defer response.Body.Close()
|
||||||
|
|
||||||
|
var resp errorResponse
|
||||||
|
decoder := json.NewDecoder(response.Body)
|
||||||
|
if err := decoder.Decode(&resp); err != nil {
|
||||||
|
return nil, fmt.Errorf("miniflux: bad request error (%v)", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.Body, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *request) buildClient() http.Client {
|
||||||
|
return http.Client{
|
||||||
|
Timeout: time.Duration(defaultTimeout * time.Second),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *request) buildHeaders() http.Header {
|
||||||
|
headers := make(http.Header)
|
||||||
|
headers.Add("User-Agent", userAgent)
|
||||||
|
headers.Add("Content-Type", "application/json")
|
||||||
|
headers.Add("Accept", "application/json")
|
||||||
|
return headers
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *request) toJSON(v interface{}) []byte {
|
||||||
|
b, err := json.Marshal(v)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to convert interface to JSON:", err)
|
||||||
|
return []byte("")
|
||||||
|
}
|
||||||
|
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRequest(endpoint, username, password string) *request {
|
||||||
|
return &request{
|
||||||
|
endpoint: endpoint,
|
||||||
|
username: username,
|
||||||
|
password: password,
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue