Make sure golint pass on the code base

This commit is contained in:
Frédéric Guillot 2017-11-27 21:30:04 -08:00
parent 8781648af9
commit bb8e61c7c5
59 changed files with 322 additions and 171 deletions

View file

@ -9,7 +9,9 @@ go:
- 1.9
before_install:
- npm install -g jshint
- go get -u github.com/golang/lint/golint
script:
- jshint server/static/js/app.js
- make lint
- make test
- make integration-test

View file

@ -1,9 +1,10 @@
APP = miniflux
VERSION = $(shell git rev-parse --short HEAD)
BUILD_DATE = `date +%FT%T%z`
DB_URL = postgres://postgres:postgres@localhost/miniflux_test?sslmode=disable
APP := miniflux
VERSION := $(shell git rev-parse --short HEAD)
BUILD_DATE := `date +%FT%T%z`
PKG_LIST := $(shell go list ./... | grep -v /vendor/)
DB_URL := postgres://postgres:postgres@localhost/miniflux_test?sslmode=disable
.PHONY: build-linux build-darwin build run clean test integration-test clean-integration-test
.PHONY: build-linux build-darwin build run clean test lint integration-test clean-integration-test
build-linux:
@ go generate
@ -25,6 +26,9 @@ clean:
test:
go test -cover -race ./...
lint:
@ golint -set_exit_status ${PKG_LIST}
integration-test:
psql -U postgres -c 'drop database if exists miniflux_test;'
psql -U postgres -c 'create database miniflux_test;'

View file

@ -24,8 +24,8 @@ func Load() *Translator {
return translator
}
// GetAvailableLanguages returns the list of available languages.
func GetAvailableLanguages() map[string]string {
// AvailableLanguages returns the list of available languages.
func AvailableLanguages() map[string]string {
return map[string]string{
"en_US": "English",
"fr_FR": "Français",

View file

@ -1,5 +1,5 @@
// Code generated by go generate; DO NOT EDIT.
// 2017-11-25 15:50:52.572283626 -0800 PST m=+0.030941705
// 2017-11-27 21:07:53.23444885 -0800 PST m=+0.028635078
package locale

View file

@ -6,8 +6,8 @@ package model
import "github.com/miniflux/miniflux2/errors"
// GetThemes returns the list of available themes.
func GetThemes() map[string]string {
// Themes returns the list of available themes.
func Themes() map[string]string {
return map[string]string{
"default": "Default",
"black": "Black",
@ -16,7 +16,7 @@ func GetThemes() map[string]string {
// ValidateTheme validates theme value.
func ValidateTheme(theme string) error {
for key := range GetThemes() {
for key := range Themes() {
if key == theme {
return nil
}

View file

@ -33,11 +33,7 @@ func (u User) ValidateUserCreation() error {
return err
}
if err := u.ValidatePassword(); err != nil {
return err
}
return nil
return u.ValidatePassword()
}
// ValidateUserModification validates user for modification.
@ -54,11 +50,7 @@ func (u User) ValidateUserModification() error {
return err
}
if err := ValidateTheme(u.Theme); err != nil {
return err
}
return nil
return ValidateTheme(u.Theme)
}
// ValidateUserLogin validates user credential requirements.

View file

@ -92,7 +92,7 @@ func (h *Handler) CreateFeed(userID, categoryID int64, url string) (*model.Feed,
func (h *Handler) RefreshFeed(userID, feedID int64) error {
defer helper.ExecutionTime(time.Now(), fmt.Sprintf("[Handler:RefreshFeed] feedID=%d", feedID))
originalFeed, err := h.store.GetFeedById(userID, feedID)
originalFeed, err := h.store.FeedByID(userID, feedID)
if err != nil {
return err
}

View file

@ -21,7 +21,7 @@ type Handler struct {
// Export exports user feeds to OPML.
func (h *Handler) Export(userID int64) (string, error) {
feeds, err := h.store.GetFeeds(userID)
feeds, err := h.store.Feeds(userID)
if err != nil {
log.Println(err)
return "", errors.New("unable to fetch feeds")
@ -52,13 +52,13 @@ func (h *Handler) Import(userID int64, data io.Reader) (err error) {
var category *model.Category
if subscription.CategoryName == "" {
category, err = h.store.GetFirstCategory(userID)
category, err = h.store.FirstCategory(userID)
if err != nil {
log.Println(err)
return errors.New("unable to find first category")
}
} else {
category, err = h.store.GetCategoryByTitle(userID, subscription.CategoryName)
category, err = h.store.CategoryByTitle(userID, subscription.CategoryName)
if err != nil {
log.Println(err)
return errors.New("unable to search category by title")

View file

@ -9,6 +9,7 @@ import (
"github.com/miniflux/miniflux2/reader/sanitizer"
)
// ItemContentProcessor executes a set of functions to sanitize and alter item contents.
func ItemContentProcessor(url, content string) string {
content = sanitizer.Sanitize(url, content)
return rewrite.Rewriter(url, content)

View file

@ -38,6 +38,7 @@ var rewriteRules = []func(string, string) string{
},
}
// Rewriter modify item contents with a set of rewriting rules.
func Rewriter(url, content string) string {
for _, rewriteRule := range rewriteRules {
content = rewriteRule(url, content)

View file

@ -6,6 +6,7 @@ package api
import (
"errors"
"github.com/miniflux/miniflux2/server/api/payload"
"github.com/miniflux/miniflux2/server/core"
)
@ -65,7 +66,7 @@ func (c *Controller) UpdateCategory(ctx *core.Context, request *core.Request, re
// GetCategories is the API handler to get a list of categories for a given user.
func (c *Controller) GetCategories(ctx *core.Context, request *core.Request, response *core.Response) {
categories, err := c.store.GetCategories(ctx.UserID())
categories, err := c.store.Categories(ctx.UserID())
if err != nil {
response.JSON().ServerError(errors.New("Unable to fetch categories"))
return

View file

@ -66,7 +66,7 @@ func (c *Controller) UpdateFeed(ctx *core.Context, request *core.Request, respon
return
}
originalFeed, err := c.store.GetFeedById(userID, feedID)
originalFeed, err := c.store.FeedByID(userID, feedID)
if err != nil {
response.JSON().NotFound(errors.New("Unable to find this feed"))
return
@ -88,7 +88,7 @@ func (c *Controller) UpdateFeed(ctx *core.Context, request *core.Request, respon
// GetFeeds is the API handler that get all feeds that belongs to the given user.
func (c *Controller) GetFeeds(ctx *core.Context, request *core.Request, response *core.Response) {
feeds, err := c.store.GetFeeds(ctx.UserID())
feeds, err := c.store.Feeds(ctx.UserID())
if err != nil {
response.JSON().ServerError(errors.New("Unable to fetch feeds from the database"))
return
@ -106,7 +106,7 @@ func (c *Controller) GetFeed(ctx *core.Context, request *core.Request, response
return
}
feed, err := c.store.GetFeedById(userID, feedID)
feed, err := c.store.FeedByID(userID, feedID)
if err != nil {
response.JSON().ServerError(errors.New("Unable to fetch this feed"))
return

View file

@ -6,6 +6,7 @@ package api
import (
"errors"
"github.com/miniflux/miniflux2/server/api/payload"
"github.com/miniflux/miniflux2/server/core"
)
@ -67,7 +68,7 @@ func (c *Controller) UpdateUser(ctx *core.Context, request *core.Request, respon
return
}
originalUser, err := c.store.GetUserById(userID)
originalUser, err := c.store.UserByID(userID)
if err != nil {
response.JSON().BadRequest(errors.New("Unable to fetch this user from the database"))
return
@ -94,7 +95,7 @@ func (c *Controller) GetUsers(ctx *core.Context, request *core.Request, response
return
}
users, err := c.store.GetUsers()
users, err := c.store.Users()
if err != nil {
response.JSON().ServerError(errors.New("Unable to fetch the list of users"))
return
@ -116,7 +117,7 @@ func (c *Controller) GetUser(ctx *core.Context, request *core.Request, response
return
}
user, err := c.store.GetUserById(userID)
user, err := c.store.UserByID(userID)
if err != nil {
response.JSON().BadRequest(errors.New("Unable to fetch this user from the database"))
return
@ -143,7 +144,7 @@ func (c *Controller) RemoveUser(ctx *core.Context, request *core.Request, respon
return
}
user, err := c.store.GetUserById(userID)
user, err := c.store.UserByID(userID)
if err != nil {
response.JSON().ServerError(errors.New("Unable to fetch this user from the database"))
return

View file

@ -12,11 +12,13 @@ import (
"github.com/miniflux/miniflux2/model"
)
// EntriesResponse represents the response sent when fetching entries.
type EntriesResponse struct {
Total int `json:"total"`
Entries model.Entries `json:"entries"`
}
// DecodeUserPayload unserialize JSON user object.
func DecodeUserPayload(data io.Reader) (*model.User, error) {
var user model.User
@ -28,6 +30,7 @@ func DecodeUserPayload(data io.Reader) (*model.User, error) {
return &user, nil
}
// DecodeURLPayload unserialize JSON subscription object.
func DecodeURLPayload(data io.Reader) (string, error) {
type payload struct {
URL string `json:"url"`
@ -42,6 +45,7 @@ func DecodeURLPayload(data io.Reader) (string, error) {
return p.URL, nil
}
// DecodeEntryStatusPayload unserialize JSON entry statuses object.
func DecodeEntryStatusPayload(data io.Reader) ([]int64, string, error) {
type payload struct {
EntryIDs []int64 `json:"entry_ids"`
@ -57,6 +61,7 @@ func DecodeEntryStatusPayload(data io.Reader) ([]int64, string, error) {
return p.EntryIDs, p.Status, nil
}
// DecodeFeedCreationPayload unserialize JSON feed creation object.
func DecodeFeedCreationPayload(data io.Reader) (string, int64, error) {
type payload struct {
FeedURL string `json:"feed_url"`
@ -72,6 +77,7 @@ func DecodeFeedCreationPayload(data io.Reader) (string, int64, error) {
return p.FeedURL, p.CategoryID, nil
}
// DecodeFeedModificationPayload unserialize JSON feed object.
func DecodeFeedModificationPayload(data io.Reader) (*model.Feed, error) {
var feed model.Feed
@ -83,6 +89,7 @@ func DecodeFeedModificationPayload(data io.Reader) (*model.Feed, error) {
return &feed, nil
}
// DecodeCategoryPayload unserialize JSON category object.
func DecodeCategoryPayload(data io.Reader) (*model.Category, error) {
var category model.Category

View file

@ -9,6 +9,7 @@ import (
"net/http"
"github.com/miniflux/miniflux2/model"
"github.com/miniflux/miniflux2/server/middleware"
"github.com/miniflux/miniflux2/server/route"
"github.com/miniflux/miniflux2/storage"
@ -26,7 +27,7 @@ type Context struct {
// IsAdminUser checks if the logged user is administrator.
func (c *Context) IsAdminUser() bool {
if v := c.request.Context().Value("IsAdminUser"); v != nil {
if v := c.request.Context().Value(middleware.IsAdminUserContextKey); v != nil {
return v.(bool)
}
return false
@ -34,7 +35,7 @@ func (c *Context) IsAdminUser() bool {
// UserTimezone returns the timezone used by the logged user.
func (c *Context) UserTimezone() string {
if v := c.request.Context().Value("UserTimezone"); v != nil {
if v := c.request.Context().Value(middleware.UserTimezoneContextKey); v != nil {
return v.(string)
}
return "UTC"
@ -42,7 +43,7 @@ func (c *Context) UserTimezone() string {
// IsAuthenticated returns a boolean if the user is authenticated.
func (c *Context) IsAuthenticated() bool {
if v := c.request.Context().Value("IsAuthenticated"); v != nil {
if v := c.request.Context().Value(middleware.IsAuthenticatedContextKey); v != nil {
return v.(bool)
}
return false
@ -50,7 +51,7 @@ func (c *Context) IsAuthenticated() bool {
// UserID returns the UserID of the logged user.
func (c *Context) UserID() int64 {
if v := c.request.Context().Value("UserId"); v != nil {
if v := c.request.Context().Value(middleware.UserIDContextKey); v != nil {
return v.(int64)
}
return 0
@ -60,7 +61,7 @@ func (c *Context) UserID() int64 {
func (c *Context) LoggedUser() *model.User {
if c.user == nil {
var err error
c.user, err = c.store.GetUserById(c.UserID())
c.user, err = c.store.UserByID(c.UserID())
if err != nil {
log.Fatalln(err)
}
@ -81,7 +82,7 @@ func (c *Context) UserLanguage() string {
// CsrfToken returns the current CSRF token.
func (c *Context) CsrfToken() string {
if v := c.request.Context().Value("CsrfToken"); v != nil {
if v := c.request.Context().Value(middleware.CsrfContextKey); v != nil {
return v.(string)
}
@ -91,7 +92,7 @@ func (c *Context) CsrfToken() string {
// Route returns the path for the given arguments.
func (c *Context) Route(name string, args ...interface{}) string {
return route.GetRoute(c.router, name, args...)
return route.Path(c.router, name, args...)
}
// NewContext creates a new Context.

View file

@ -27,7 +27,7 @@ type Handler struct {
translator *locale.Translator
template *template.Engine
router *mux.Router
middleware *middleware.MiddlewareChain
middleware *middleware.Chain
}
// Use is a wrapper around an HTTP handler.
@ -51,7 +51,7 @@ func (h *Handler) Use(f HandlerFunc) http.Handler {
}
// NewHandler returns a new Handler.
func NewHandler(store *storage.Storage, router *mux.Router, template *template.Engine, translator *locale.Translator, middleware *middleware.MiddlewareChain) *Handler {
func NewHandler(store *storage.Storage, router *mux.Router, template *template.Engine, translator *locale.Translator, middleware *middleware.Chain) *Handler {
return &Handler{
store: store,
translator: translator,

View file

@ -6,15 +6,18 @@ package middleware
import (
"context"
"github.com/miniflux/miniflux2/storage"
"log"
"net/http"
"github.com/miniflux/miniflux2/storage"
)
// BasicAuthMiddleware is the middleware for HTTP Basic authentication.
type BasicAuthMiddleware struct {
store *storage.Storage
}
// Handler executes the middleware.
func (b *BasicAuthMiddleware) Handler(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
@ -35,7 +38,7 @@ func (b *BasicAuthMiddleware) Handler(next http.Handler) http.Handler {
return
}
user, err := b.store.GetUserByUsername(username)
user, err := b.store.UserByUsername(username)
if err != nil || user == nil {
log.Println("[Middleware:BasicAuth] User not found:", username)
w.WriteHeader(http.StatusUnauthorized)
@ -47,15 +50,16 @@ func (b *BasicAuthMiddleware) Handler(next http.Handler) http.Handler {
b.store.SetLastLogin(user.ID)
ctx := r.Context()
ctx = context.WithValue(ctx, "UserId", user.ID)
ctx = context.WithValue(ctx, "UserTimezone", user.Timezone)
ctx = context.WithValue(ctx, "IsAdminUser", user.IsAdmin)
ctx = context.WithValue(ctx, "IsAuthenticated", true)
ctx = context.WithValue(ctx, UserIDContextKey, user.ID)
ctx = context.WithValue(ctx, UserTimezoneContextKey, user.Timezone)
ctx = context.WithValue(ctx, IsAdminUserContextKey, user.IsAdmin)
ctx = context.WithValue(ctx, IsAuthenticatedContextKey, true)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// NewBasicAuthMiddleware returns a new BasicAuthMiddleware.
func NewBasicAuthMiddleware(s *storage.Storage) *BasicAuthMiddleware {
return &BasicAuthMiddleware{store: s}
}

View file

@ -0,0 +1,26 @@
// 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.
package middleware
type contextKey struct {
name string
}
var (
// UserIDContextKey is the context key used to store the user ID.
UserIDContextKey = &contextKey{"UserID"}
// UserTimezoneContextKey is the context key used to store the user timezone.
UserTimezoneContextKey = &contextKey{"UserTimezone"}
// IsAdminUserContextKey is the context key used to store the user role.
IsAdminUserContextKey = &contextKey{"IsAdminUser"}
// IsAuthenticatedContextKey is the context key used to store the authentication flag.
IsAuthenticatedContextKey = &contextKey{"IsAuthenticated"}
// CsrfContextKey is the context key used to store CSRF token.
CsrfContextKey = &contextKey{"CSRF"}
)

View file

@ -6,11 +6,13 @@ package middleware
import (
"context"
"github.com/miniflux/miniflux2/helper"
"log"
"net/http"
"github.com/miniflux/miniflux2/helper"
)
// Csrf is a middleware that handle CSRF tokens.
func Csrf(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var csrfToken string
@ -32,7 +34,7 @@ func Csrf(next http.Handler) http.Handler {
}
ctx := r.Context()
ctx = context.WithValue(ctx, "CsrfToken", csrfToken)
ctx = context.WithValue(ctx, CsrfContextKey, csrfToken)
w.Header().Add("Vary", "Cookie")
isTokenValid := csrfToken == r.FormValue("csrf") || csrfToken == r.Header.Get("X-Csrf-Token")

View file

@ -8,13 +8,16 @@ import (
"net/http"
)
// Middleware represents a HTTP middleware.
type Middleware func(http.Handler) http.Handler
type MiddlewareChain struct {
// Chain handles a list of middlewares.
type Chain struct {
middlewares []Middleware
}
func (m *MiddlewareChain) Wrap(h http.Handler) http.Handler {
// Wrap adds a HTTP handler into the chain.
func (m *Chain) Wrap(h http.Handler) http.Handler {
for i := range m.middlewares {
h = m.middlewares[len(m.middlewares)-1-i](h)
}
@ -22,10 +25,12 @@ func (m *MiddlewareChain) Wrap(h http.Handler) http.Handler {
return h
}
func (m *MiddlewareChain) WrapFunc(fn http.HandlerFunc) http.Handler {
// WrapFunc adds a HTTP handler function into the chain.
func (m *Chain) WrapFunc(fn http.HandlerFunc) http.Handler {
return m.Wrap(fn)
}
func NewMiddlewareChain(middlewares ...Middleware) *MiddlewareChain {
return &MiddlewareChain{append(([]Middleware)(nil), middlewares...)}
// NewChain returns a new Chain.
func NewChain(middlewares ...Middleware) *Chain {
return &Chain{append(([]Middleware)(nil), middlewares...)}
}

View file

@ -16,11 +16,13 @@ import (
"github.com/gorilla/mux"
)
// SessionMiddleware represents a session middleware.
type SessionMiddleware struct {
store *storage.Storage
router *mux.Router
}
// Handler execute the middleware.
func (s *SessionMiddleware) Handler(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
session := s.getSessionFromCookie(r)
@ -30,13 +32,13 @@ func (s *SessionMiddleware) Handler(next http.Handler) http.Handler {
if s.isPublicRoute(r) {
next.ServeHTTP(w, r)
} else {
http.Redirect(w, r, route.GetRoute(s.router, "login"), http.StatusFound)
http.Redirect(w, r, route.Path(s.router, "login"), http.StatusFound)
}
} else {
log.Println("[Middleware:Session]", session)
ctx := r.Context()
ctx = context.WithValue(ctx, "UserId", session.UserID)
ctx = context.WithValue(ctx, "IsAuthenticated", true)
ctx = context.WithValue(ctx, UserIDContextKey, session.UserID)
ctx = context.WithValue(ctx, IsAuthenticatedContextKey, true)
next.ServeHTTP(w, r.WithContext(ctx))
}
@ -59,7 +61,7 @@ func (s *SessionMiddleware) getSessionFromCookie(r *http.Request) *model.Session
return nil
}
session, err := s.store.GetSessionByToken(sessionCookie.Value)
session, err := s.store.SessionByToken(sessionCookie.Value)
if err != nil {
log.Println(err)
return nil
@ -68,6 +70,7 @@ func (s *SessionMiddleware) getSessionFromCookie(r *http.Request) *model.Session
return session
}
// NewSessionMiddleware returns a new SessionMiddleware.
func NewSessionMiddleware(s *storage.Storage, r *mux.Router) *SessionMiddleware {
return &SessionMiddleware{store: s, router: r}
}

View file

@ -11,7 +11,8 @@ import (
"github.com/gorilla/mux"
)
func GetRoute(router *mux.Router, name string, args ...interface{}) string {
// Path returns the defined route based on given arguments.
func Path(router *mux.Router, name string, args ...interface{}) string {
route := router.Get(name)
if route == nil {
log.Fatalln("Route not found:", name)

View file

@ -31,11 +31,11 @@ func getRoutes(cfg *config.Config, store *storage.Storage, feedHandler *feed.Han
apiController := api_controller.NewController(store, feedHandler)
uiController := ui_controller.NewController(cfg, store, pool, feedHandler, opml.NewHandler(store))
apiHandler := core.NewHandler(store, router, templateEngine, translator, middleware.NewMiddlewareChain(
apiHandler := core.NewHandler(store, router, templateEngine, translator, middleware.NewChain(
middleware.NewBasicAuthMiddleware(store).Handler,
))
uiHandler := core.NewHandler(store, router, templateEngine, translator, middleware.NewMiddlewareChain(
uiHandler := core.NewHandler(store, router, templateEngine, translator, middleware.NewChain(
middleware.NewSessionMiddleware(store, router).Handler,
middleware.Csrf,
))

View file

@ -1,5 +1,5 @@
// Code generated by go generate; DO NOT EDIT.
// 2017-11-25 10:47:30.351686806 -0800 PST m=+0.007814912
// 2017-11-27 21:07:53.21170439 -0800 PST m=+0.005890618
package static

View file

@ -1,5 +1,5 @@
// Code generated by go generate; DO NOT EDIT.
// 2017-11-25 10:47:30.352812144 -0800 PST m=+0.008940250
// 2017-11-27 21:07:53.213299146 -0800 PST m=+0.007485374
package static

View file

@ -1,5 +1,5 @@
// Code generated by go generate; DO NOT EDIT.
// 2017-11-25 10:47:30.357950671 -0800 PST m=+0.014078777
// 2017-11-27 21:07:53.215205872 -0800 PST m=+0.009392100
package static

View file

@ -1,5 +1,5 @@
// Code generated by go generate; DO NOT EDIT.
// 2017-11-25 10:47:30.370347794 -0800 PST m=+0.026475900
// 2017-11-27 21:07:53.233262137 -0800 PST m=+0.027448365
package template

View file

@ -49,7 +49,7 @@ func (e *Engine) parseAll() {
return false
},
"route": func(name string, args ...interface{}) string {
return route.GetRoute(e.router, name, args...)
return route.Path(e.router, name, args...)
},
"noescape": func(str string) template.HTML {
return template.HTML(str)

View file

@ -1,5 +1,5 @@
// Code generated by go generate; DO NOT EDIT.
// 2017-11-25 17:05:58.40092186 -0800 PST m=+0.019242510
// 2017-11-27 21:07:53.218349526 -0800 PST m=+0.012535754
package template

View file

@ -9,6 +9,7 @@ import (
"github.com/miniflux/miniflux2/version"
)
// AboutPage shows the about page.
func (c *Controller) AboutPage(ctx *core.Context, request *core.Request, response *core.Response) {
args, err := c.getCommonTemplateArgs(ctx)
if err != nil {

View file

@ -22,7 +22,7 @@ func (c *Controller) ShowCategories(ctx *core.Context, request *core.Request, re
}
user := ctx.LoggedUser()
categories, err := c.store.GetCategoriesWithFeedCount(user.ID)
categories, err := c.store.CategoriesWithFeedCount(user.ID)
if err != nil {
response.HTML().ServerError(err)
return
@ -57,7 +57,7 @@ func (c *Controller) ShowCategoryEntries(ctx *core.Context, request *core.Reques
builder.WithDirection(model.DefaultSortingDirection)
builder.WithoutStatus(model.EntryStatusRemoved)
builder.WithOffset(offset)
builder.WithLimit(NbItemsPerPage)
builder.WithLimit(nbItemsPerPage)
entries, err := builder.GetEntries()
if err != nil {
@ -110,7 +110,7 @@ func (c *Controller) SaveCategory(ctx *core.Context, request *core.Request, resp
return
}
duplicateCategory, err := c.store.GetCategoryByTitle(user.ID, categoryForm.Title)
duplicateCategory, err := c.store.CategoryByTitle(user.ID, categoryForm.Title)
if err != nil {
response.HTML().ServerError(err)
return
@ -223,7 +223,7 @@ func (c *Controller) getCategoryFromURL(ctx *core.Context, request *core.Request
}
user := ctx.LoggedUser()
category, err := c.store.GetCategory(user.ID, categoryID)
category, err := c.store.Category(user.ID, categoryID)
if err != nil {
response.HTML().ServerError(err)
return nil, err

View file

@ -39,7 +39,7 @@ func (c *Controller) ShowFeedsPage(ctx *core.Context, request *core.Request, res
return
}
feeds, err := c.store.GetFeeds(user.ID)
feeds, err := c.store.Feeds(user.ID)
if err != nil {
response.HTML().ServerError(err)
return
@ -74,7 +74,7 @@ func (c *Controller) ShowFeedEntries(ctx *core.Context, request *core.Request, r
builder.WithOrder(model.DefaultSortingOrder)
builder.WithDirection(model.DefaultSortingDirection)
builder.WithOffset(offset)
builder.WithLimit(NbItemsPerPage)
builder.WithLimit(nbItemsPerPage)
entries, err := builder.GetEntries()
if err != nil {
@ -190,7 +190,7 @@ func (c *Controller) getFeedFromURL(request *core.Request, response *core.Respon
return nil, err
}
feed, err := c.store.GetFeedById(user.ID, feedID)
feed, err := c.store.FeedByID(user.ID, feedID)
if err != nil {
response.HTML().ServerError(err)
return nil, err
@ -210,7 +210,7 @@ func (c *Controller) getFeedFormTemplateArgs(ctx *core.Context, user *model.User
return nil, err
}
categories, err := c.store.GetCategories(user.ID)
categories, err := c.store.Categories(user.ID)
if err != nil {
return nil, err
}

View file

@ -25,7 +25,7 @@ func (c *Controller) ShowHistoryPage(ctx *core.Context, request *core.Request, r
builder.WithOrder(model.DefaultSortingOrder)
builder.WithDirection(model.DefaultSortingDirection)
builder.WithOffset(offset)
builder.WithLimit(NbItemsPerPage)
builder.WithLimit(nbItemsPerPage)
entries, err := builder.GetEntries()
if err != nil {

View file

@ -5,10 +5,12 @@
package controller
import (
"github.com/miniflux/miniflux2/server/core"
"time"
"github.com/miniflux/miniflux2/server/core"
)
// ShowIcon shows the feed icon.
func (c *Controller) ShowIcon(ctx *core.Context, request *core.Request, response *core.Response) {
iconID, err := request.IntegerParam("iconID")
if err != nil {
@ -16,7 +18,7 @@ func (c *Controller) ShowIcon(ctx *core.Context, request *core.Request, response
return
}
icon, err := c.store.GetIconByID(iconID)
icon, err := c.store.IconByID(iconID)
if err != nil {
response.HTML().ServerError(err)
return

View file

@ -82,7 +82,7 @@ func (c *Controller) OAuth2Callback(ctx *core.Context, request *core.Request, re
return
}
user, err := c.store.GetUserByExtraField(profile.Key, profile.ID)
user, err := c.store.UserByExtraField(profile.Key, profile.ID)
if err != nil {
response.HTML().ServerError(err)
return

View file

@ -10,6 +10,7 @@ import (
"github.com/miniflux/miniflux2/server/core"
)
// Export generates the OPML file.
func (c *Controller) Export(ctx *core.Context, request *core.Request, response *core.Response) {
user := ctx.LoggedUser()
opml, err := c.opmlHandler.Export(user.ID)
@ -21,6 +22,7 @@ func (c *Controller) Export(ctx *core.Context, request *core.Request, response *
response.XML().Download("feeds.opml", opml)
}
// Import shows the import form.
func (c *Controller) Import(ctx *core.Context, request *core.Request, response *core.Response) {
args, err := c.getCommonTemplateArgs(ctx)
if err != nil {
@ -33,6 +35,7 @@ func (c *Controller) Import(ctx *core.Context, request *core.Request, response *
}))
}
// UploadOPML handles OPML file importation.
func (c *Controller) UploadOPML(ctx *core.Context, request *core.Request, response *core.Response) {
file, fileHeader, err := request.File("file")
if err != nil {

View file

@ -5,10 +5,10 @@
package controller
const (
NbItemsPerPage = 100
nbItemsPerPage = 100
)
type Pagination struct {
type pagination struct {
Route string
Total int
Offset int
@ -19,25 +19,25 @@ type Pagination struct {
PrevOffset int
}
func (c *Controller) getPagination(route string, total, offset int) Pagination {
func (c *Controller) getPagination(route string, total, offset int) pagination {
nextOffset := 0
prevOffset := 0
showNext := (total - offset) > NbItemsPerPage
showNext := (total - offset) > nbItemsPerPage
showPrev := offset > 0
if showNext {
nextOffset = offset + NbItemsPerPage
nextOffset = offset + nbItemsPerPage
}
if showPrev {
prevOffset = offset - NbItemsPerPage
prevOffset = offset - nbItemsPerPage
}
return Pagination{
return pagination{
Route: route,
Total: total,
Offset: offset,
ItemsPerPage: NbItemsPerPage,
ItemsPerPage: nbItemsPerPage,
ShowNext: showNext,
NextOffset: nextOffset,
ShowPrev: showPrev,

View file

@ -5,10 +5,12 @@
package controller
import (
"github.com/miniflux/miniflux2/server/core"
"log"
"github.com/miniflux/miniflux2/server/core"
)
// ShowSessions shows the list of active sessions.
func (c *Controller) ShowSessions(ctx *core.Context, request *core.Request, response *core.Response) {
user := ctx.LoggedUser()
args, err := c.getCommonTemplateArgs(ctx)
@ -17,7 +19,7 @@ func (c *Controller) ShowSessions(ctx *core.Context, request *core.Request, resp
return
}
sessions, err := c.store.GetSessions(user.ID)
sessions, err := c.store.Sessions(user.ID)
if err != nil {
response.HTML().ServerError(err)
return
@ -31,6 +33,7 @@ func (c *Controller) ShowSessions(ctx *core.Context, request *core.Request, resp
}))
}
// RemoveSession remove a session.
func (c *Controller) RemoveSession(ctx *core.Context, request *core.Request, response *core.Response) {
user := ctx.LoggedUser()

View file

@ -5,13 +5,15 @@
package controller
import (
"log"
"github.com/miniflux/miniflux2/locale"
"github.com/miniflux/miniflux2/model"
"github.com/miniflux/miniflux2/server/core"
"github.com/miniflux/miniflux2/server/ui/form"
"log"
)
// ShowSettings shows the settings page.
func (c *Controller) ShowSettings(ctx *core.Context, request *core.Request, response *core.Response) {
user := ctx.LoggedUser()
@ -24,6 +26,7 @@ func (c *Controller) ShowSettings(ctx *core.Context, request *core.Request, resp
response.HTML().Render("settings", args)
}
// UpdateSettings update the settings.
func (c *Controller) UpdateSettings(ctx *core.Context, request *core.Request, response *core.Response) {
user := ctx.LoggedUser()
@ -81,9 +84,9 @@ func (c *Controller) getSettingsFormTemplateArgs(ctx *core.Context, user *model.
}
args["menu"] = "settings"
args["themes"] = model.GetThemes()
args["languages"] = locale.GetAvailableLanguages()
args["timezones"], err = c.store.GetTimezones()
args["themes"] = model.Themes()
args["languages"] = locale.AvailableLanguages()
args["timezones"], err = c.store.Timezones()
if err != nil {
return args, err
}

View file

@ -6,12 +6,14 @@ package controller
import (
"encoding/base64"
"github.com/miniflux/miniflux2/server/core"
"github.com/miniflux/miniflux2/server/static"
"log"
"time"
"github.com/miniflux/miniflux2/server/core"
"github.com/miniflux/miniflux2/server/static"
)
// Stylesheet renders the CSS.
func (c *Controller) Stylesheet(ctx *core.Context, request *core.Request, response *core.Response) {
stylesheet := request.StringParam("name", "white")
body := static.Stylesheets["common"]
@ -25,10 +27,12 @@ func (c *Controller) Stylesheet(ctx *core.Context, request *core.Request, respon
response.Cache("text/css", etag, []byte(body), 48*time.Hour)
}
// Javascript renders application client side code.
func (c *Controller) Javascript(ctx *core.Context, request *core.Request, response *core.Response) {
response.Cache("text/javascript", static.JavascriptChecksums["app"], []byte(static.Javascript["app"]), 48*time.Hour)
}
// Favicon renders the application favicon.
func (c *Controller) Favicon(ctx *core.Context, request *core.Request, response *core.Response) {
blob, err := base64.StdEncoding.DecodeString(static.Binaries["favicon.ico"])
if err != nil {

View file

@ -135,7 +135,7 @@ func (c *Controller) getSubscriptionFormTemplateArgs(ctx *core.Context, user *mo
return nil, err
}
categories, err := c.store.GetCategories(user.ID)
categories, err := c.store.Categories(user.ID)
if err != nil {
return nil, err
}

View file

@ -19,7 +19,7 @@ func (c *Controller) ShowUnreadPage(ctx *core.Context, request *core.Request, re
builder.WithOrder(model.DefaultSortingOrder)
builder.WithDirection(model.DefaultSortingDirection)
builder.WithOffset(offset)
builder.WithLimit(NbItemsPerPage)
builder.WithLimit(nbItemsPerPage)
entries, err := builder.GetEntries()
if err != nil {

View file

@ -6,12 +6,14 @@ package controller
import (
"errors"
"log"
"github.com/miniflux/miniflux2/model"
"github.com/miniflux/miniflux2/server/core"
"github.com/miniflux/miniflux2/server/ui/form"
"log"
)
// ShowUsers shows the list of users.
func (c *Controller) ShowUsers(ctx *core.Context, request *core.Request, response *core.Response) {
user := ctx.LoggedUser()
@ -26,7 +28,7 @@ func (c *Controller) ShowUsers(ctx *core.Context, request *core.Request, respons
return
}
users, err := c.store.GetUsers()
users, err := c.store.Users()
if err != nil {
response.HTML().ServerError(err)
return
@ -38,6 +40,7 @@ func (c *Controller) ShowUsers(ctx *core.Context, request *core.Request, respons
}))
}
// CreateUser shows the user creation form.
func (c *Controller) CreateUser(ctx *core.Context, request *core.Request, response *core.Response) {
user := ctx.LoggedUser()
@ -58,6 +61,7 @@ func (c *Controller) CreateUser(ctx *core.Context, request *core.Request, respon
}))
}
// SaveUser validate and save the new user into the database.
func (c *Controller) SaveUser(ctx *core.Context, request *core.Request, response *core.Response) {
user := ctx.LoggedUser()
@ -105,6 +109,7 @@ func (c *Controller) SaveUser(ctx *core.Context, request *core.Request, response
response.Redirect(ctx.Route("users"))
}
// EditUser shows the form to edit a user.
func (c *Controller) EditUser(ctx *core.Context, request *core.Request, response *core.Response) {
user := ctx.LoggedUser()
@ -134,6 +139,7 @@ func (c *Controller) EditUser(ctx *core.Context, request *core.Request, response
}))
}
// UpdateUser validate and update a user.
func (c *Controller) UpdateUser(ctx *core.Context, request *core.Request, response *core.Response) {
user := ctx.LoggedUser()
@ -189,6 +195,7 @@ func (c *Controller) UpdateUser(ctx *core.Context, request *core.Request, respon
response.Redirect(ctx.Route("users"))
}
// RemoveUser deletes a user from the database.
func (c *Controller) RemoveUser(ctx *core.Context, request *core.Request, response *core.Response) {
user := ctx.LoggedUser()
if !user.IsAdmin {
@ -216,7 +223,7 @@ func (c *Controller) getUserFromURL(ctx *core.Context, request *core.Request, re
return nil, err
}
user, err := c.store.GetUserById(userID)
user, err := c.store.UserByID(userID)
if err != nil {
response.HTML().ServerError(err)
return nil, err

View file

@ -6,9 +6,10 @@ package filter
import (
"encoding/base64"
"strings"
"github.com/miniflux/miniflux2/reader/url"
"github.com/miniflux/miniflux2/server/route"
"strings"
"github.com/PuerkitoBio/goquery"
"github.com/gorilla/mux"
@ -24,7 +25,7 @@ func ImageProxyFilter(r *mux.Router, data string) string {
doc.Find("img").Each(func(i int, img *goquery.Selection) {
if srcAttr, ok := img.Attr("src"); ok {
if !url.IsHTTPS(srcAttr) {
path := route.GetRoute(r, "proxy", "encodedURL", base64.StdEncoding.EncodeToString([]byte(srcAttr)))
path := route.Path(r, "proxy", "encodedURL", base64.StdEncoding.EncodeToString([]byte(srcAttr)))
img.SetAttr("src", path)
}
}

View file

@ -5,23 +5,27 @@
package form
import (
"errors"
"net/http"
"github.com/miniflux/miniflux2/errors"
)
// AuthForm represents the authentication form.
type AuthForm struct {
Username string
Password string
}
// Validate makes sure the form values are valid.
func (a AuthForm) Validate() error {
if a.Username == "" || a.Password == "" {
return errors.New("All fields are mandatory.")
return errors.NewLocalizedError("All fields are mandatory.")
}
return nil
}
// NewAuthForm returns a new AuthForm.
func NewAuthForm(r *http.Request) *AuthForm {
return &AuthForm{
Username: r.FormValue("username"),

View file

@ -5,9 +5,10 @@
package form
import (
"errors"
"github.com/miniflux/miniflux2/model"
"net/http"
"github.com/miniflux/miniflux2/errors"
"github.com/miniflux/miniflux2/model"
)
// CategoryForm represents a feed form in the UI
@ -15,18 +16,21 @@ type CategoryForm struct {
Title string
}
// Validate makes sure the form values are valid.
func (c CategoryForm) Validate() error {
if c.Title == "" {
return errors.New("The title is mandatory.")
return errors.NewLocalizedError("The title is mandatory.")
}
return nil
}
// Merge update the given category fields.
func (c CategoryForm) Merge(category *model.Category) *model.Category {
category.Title = c.Title
return category
}
// NewCategoryForm returns a new CategoryForm.
func NewCategoryForm(r *http.Request) *CategoryForm {
return &CategoryForm{
Title: r.FormValue("title"),

View file

@ -5,10 +5,11 @@
package form
import (
"errors"
"github.com/miniflux/miniflux2/model"
"net/http"
"strconv"
"github.com/miniflux/miniflux2/errors"
"github.com/miniflux/miniflux2/model"
)
// FeedForm represents a feed form in the UI
@ -22,11 +23,12 @@ type FeedForm struct {
// ValidateModification validates FeedForm fields
func (f FeedForm) ValidateModification() error {
if f.FeedURL == "" || f.SiteURL == "" || f.Title == "" || f.CategoryID == 0 {
return errors.New("All fields are mandatory.")
return errors.NewLocalizedError("All fields are mandatory.")
}
return nil
}
// Merge updates the fields of the given feed.
func (f FeedForm) Merge(feed *model.Feed) *model.Feed {
feed.Category.ID = f.CategoryID
feed.Title = f.Title

View file

@ -5,11 +5,13 @@
package form
import (
"errors"
"github.com/miniflux/miniflux2/model"
"net/http"
"github.com/miniflux/miniflux2/errors"
"github.com/miniflux/miniflux2/model"
)
// SettingsForm represents the settings form.
type SettingsForm struct {
Username string
Password string
@ -19,6 +21,7 @@ type SettingsForm struct {
Timezone string
}
// Merge updates the fields of the given user.
func (s *SettingsForm) Merge(user *model.User) *model.User {
user.Username = s.Username
user.Theme = s.Theme
@ -32,24 +35,26 @@ func (s *SettingsForm) Merge(user *model.User) *model.User {
return user
}
// Validate makes sure the form values are valid.
func (s *SettingsForm) Validate() error {
if s.Username == "" || s.Theme == "" || s.Language == "" || s.Timezone == "" {
return errors.New("The username, theme, language and timezone fields are mandatory.")
return errors.NewLocalizedError("The username, theme, language and timezone fields are mandatory.")
}
if s.Password != "" {
if s.Password != s.Confirmation {
return errors.New("Passwords are not the same.")
return errors.NewLocalizedError("Passwords are not the same.")
}
if len(s.Password) < 6 {
return errors.New("You must use at least 6 characters")
return errors.NewLocalizedError("You must use at least 6 characters")
}
}
return nil
}
// NewSettingsForm returns a new SettingsForm.
func NewSettingsForm(r *http.Request) *SettingsForm {
return &SettingsForm{
Username: r.FormValue("username"),

View file

@ -5,24 +5,28 @@
package form
import (
"errors"
"net/http"
"strconv"
"github.com/miniflux/miniflux2/errors"
)
// SubscriptionForm represents the subscription form.
type SubscriptionForm struct {
URL string
CategoryID int64
}
// Validate makes sure the form values are valid.
func (s *SubscriptionForm) Validate() error {
if s.URL == "" || s.CategoryID == 0 {
return errors.New("The URL and the category are mandatory.")
return errors.NewLocalizedError("The URL and the category are mandatory.")
}
return nil
}
// NewSubscriptionForm returns a new SubscriptionForm.
func NewSubscriptionForm(r *http.Request) *SubscriptionForm {
categoryID, err := strconv.Atoi(r.FormValue("category_id"))
if err != nil {

View file

@ -5,11 +5,13 @@
package form
import (
"errors"
"github.com/miniflux/miniflux2/model"
"net/http"
"github.com/miniflux/miniflux2/errors"
"github.com/miniflux/miniflux2/model"
)
// UserForm represents the user form.
type UserForm struct {
Username string
Password string
@ -17,40 +19,43 @@ type UserForm struct {
IsAdmin bool
}
// ValidateCreation validates user creation.
func (u UserForm) ValidateCreation() error {
if u.Username == "" || u.Password == "" || u.Confirmation == "" {
return errors.New("All fields are mandatory.")
return errors.NewLocalizedError("All fields are mandatory.")
}
if u.Password != u.Confirmation {
return errors.New("Passwords are not the same.")
return errors.NewLocalizedError("Passwords are not the same.")
}
if len(u.Password) < 6 {
return errors.New("You must use at least 6 characters.")
return errors.NewLocalizedError("You must use at least 6 characters.")
}
return nil
}
// ValidateModification validates user modification.
func (u UserForm) ValidateModification() error {
if u.Username == "" {
return errors.New("The username is mandatory.")
return errors.NewLocalizedError("The username is mandatory.")
}
if u.Password != "" {
if u.Password != u.Confirmation {
return errors.New("Passwords are not the same.")
return errors.NewLocalizedError("Passwords are not the same.")
}
if len(u.Password) < 6 {
return errors.New("You must use at least 6 characters.")
return errors.NewLocalizedError("You must use at least 6 characters.")
}
}
return nil
}
// ToUser returns a User from the form values.
func (u UserForm) ToUser() *model.User {
return &model.User{
Username: u.Username,
@ -59,6 +64,7 @@ func (u UserForm) ToUser() *model.User {
}
}
// Merge updates the fields of the given user.
func (u UserForm) Merge(user *model.User) *model.User {
user.Username = u.Username
user.IsAdmin = u.IsAdmin
@ -70,6 +76,7 @@ func (u UserForm) Merge(user *model.User) *model.User {
return user
}
// NewUserForm returns a new UserForm.
func NewUserForm(r *http.Request) *UserForm {
return &UserForm{
Username: r.FormValue("username"),

View file

@ -7,10 +7,12 @@ package payload
import (
"encoding/json"
"fmt"
"github.com/miniflux/miniflux2/model"
"io"
"github.com/miniflux/miniflux2/model"
)
// DecodeEntryStatusPayload unserialize JSON request to update entry statuses.
func DecodeEntryStatusPayload(data io.Reader) (entryIDs []int64, status string, err error) {
type payload struct {
EntryIDs []int64 `json:"entry_ids"`

View file

@ -1,5 +1,5 @@
// Code generated by go generate; DO NOT EDIT.
// 2017-11-25 10:47:30.347837046 -0800 PST m=+0.003965152
// 2017-11-27 21:07:53.208711992 -0800 PST m=+0.002898220
package sql

View file

@ -14,6 +14,7 @@ import (
"github.com/miniflux/miniflux2/model"
)
// AnotherCategoryExists checks if another category exists with the same title.
func (s *Storage) AnotherCategoryExists(userID, categoryID int64, title string) bool {
defer helper.ExecutionTime(time.Now(), fmt.Sprintf("[Storage:AnotherCategoryExists] userID=%d, categoryID=%d, title=%s", userID, categoryID, title))
@ -23,6 +24,7 @@ func (s *Storage) AnotherCategoryExists(userID, categoryID int64, title string)
return result >= 1
}
// CategoryExists checks if the given category exists into the database.
func (s *Storage) CategoryExists(userID, categoryID int64) bool {
defer helper.ExecutionTime(time.Now(), fmt.Sprintf("[Storage:CategoryExists] userID=%d, categoryID=%d", userID, categoryID))
@ -32,8 +34,9 @@ func (s *Storage) CategoryExists(userID, categoryID int64) bool {
return result >= 1
}
func (s *Storage) GetCategory(userID, categoryID int64) (*model.Category, error) {
defer helper.ExecutionTime(time.Now(), fmt.Sprintf("[Storage:GetCategory] userID=%d, getCategory=%d", userID, categoryID))
// Category returns a category from the database.
func (s *Storage) Category(userID, categoryID int64) (*model.Category, error) {
defer helper.ExecutionTime(time.Now(), fmt.Sprintf("[Storage:Category] userID=%d, getCategory=%d", userID, categoryID))
var category model.Category
query := `SELECT id, user_id, title FROM categories WHERE user_id=$1 AND id=$2`
@ -41,29 +44,31 @@ func (s *Storage) GetCategory(userID, categoryID int64) (*model.Category, error)
if err == sql.ErrNoRows {
return nil, nil
} else if err != nil {
return nil, fmt.Errorf("Unable to fetch category: %v", err)
return nil, fmt.Errorf("unable to fetch category: %v", err)
}
return &category, nil
}
func (s *Storage) GetFirstCategory(userID int64) (*model.Category, error) {
defer helper.ExecutionTime(time.Now(), fmt.Sprintf("[Storage:GetFirstCategory] userID=%d", userID))
// FirstCategory returns the first category for the given user.
func (s *Storage) FirstCategory(userID int64) (*model.Category, error) {
defer helper.ExecutionTime(time.Now(), fmt.Sprintf("[Storage:FirstCategory] userID=%d", userID))
var category model.Category
query := `SELECT id, user_id, title FROM categories WHERE user_id=$1 ORDER BY title ASC`
query := `SELECT id, user_id, title FROM categories WHERE user_id=$1 ORDER BY title ASC LIMIT 1`
err := s.db.QueryRow(query, userID).Scan(&category.ID, &category.UserID, &category.Title)
if err == sql.ErrNoRows {
return nil, nil
} else if err != nil {
return nil, fmt.Errorf("Unable to fetch category: %v", err)
return nil, fmt.Errorf("unable to fetch category: %v", err)
}
return &category, nil
}
func (s *Storage) GetCategoryByTitle(userID int64, title string) (*model.Category, error) {
defer helper.ExecutionTime(time.Now(), fmt.Sprintf("[Storage:GetCategoryByTitle] userID=%d, title=%s", userID, title))
// CategoryByTitle finds a category by the title.
func (s *Storage) CategoryByTitle(userID int64, title string) (*model.Category, error) {
defer helper.ExecutionTime(time.Now(), fmt.Sprintf("[Storage:CategoryByTitle] userID=%d, title=%s", userID, title))
var category model.Category
query := `SELECT id, user_id, title FROM categories WHERE user_id=$1 AND title=$2`
@ -77,10 +82,11 @@ func (s *Storage) GetCategoryByTitle(userID int64, title string) (*model.Categor
return &category, nil
}
func (s *Storage) GetCategories(userID int64) (model.Categories, error) {
defer helper.ExecutionTime(time.Now(), fmt.Sprintf("[Storage:GetCategories] userID=%d", userID))
// Categories returns all categories that belongs to the given user.
func (s *Storage) Categories(userID int64) (model.Categories, error) {
defer helper.ExecutionTime(time.Now(), fmt.Sprintf("[Storage:Categories] userID=%d", userID))
query := `SELECT id, user_id, title FROM categories WHERE user_id=$1`
query := `SELECT id, user_id, title FROM categories WHERE user_id=$1 ORDER BY title ASC`
rows, err := s.db.Query(query, userID)
if err != nil {
return nil, fmt.Errorf("Unable to fetch categories: %v", err)
@ -100,8 +106,9 @@ func (s *Storage) GetCategories(userID int64) (model.Categories, error) {
return categories, nil
}
func (s *Storage) GetCategoriesWithFeedCount(userID int64) (model.Categories, error) {
defer helper.ExecutionTime(time.Now(), fmt.Sprintf("[Storage:GetCategoriesWithFeedCount] userID=%d", userID))
// CategoriesWithFeedCount returns all categories with the number of feeds.
func (s *Storage) CategoriesWithFeedCount(userID int64) (model.Categories, error) {
defer helper.ExecutionTime(time.Now(), fmt.Sprintf("[Storage:CategoriesWithFeedCount] userID=%d", userID))
query := `SELECT
c.id, c.user_id, c.title,
(SELECT count(*) FROM feeds WHERE feeds.category_id=c.id) AS count
@ -126,6 +133,7 @@ func (s *Storage) GetCategoriesWithFeedCount(userID int64) (model.Categories, er
return categories, nil
}
// CreateCategory creates a new category.
func (s *Storage) CreateCategory(category *model.Category) error {
defer helper.ExecutionTime(time.Now(), fmt.Sprintf("[Storage:CreateCategory] title=%s", category.Title))
@ -149,6 +157,7 @@ func (s *Storage) CreateCategory(category *model.Category) error {
return nil
}
// UpdateCategory updates an existing category.
func (s *Storage) UpdateCategory(category *model.Category) error {
defer helper.ExecutionTime(time.Now(), fmt.Sprintf("[Storage:UpdateCategory] categoryID=%d", category.ID))
@ -167,6 +176,7 @@ func (s *Storage) UpdateCategory(category *model.Category) error {
return nil
}
// RemoveCategory deletes a category.
func (s *Storage) RemoveCategory(userID, categoryID int64) error {
defer helper.ExecutionTime(time.Now(), fmt.Sprintf("[Storage:RemoveCategory] userID=%d, categoryID=%d", userID, categoryID))

View file

@ -14,6 +14,7 @@ import (
"github.com/miniflux/miniflux2/model"
)
// FeedExists checks if the given feed exists.
func (s *Storage) FeedExists(userID, feedID int64) bool {
defer helper.ExecutionTime(time.Now(), fmt.Sprintf("[Storage:FeedExists] userID=%d, feedID=%d", userID, feedID))
@ -23,6 +24,7 @@ func (s *Storage) FeedExists(userID, feedID int64) bool {
return result >= 1
}
// FeedURLExists checks if feed URL already exists.
func (s *Storage) FeedURLExists(userID int64, feedURL string) bool {
defer helper.ExecutionTime(time.Now(), fmt.Sprintf("[Storage:FeedURLExists] userID=%d, feedURL=%s", userID, feedURL))
@ -43,8 +45,9 @@ func (s *Storage) CountFeeds(userID int64) int {
return result
}
func (s *Storage) GetFeeds(userID int64) (model.Feeds, error) {
defer helper.ExecutionTime(time.Now(), fmt.Sprintf("[Storage:GetFeeds] userID=%d", userID))
// Feeds returns all feeds of the given user.
func (s *Storage) Feeds(userID int64) (model.Feeds, error) {
defer helper.ExecutionTime(time.Now(), fmt.Sprintf("[Storage:Feeds] userID=%d", userID))
feeds := make(model.Feeds, 0)
query := `SELECT
@ -109,8 +112,9 @@ func (s *Storage) GetFeeds(userID int64) (model.Feeds, error) {
return feeds, nil
}
func (s *Storage) GetFeedById(userID, feedID int64) (*model.Feed, error) {
defer helper.ExecutionTime(time.Now(), fmt.Sprintf("[Storage:GetFeedById] feedID=%d", feedID))
// FeedByID returns a feed by the ID.
func (s *Storage) FeedByID(userID, feedID int64) (*model.Feed, error) {
defer helper.ExecutionTime(time.Now(), fmt.Sprintf("[Storage:FeedByID] feedID=%d", feedID))
var feed model.Feed
feed.Category = &model.Category{UserID: userID}
@ -149,6 +153,7 @@ func (s *Storage) GetFeedById(userID, feedID int64) (*model.Feed, error) {
return &feed, nil
}
// CreateFeed creates a new feed.
func (s *Storage) CreateFeed(feed *model.Feed) error {
defer helper.ExecutionTime(time.Now(), fmt.Sprintf("[Storage:CreateFeed] feedURL=%s", feed.FeedURL))
sql := `
@ -184,6 +189,7 @@ func (s *Storage) CreateFeed(feed *model.Feed) error {
return nil
}
// UpdateFeed updates an existing feed.
func (s *Storage) UpdateFeed(feed *model.Feed) (err error) {
defer helper.ExecutionTime(time.Now(), fmt.Sprintf("[Storage:UpdateFeed] feedURL=%s", feed.FeedURL))
@ -213,6 +219,7 @@ func (s *Storage) UpdateFeed(feed *model.Feed) (err error) {
return nil
}
// RemoveFeed removes a feed.
func (s *Storage) RemoveFeed(userID, feedID int64) error {
defer helper.ExecutionTime(time.Now(), fmt.Sprintf("[Storage:RemoveFeed] userID=%d, feedID=%d", userID, feedID))

View file

@ -14,6 +14,7 @@ import (
"github.com/miniflux/miniflux2/model"
)
// HasIcon checks if the given feed has an icon.
func (s *Storage) HasIcon(feedID int64) bool {
var result int
query := `SELECT count(*) as c FROM feed_icons WHERE feed_id=$1`
@ -21,8 +22,9 @@ func (s *Storage) HasIcon(feedID int64) bool {
return result == 1
}
func (s *Storage) GetIconByID(iconID int64) (*model.Icon, error) {
defer helper.ExecutionTime(time.Now(), "[Storage:GetIconByID]")
// IconByID returns an icon by the ID.
func (s *Storage) IconByID(iconID int64) (*model.Icon, error) {
defer helper.ExecutionTime(time.Now(), "[Storage:IconByID]")
var icon model.Icon
query := `SELECT id, hash, mime_type, content FROM icons WHERE id=$1`
@ -36,8 +38,9 @@ func (s *Storage) GetIconByID(iconID int64) (*model.Icon, error) {
return &icon, nil
}
func (s *Storage) GetIconByHash(icon *model.Icon) error {
defer helper.ExecutionTime(time.Now(), "[Storage:GetIconByHash]")
// IconByHash returns an icon by the hash (checksum).
func (s *Storage) IconByHash(icon *model.Icon) error {
defer helper.ExecutionTime(time.Now(), "[Storage:IconByHash]")
err := s.db.QueryRow(`SELECT id FROM icons WHERE hash=$1`, icon.Hash).Scan(&icon.ID)
if err == sql.ErrNoRows {
@ -49,6 +52,7 @@ func (s *Storage) GetIconByHash(icon *model.Icon) error {
return nil
}
// CreateIcon creates a new icon.
func (s *Storage) CreateIcon(icon *model.Icon) error {
defer helper.ExecutionTime(time.Now(), "[Storage:CreateIcon]")
@ -73,10 +77,11 @@ func (s *Storage) CreateIcon(icon *model.Icon) error {
return nil
}
// CreateFeedIcon creates an icon and associate the icon to the given feed.
func (s *Storage) CreateFeedIcon(feed *model.Feed, icon *model.Icon) error {
defer helper.ExecutionTime(time.Now(), fmt.Sprintf("[Storage:CreateFeedIcon] feedID=%d", feed.ID))
err := s.GetIconByHash(icon)
err := s.IconByHash(icon)
if err != nil {
return err
}

View file

@ -7,11 +7,13 @@ package storage
import (
"database/sql"
"fmt"
"github.com/miniflux/miniflux2/helper"
"github.com/miniflux/miniflux2/model"
)
func (s *Storage) GetSessions(userID int64) (model.Sessions, error) {
// Sessions returns the list of sessions for the given user.
func (s *Storage) Sessions(userID int64) (model.Sessions, error) {
query := `SELECT id, user_id, token, created_at, user_agent, ip FROM sessions WHERE user_id=$1 ORDER BY id DESC`
rows, err := s.db.Query(query, userID)
if err != nil {
@ -41,6 +43,7 @@ func (s *Storage) GetSessions(userID int64) (model.Sessions, error) {
return sessions, nil
}
// CreateSession creates a new sessions.
func (s *Storage) CreateSession(username, userAgent, ip string) (sessionID string, err error) {
var userID int64
@ -61,7 +64,8 @@ func (s *Storage) CreateSession(username, userAgent, ip string) (sessionID strin
return token, nil
}
func (s *Storage) GetSessionByToken(token string) (*model.Session, error) {
// SessionByToken finds a session by the token.
func (s *Storage) SessionByToken(token string) (*model.Session, error) {
var session model.Session
query := "SELECT id, user_id, token, created_at, user_agent, ip FROM sessions WHERE token = $1"
@ -83,6 +87,7 @@ func (s *Storage) GetSessionByToken(token string) (*model.Session, error) {
return &session, nil
}
// RemoveSessionByToken remove a session by using the token.
func (s *Storage) RemoveSessionByToken(userID int64, token string) error {
result, err := s.db.Exec(`DELETE FROM sessions WHERE user_id=$1 AND token=$2`, userID, token)
if err != nil {
@ -101,6 +106,7 @@ func (s *Storage) RemoveSessionByToken(userID int64, token string) error {
return nil
}
// RemoveSessionByID remove a session by using the ID.
func (s *Storage) RemoveSessionByID(userID, sessionID int64) error {
result, err := s.db.Exec(`DELETE FROM sessions WHERE user_id=$1 AND id=$2`, userID, sessionID)
if err != nil {
@ -119,7 +125,8 @@ func (s *Storage) RemoveSessionByID(userID, sessionID int64) error {
return nil
}
// FlushAllSessions removes all sessions from the database.
func (s *Storage) FlushAllSessions() (err error) {
_, err = s.db.Exec(`delete from sessions`)
_, err = s.db.Exec(`DELETE FROM sessions`)
return
}

View file

@ -8,19 +8,23 @@ import (
"database/sql"
"log"
// Postgresql driver import
_ "github.com/lib/pq"
)
// Storage handles all operations related to the database.
type Storage struct {
db *sql.DB
}
// Close closes all database connections.
func (s *Storage) Close() {
s.db.Close()
}
func NewStorage(databaseUrl string, maxOpenConns int) *Storage {
db, err := sql.Open("postgres", databaseUrl)
// NewStorage returns a new Storage.
func NewStorage(databaseURL string, maxOpenConns int) *Storage {
db, err := sql.Open("postgres", databaseURL)
if err != nil {
log.Fatalf("Unable to connect to the database: %v", err)
}

View file

@ -6,12 +6,14 @@ package storage
import (
"fmt"
"github.com/miniflux/miniflux2/helper"
"time"
"github.com/miniflux/miniflux2/helper"
)
func (s *Storage) GetTimezones() (map[string]string, error) {
defer helper.ExecutionTime(time.Now(), "[Storage:GetTimezones]")
// Timezones returns all timezones supported by the database.
func (s *Storage) Timezones() (map[string]string, error) {
defer helper.ExecutionTime(time.Now(), "[Storage:Timezones]")
timezones := make(map[string]string)
query := `select name from pg_timezone_names() order by name asc`

View file

@ -19,6 +19,7 @@ import (
"golang.org/x/crypto/bcrypt"
)
// SetLastLogin updates the last login date of a user.
func (s *Storage) SetLastLogin(userID int64) error {
defer helper.ExecutionTime(time.Now(), fmt.Sprintf("[Storage:SetLastLogin] userID=%d", userID))
query := "UPDATE users SET last_login_at=now() WHERE id=$1"
@ -30,6 +31,7 @@ func (s *Storage) SetLastLogin(userID int64) error {
return nil
}
// UserExists checks if a user exists by using the given username.
func (s *Storage) UserExists(username string) bool {
defer helper.ExecutionTime(time.Now(), fmt.Sprintf("[Storage:UserExists] username=%s", username))
@ -38,6 +40,7 @@ func (s *Storage) UserExists(username string) bool {
return result >= 1
}
// AnotherUserExists checks if another user exists with the given username.
func (s *Storage) AnotherUserExists(userID int64, username string) bool {
defer helper.ExecutionTime(time.Now(), fmt.Sprintf("[Storage:AnotherUserExists] userID=%d, username=%s", userID, username))
@ -46,6 +49,7 @@ func (s *Storage) AnotherUserExists(userID int64, username string) bool {
return result >= 1
}
// CreateUser creates a new user.
func (s *Storage) CreateUser(user *model.User) (err error) {
defer helper.ExecutionTime(time.Now(), fmt.Sprintf("[Storage:CreateUser] username=%s", user.Username))
password := ""
@ -84,6 +88,7 @@ func (s *Storage) CreateUser(user *model.User) (err error) {
return nil
}
// UpdateExtraField updates an extra field of the given user.
func (s *Storage) UpdateExtraField(userID int64, field, value string) error {
query := fmt.Sprintf(`UPDATE users SET extra = hstore('%s', $1) WHERE id=$2`, field)
_, err := s.db.Exec(query, value, userID)
@ -93,6 +98,7 @@ func (s *Storage) UpdateExtraField(userID int64, field, value string) error {
return nil
}
// RemoveExtraField deletes an extra field for the given user.
func (s *Storage) RemoveExtraField(userID int64, field string) error {
query := `UPDATE users SET extra = delete(extra, $1) WHERE id=$2`
_, err := s.db.Exec(query, field, userID)
@ -102,6 +108,7 @@ func (s *Storage) RemoveExtraField(userID int64, field string) error {
return nil
}
// UpdateUser updates a user.
func (s *Storage) UpdateUser(user *model.User) error {
defer helper.ExecutionTime(time.Now(), fmt.Sprintf("[Storage:UpdateUser] username=%s", user.Username))
user.Username = strings.ToLower(user.Username)
@ -128,8 +135,9 @@ func (s *Storage) UpdateUser(user *model.User) error {
return nil
}
func (s *Storage) GetUserById(userID int64) (*model.User, error) {
defer helper.ExecutionTime(time.Now(), fmt.Sprintf("[Storage:GetUserById] userID=%d", userID))
// UserByID finds a user by the ID.
func (s *Storage) UserByID(userID int64) (*model.User, error) {
defer helper.ExecutionTime(time.Now(), fmt.Sprintf("[Storage:UserByID] userID=%d", userID))
var user model.User
var extra hstore.Hstore
@ -151,8 +159,9 @@ func (s *Storage) GetUserById(userID int64) (*model.User, error) {
return &user, nil
}
func (s *Storage) GetUserByUsername(username string) (*model.User, error) {
defer helper.ExecutionTime(time.Now(), fmt.Sprintf("[Storage:GetUserByUsername] username=%s", username))
// UserByUsername finds a user by the username.
func (s *Storage) UserByUsername(username string) (*model.User, error) {
defer helper.ExecutionTime(time.Now(), fmt.Sprintf("[Storage:UserByUsername] username=%s", username))
var user model.User
row := s.db.QueryRow("SELECT id, username, is_admin, theme, language, timezone FROM users WHERE username=$1", username)
@ -166,8 +175,9 @@ func (s *Storage) GetUserByUsername(username string) (*model.User, error) {
return &user, nil
}
func (s *Storage) GetUserByExtraField(field, value string) (*model.User, error) {
defer helper.ExecutionTime(time.Now(), fmt.Sprintf("[Storage:GetUserByExtraField] field=%s", field))
// UserByExtraField finds a user by an extra field value.
func (s *Storage) UserByExtraField(field, value string) (*model.User, error) {
defer helper.ExecutionTime(time.Now(), fmt.Sprintf("[Storage:UserByExtraField] field=%s", field))
var user model.User
query := `SELECT id, username, is_admin, theme, language, timezone FROM users WHERE extra->$1=$2`
row := s.db.QueryRow(query, field, value)
@ -181,6 +191,7 @@ func (s *Storage) GetUserByExtraField(field, value string) (*model.User, error)
return &user, nil
}
// RemoveUser deletes a user.
func (s *Storage) RemoveUser(userID int64) error {
defer helper.ExecutionTime(time.Now(), fmt.Sprintf("[Storage:RemoveUser] userID=%d", userID))
@ -195,14 +206,15 @@ func (s *Storage) RemoveUser(userID int64) error {
}
if count == 0 {
return errors.New("nothing has been removed.")
return errors.New("nothing has been removed")
}
return nil
}
func (s *Storage) GetUsers() (model.Users, error) {
defer helper.ExecutionTime(time.Now(), "[Storage:GetUsers]")
// Users returns all users.
func (s *Storage) Users() (model.Users, error) {
defer helper.ExecutionTime(time.Now(), "[Storage:Users]")
var users model.Users
rows, err := s.db.Query("SELECT id, username, is_admin, theme, language, timezone, last_login_at FROM users ORDER BY username ASC")
@ -233,6 +245,7 @@ func (s *Storage) GetUsers() (model.Users, error) {
return users, nil
}
// CheckPassword validate the hashed password.
func (s *Storage) CheckPassword(username, password string) error {
defer helper.ExecutionTime(time.Now(), "[Storage:CheckPassword]")
@ -241,13 +254,13 @@ func (s *Storage) CheckPassword(username, password string) error {
err := s.db.QueryRow("SELECT password FROM users WHERE username=$1", username).Scan(&hash)
if err == sql.ErrNoRows {
return fmt.Errorf("Unable to find this user: %s\n", username)
return fmt.Errorf("unable to find this user: %s", username)
} else if err != nil {
return fmt.Errorf("Unable to fetch user: %v\n", err)
return fmt.Errorf("unable to fetch user: %v", err)
}
if err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)); err != nil {
return fmt.Errorf("Invalid password for %s\n", username)
return fmt.Errorf("invalid password for %s", username)
}
return nil