Move UI middlewares and routes to ui package

This commit is contained in:
Frédéric Guillot 2018-11-11 11:28:29 -08:00
parent 0925899cee
commit 5a69a61d48
70 changed files with 739 additions and 769 deletions

View File

@ -5,8 +5,6 @@
package daemon // import "miniflux.app/daemon"
import (
"net/http"
"miniflux.app/api"
"miniflux.app/config"
"miniflux.app/fever"
@ -14,7 +12,6 @@ import (
"miniflux.app/reader/feed"
"miniflux.app/scheduler"
"miniflux.app/storage"
"miniflux.app/template"
"miniflux.app/ui"
"github.com/gorilla/mux"
@ -22,8 +19,6 @@ import (
func routes(cfg *config.Config, store *storage.Storage, feedHandler *feed.Handler, pool *scheduler.WorkerPool) *mux.Router {
router := mux.NewRouter()
templateEngine := template.NewEngine(cfg, router)
uiController := ui.NewController(cfg, store, pool, feedHandler, templateEngine, router)
middleware := middleware.New(cfg, store, router)
if cfg.BasePath() != "" {
@ -34,86 +29,9 @@ func routes(cfg *config.Config, store *storage.Storage, feedHandler *feed.Handle
router.Use(middleware.HeaderConfig)
router.Use(middleware.Logging)
router.HandleFunc("/healthcheck", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("OK"))
})
router.HandleFunc("/robots.txt", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
w.Write([]byte("User-agent: *\nDisallow: /"))
})
fever.Serve(router, cfg, store)
api.Serve(router, store, feedHandler)
uiRouter := router.NewRoute().Subrouter()
uiRouter.Use(middleware.AppSession)
uiRouter.Use(middleware.UserSession)
uiRouter.HandleFunc("/stylesheets/{name}.css", uiController.Stylesheet).Name("stylesheet").Methods("GET")
uiRouter.HandleFunc("/{name}.js", uiController.Javascript).Name("javascript").Methods("GET")
uiRouter.HandleFunc("/favicon.ico", uiController.Favicon).Name("favicon").Methods("GET")
uiRouter.HandleFunc("/icon/{filename}", uiController.AppIcon).Name("appIcon").Methods("GET")
uiRouter.HandleFunc("/manifest.json", uiController.WebManifest).Name("webManifest").Methods("GET")
uiRouter.HandleFunc("/subscribe", uiController.AddSubscription).Name("addSubscription").Methods("GET")
uiRouter.HandleFunc("/subscribe", uiController.SubmitSubscription).Name("submitSubscription").Methods("POST")
uiRouter.HandleFunc("/subscriptions", uiController.ChooseSubscription).Name("chooseSubscription").Methods("POST")
uiRouter.HandleFunc("/mark-all-as-read", uiController.MarkAllAsRead).Name("markAllAsRead").Methods("GET")
uiRouter.HandleFunc("/unread", uiController.ShowUnreadPage).Name("unread").Methods("GET")
uiRouter.HandleFunc("/history", uiController.ShowHistoryPage).Name("history").Methods("GET")
uiRouter.HandleFunc("/starred", uiController.ShowStarredPage).Name("starred").Methods("GET")
uiRouter.HandleFunc("/search", uiController.ShowSearchEntries).Name("searchEntries").Methods("GET")
uiRouter.HandleFunc("/search/entry/{entryID}", uiController.ShowSearchEntry).Name("searchEntry").Methods("GET")
uiRouter.HandleFunc("/feed/{feedID}/refresh", uiController.RefreshFeed).Name("refreshFeed").Methods("GET")
uiRouter.HandleFunc("/feed/{feedID}/edit", uiController.EditFeed).Name("editFeed").Methods("GET")
uiRouter.HandleFunc("/feed/{feedID}/remove", uiController.RemoveFeed).Name("removeFeed").Methods("POST")
uiRouter.HandleFunc("/feed/{feedID}/update", uiController.UpdateFeed).Name("updateFeed").Methods("POST")
uiRouter.HandleFunc("/feed/{feedID}/entries", uiController.ShowFeedEntries).Name("feedEntries").Methods("GET")
uiRouter.HandleFunc("/feeds", uiController.ShowFeedsPage).Name("feeds").Methods("GET")
uiRouter.HandleFunc("/feeds/refresh", uiController.RefreshAllFeeds).Name("refreshAllFeeds").Methods("GET")
uiRouter.HandleFunc("/unread/entry/{entryID}", uiController.ShowUnreadEntry).Name("unreadEntry").Methods("GET")
uiRouter.HandleFunc("/history/entry/{entryID}", uiController.ShowReadEntry).Name("readEntry").Methods("GET")
uiRouter.HandleFunc("/history/flush", uiController.FlushHistory).Name("flushHistory").Methods("GET")
uiRouter.HandleFunc("/feed/{feedID}/entry/{entryID}", uiController.ShowFeedEntry).Name("feedEntry").Methods("GET")
uiRouter.HandleFunc("/category/{categoryID}/entry/{entryID}", uiController.ShowCategoryEntry).Name("categoryEntry").Methods("GET")
uiRouter.HandleFunc("/starred/entry/{entryID}", uiController.ShowStarredEntry).Name("starredEntry").Methods("GET")
uiRouter.HandleFunc("/entry/status", uiController.UpdateEntriesStatus).Name("updateEntriesStatus").Methods("POST")
uiRouter.HandleFunc("/entry/save/{entryID}", uiController.SaveEntry).Name("saveEntry").Methods("POST")
uiRouter.HandleFunc("/entry/download/{entryID}", uiController.FetchContent).Name("fetchContent").Methods("POST")
uiRouter.HandleFunc("/entry/bookmark/{entryID}", uiController.ToggleBookmark).Name("toggleBookmark").Methods("POST")
uiRouter.HandleFunc("/categories", uiController.CategoryList).Name("categories").Methods("GET")
uiRouter.HandleFunc("/category/create", uiController.CreateCategory).Name("createCategory").Methods("GET")
uiRouter.HandleFunc("/category/save", uiController.SaveCategory).Name("saveCategory").Methods("POST")
uiRouter.HandleFunc("/category/{categoryID}/entries", uiController.CategoryEntries).Name("categoryEntries").Methods("GET")
uiRouter.HandleFunc("/category/{categoryID}/edit", uiController.EditCategory).Name("editCategory").Methods("GET")
uiRouter.HandleFunc("/category/{categoryID}/update", uiController.UpdateCategory).Name("updateCategory").Methods("POST")
uiRouter.HandleFunc("/category/{categoryID}/remove", uiController.RemoveCategory).Name("removeCategory").Methods("POST")
uiRouter.HandleFunc("/feed/icon/{iconID}", uiController.ShowIcon).Name("icon").Methods("GET")
uiRouter.HandleFunc("/proxy/{encodedURL}", uiController.ImageProxy).Name("proxy").Methods("GET")
uiRouter.HandleFunc("/users", uiController.ShowUsers).Name("users").Methods("GET")
uiRouter.HandleFunc("/user/create", uiController.CreateUser).Name("createUser").Methods("GET")
uiRouter.HandleFunc("/user/save", uiController.SaveUser).Name("saveUser").Methods("POST")
uiRouter.HandleFunc("/users/{userID}/edit", uiController.EditUser).Name("editUser").Methods("GET")
uiRouter.HandleFunc("/users/{userID}/update", uiController.UpdateUser).Name("updateUser").Methods("POST")
uiRouter.HandleFunc("/users/{userID}/remove", uiController.RemoveUser).Name("removeUser").Methods("POST")
uiRouter.HandleFunc("/about", uiController.About).Name("about").Methods("GET")
uiRouter.HandleFunc("/settings", uiController.ShowSettings).Name("settings").Methods("GET")
uiRouter.HandleFunc("/settings", uiController.UpdateSettings).Name("updateSettings").Methods("POST")
uiRouter.HandleFunc("/bookmarklet", uiController.Bookmarklet).Name("bookmarklet").Methods("GET")
uiRouter.HandleFunc("/integrations", uiController.ShowIntegrations).Name("integrations").Methods("GET")
uiRouter.HandleFunc("/integration", uiController.UpdateIntegration).Name("updateIntegration").Methods("POST")
uiRouter.HandleFunc("/integration/pocket/authorize", uiController.PocketAuthorize).Name("pocketAuthorize").Methods("GET")
uiRouter.HandleFunc("/integration/pocket/callback", uiController.PocketCallback).Name("pocketCallback").Methods("GET")
uiRouter.HandleFunc("/sessions", uiController.ShowSessions).Name("sessions").Methods("GET")
uiRouter.HandleFunc("/sessions/{sessionID}/remove", uiController.RemoveSession).Name("removeSession").Methods("POST")
uiRouter.HandleFunc("/export", uiController.Export).Name("export").Methods("GET")
uiRouter.HandleFunc("/import", uiController.Import).Name("import").Methods("GET")
uiRouter.HandleFunc("/upload", uiController.UploadOPML).Name("uploadOPML").Methods("POST")
uiRouter.HandleFunc("/oauth2/{provider}/unlink", uiController.OAuth2Unlink).Name("oauth2Unlink").Methods("GET")
uiRouter.HandleFunc("/oauth2/{provider}/redirect", uiController.OAuth2Redirect).Name("oauth2Redirect").Methods("GET")
uiRouter.HandleFunc("/oauth2/{provider}/callback", uiController.OAuth2Callback).Name("oauth2Callback").Methods("GET")
uiRouter.HandleFunc("/login", uiController.CheckLogin).Name("checkLogin").Methods("POST")
uiRouter.HandleFunc("/logout", uiController.Logout).Name("logout").Methods("GET")
uiRouter.HandleFunc("/", uiController.ShowLoginPage).Name("login").Methods("GET")
ui.Serve(router, cfg, store, pool, feedHandler)
return router
}

View File

@ -1,76 +0,0 @@
// Copyright 2018 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
package middleware // import "miniflux.app/middleware"
import (
"context"
"errors"
"net/http"
"miniflux.app/http/cookie"
"miniflux.app/http/request"
"miniflux.app/http/response/html"
"miniflux.app/logger"
"miniflux.app/model"
)
// AppSession handles application session middleware.
func (m *Middleware) AppSession(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var err error
session := m.getAppSessionValueFromCookie(r)
if session == nil {
logger.Debug("[Middleware:AppSession] Session not found")
session, err = m.store.CreateSession()
if err != nil {
html.ServerError(w, r, err)
return
}
http.SetCookie(w, cookie.New(cookie.CookieSessionID, session.ID, m.cfg.IsHTTPS, m.cfg.BasePath()))
} else {
logger.Debug("[Middleware:AppSession] %s", session)
}
if r.Method == "POST" {
formValue := r.FormValue("csrf")
headerValue := r.Header.Get("X-Csrf-Token")
if session.Data.CSRF != formValue && session.Data.CSRF != headerValue {
logger.Error(`[Middleware:AppSession] Invalid or missing CSRF token: Form="%s", Header="%s"`, formValue, headerValue)
html.BadRequest(w, r, errors.New("Invalid or missing CSRF"))
return
}
}
ctx := r.Context()
ctx = context.WithValue(ctx, request.SessionIDContextKey, session.ID)
ctx = context.WithValue(ctx, request.CSRFContextKey, session.Data.CSRF)
ctx = context.WithValue(ctx, request.OAuth2StateContextKey, session.Data.OAuth2State)
ctx = context.WithValue(ctx, request.FlashMessageContextKey, session.Data.FlashMessage)
ctx = context.WithValue(ctx, request.FlashErrorMessageContextKey, session.Data.FlashErrorMessage)
ctx = context.WithValue(ctx, request.UserLanguageContextKey, session.Data.Language)
ctx = context.WithValue(ctx, request.UserThemeContextKey, session.Data.Theme)
ctx = context.WithValue(ctx, request.PocketRequestTokenContextKey, session.Data.PocketRequestToken)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func (m *Middleware) getAppSessionValueFromCookie(r *http.Request) *model.Session {
cookieValue := request.CookieValue(r, cookie.CookieSessionID)
if cookieValue == "" {
return nil
}
session, err := m.store.Session(cookieValue)
if err != nil {
logger.Error("[Middleware:AppSession] %v", err)
return nil
}
return session
}

View File

@ -1,77 +0,0 @@
// 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 // import "miniflux.app/middleware"
import (
"context"
"net/http"
"miniflux.app/http/cookie"
"miniflux.app/http/request"
"miniflux.app/http/response/html"
"miniflux.app/http/route"
"miniflux.app/logger"
"miniflux.app/model"
"github.com/gorilla/mux"
)
// UserSession handles the user session middleware.
func (m *Middleware) UserSession(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
session := m.getUserSessionFromCookie(r)
if session == nil {
logger.Debug("[Middleware:UserSession] Session not found")
if m.isPublicRoute(r) {
next.ServeHTTP(w, r)
} else {
html.Redirect(w, r, route.Path(m.router, "login"))
}
} else {
logger.Debug("[Middleware:UserSession] %s", session)
ctx := r.Context()
ctx = context.WithValue(ctx, request.UserIDContextKey, session.UserID)
ctx = context.WithValue(ctx, request.IsAuthenticatedContextKey, true)
ctx = context.WithValue(ctx, request.UserSessionTokenContextKey, session.Token)
next.ServeHTTP(w, r.WithContext(ctx))
}
})
}
func (m *Middleware) isPublicRoute(r *http.Request) bool {
route := mux.CurrentRoute(r)
switch route.GetName() {
case "login",
"checkLogin",
"stylesheet",
"javascript",
"oauth2Redirect",
"oauth2Callback",
"appIcon",
"favicon",
"webManifest":
return true
default:
return false
}
}
func (m *Middleware) getUserSessionFromCookie(r *http.Request) *model.UserSession {
cookieValue := request.CookieValue(r, cookie.CookieUserSessionID)
if cookieValue == "" {
return nil
}
session, err := m.store.UserSessionByToken(cookieValue)
if err != nil {
logger.Error("[Middleware:UserSession] %v", err)
return nil
}
return session
}

View File

@ -14,22 +14,21 @@ import (
"miniflux.app/version"
)
// About shows the about page.
func (c *Controller) About(w http.ResponseWriter, r *http.Request) {
user, err := c.store.UserByID(request.UserID(r))
func (h *handler) showAboutPage(w http.ResponseWriter, r *http.Request) {
user, err := h.store.UserByID(request.UserID(r))
if err != nil {
html.ServerError(w, r, err)
return
}
sess := session.New(c.store, request.SessionID(r))
view := view.New(c.tpl, r, sess)
sess := session.New(h.store, request.SessionID(r))
view := view.New(h.tpl, r, sess)
view.Set("version", version.Version)
view.Set("build_date", version.BuildDate)
view.Set("menu", "settings")
view.Set("user", user)
view.Set("countUnread", c.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", c.store.CountErrorFeeds(user.ID))
view.Set("countUnread", h.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", h.store.CountErrorFeeds(user.ID))
html.OK(w, r, view.Render("about"))
}

View File

@ -15,16 +15,15 @@ import (
"miniflux.app/ui/view"
)
// ShowStarredPage renders the page with all starred entries.
func (c *Controller) ShowStarredPage(w http.ResponseWriter, r *http.Request) {
user, err := c.store.UserByID(request.UserID(r))
func (h *handler) showStarredPage(w http.ResponseWriter, r *http.Request) {
user, err := h.store.UserByID(request.UserID(r))
if err != nil {
html.ServerError(w, r, err)
return
}
offset := request.QueryIntParam(r, "offset", 0)
builder := c.store.NewEntryQueryBuilder(user.ID)
builder := h.store.NewEntryQueryBuilder(user.ID)
builder.WithoutStatus(model.EntryStatusRemoved)
builder.WithStarred()
builder.WithOrder(model.DefaultSortingOrder)
@ -44,17 +43,17 @@ func (c *Controller) ShowStarredPage(w http.ResponseWriter, r *http.Request) {
return
}
sess := session.New(c.store, request.SessionID(r))
view := view.New(c.tpl, r, sess)
sess := session.New(h.store, request.SessionID(r))
view := view.New(h.tpl, r, sess)
view.Set("total", count)
view.Set("entries", entries)
view.Set("pagination", c.getPagination(route.Path(c.router, "starred"), count, offset))
view.Set("pagination", getPagination(route.Path(h.router, "starred"), count, offset))
view.Set("menu", "starred")
view.Set("user", user)
view.Set("countUnread", c.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", c.store.CountErrorFeeds(user.ID))
view.Set("hasSaveEntry", c.store.HasSaveEntry(user.ID))
view.Set("countUnread", h.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", h.store.CountErrorFeeds(user.ID))
view.Set("hasSaveEntry", h.store.HasSaveEntry(user.ID))
html.OK(w, r, view.Render("bookmark_entries"))
}

View File

@ -13,20 +13,19 @@ import (
"miniflux.app/ui/view"
)
// CreateCategory shows the form to create a new category.
func (c *Controller) CreateCategory(w http.ResponseWriter, r *http.Request) {
user, err := c.store.UserByID(request.UserID(r))
func (h *handler) showCreateCategoryPage(w http.ResponseWriter, r *http.Request) {
user, err := h.store.UserByID(request.UserID(r))
if err != nil {
html.ServerError(w, r, err)
return
}
sess := session.New(c.store, request.SessionID(r))
view := view.New(c.tpl, r, sess)
sess := session.New(h.store, request.SessionID(r))
view := view.New(h.tpl, r, sess)
view.Set("menu", "categories")
view.Set("user", user)
view.Set("countUnread", c.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", c.store.CountErrorFeeds(user.ID))
view.Set("countUnread", h.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", h.store.CountErrorFeeds(user.ID))
html.OK(w, r, view.Render("create_category"))
}

View File

@ -14,19 +14,18 @@ import (
"miniflux.app/ui/view"
)
// EditCategory shows the form to modify a category.
func (c *Controller) EditCategory(w http.ResponseWriter, r *http.Request) {
sess := session.New(c.store, request.SessionID(r))
view := view.New(c.tpl, r, sess)
func (h *handler) showEditCategoryPage(w http.ResponseWriter, r *http.Request) {
sess := session.New(h.store, request.SessionID(r))
view := view.New(h.tpl, r, sess)
user, err := c.store.UserByID(request.UserID(r))
user, err := h.store.UserByID(request.UserID(r))
if err != nil {
html.ServerError(w, r, err)
return
}
categoryID := request.RouteInt64Param(r, "categoryID")
category, err := c.store.Category(request.UserID(r), categoryID)
category, err := h.store.Category(request.UserID(r), categoryID)
if err != nil {
html.ServerError(w, r, err)
return
@ -45,8 +44,8 @@ func (c *Controller) EditCategory(w http.ResponseWriter, r *http.Request) {
view.Set("category", category)
view.Set("menu", "categories")
view.Set("user", user)
view.Set("countUnread", c.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", c.store.CountErrorFeeds(user.ID))
view.Set("countUnread", h.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", h.store.CountErrorFeeds(user.ID))
html.OK(w, r, view.Render("edit_category"))
}

View File

@ -15,16 +15,15 @@ import (
"miniflux.app/ui/view"
)
// CategoryEntries shows all entries for the given category.
func (c *Controller) CategoryEntries(w http.ResponseWriter, r *http.Request) {
user, err := c.store.UserByID(request.UserID(r))
func (h *handler) showCategoryEntriesPage(w http.ResponseWriter, r *http.Request) {
user, err := h.store.UserByID(request.UserID(r))
if err != nil {
html.ServerError(w, r, err)
return
}
categoryID := request.RouteInt64Param(r, "categoryID")
category, err := c.store.Category(request.UserID(r), categoryID)
category, err := h.store.Category(request.UserID(r), categoryID)
if err != nil {
html.ServerError(w, r, err)
return
@ -36,7 +35,7 @@ func (c *Controller) CategoryEntries(w http.ResponseWriter, r *http.Request) {
}
offset := request.QueryIntParam(r, "offset", 0)
builder := c.store.NewEntryQueryBuilder(user.ID)
builder := h.store.NewEntryQueryBuilder(user.ID)
builder.WithCategoryID(category.ID)
builder.WithOrder(model.DefaultSortingOrder)
builder.WithDirection(user.EntryDirection)
@ -56,17 +55,17 @@ func (c *Controller) CategoryEntries(w http.ResponseWriter, r *http.Request) {
return
}
sess := session.New(c.store, request.SessionID(r))
view := view.New(c.tpl, r, sess)
sess := session.New(h.store, request.SessionID(r))
view := view.New(h.tpl, r, sess)
view.Set("category", category)
view.Set("total", count)
view.Set("entries", entries)
view.Set("pagination", c.getPagination(route.Path(c.router, "categoryEntries", "categoryID", category.ID), count, offset))
view.Set("pagination", getPagination(route.Path(h.router, "categoryEntries", "categoryID", category.ID), count, offset))
view.Set("menu", "categories")
view.Set("user", user)
view.Set("countUnread", c.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", c.store.CountErrorFeeds(user.ID))
view.Set("hasSaveEntry", c.store.HasSaveEntry(user.ID))
view.Set("countUnread", h.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", h.store.CountErrorFeeds(user.ID))
view.Set("hasSaveEntry", h.store.HasSaveEntry(user.ID))
html.OK(w, r, view.Render("category_entries"))
}

View File

@ -13,28 +13,27 @@ import (
"miniflux.app/ui/view"
)
// CategoryList shows the page with all categories.
func (c *Controller) CategoryList(w http.ResponseWriter, r *http.Request) {
user, err := c.store.UserByID(request.UserID(r))
func (h *handler) showCategoryListPage(w http.ResponseWriter, r *http.Request) {
user, err := h.store.UserByID(request.UserID(r))
if err != nil {
html.ServerError(w, r, err)
return
}
categories, err := c.store.CategoriesWithFeedCount(user.ID)
categories, err := h.store.CategoriesWithFeedCount(user.ID)
if err != nil {
html.ServerError(w, r, err)
return
}
sess := session.New(c.store, request.SessionID(r))
view := view.New(c.tpl, r, sess)
sess := session.New(h.store, request.SessionID(r))
view := view.New(h.tpl, r, sess)
view.Set("categories", categories)
view.Set("total", len(categories))
view.Set("menu", "categories")
view.Set("user", user)
view.Set("countUnread", c.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", c.store.CountErrorFeeds(user.ID))
view.Set("countUnread", h.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", h.store.CountErrorFeeds(user.ID))
html.OK(w, r, view.Render("categories"))
}

View File

@ -12,16 +12,15 @@ import (
"miniflux.app/http/route"
)
// RemoveCategory deletes a category from the database.
func (c *Controller) RemoveCategory(w http.ResponseWriter, r *http.Request) {
user, err := c.store.UserByID(request.UserID(r))
func (h *handler) removeCategory(w http.ResponseWriter, r *http.Request) {
user, err := h.store.UserByID(request.UserID(r))
if err != nil {
html.ServerError(w, r, err)
return
}
categoryID := request.RouteInt64Param(r, "categoryID")
category, err := c.store.Category(request.UserID(r), categoryID)
category, err := h.store.Category(request.UserID(r), categoryID)
if err != nil {
html.ServerError(w, r, err)
return
@ -32,10 +31,10 @@ func (c *Controller) RemoveCategory(w http.ResponseWriter, r *http.Request) {
return
}
if err := c.store.RemoveCategory(user.ID, category.ID); err != nil {
if err := h.store.RemoveCategory(user.ID, category.ID); err != nil {
html.ServerError(w, r, err)
return
}
html.Redirect(w, r, route.Path(c.router, "categories"))
html.Redirect(w, r, route.Path(h.router, "categories"))
}

View File

@ -17,9 +17,8 @@ import (
"miniflux.app/ui/view"
)
// SaveCategory validate and save the new category into the database.
func (c *Controller) SaveCategory(w http.ResponseWriter, r *http.Request) {
user, err := c.store.UserByID(request.UserID(r))
func (h *handler) saveCategory(w http.ResponseWriter, r *http.Request) {
user, err := h.store.UserByID(request.UserID(r))
if err != nil {
html.ServerError(w, r, err)
return
@ -27,13 +26,13 @@ func (c *Controller) SaveCategory(w http.ResponseWriter, r *http.Request) {
categoryForm := form.NewCategoryForm(r)
sess := session.New(c.store, request.SessionID(r))
view := view.New(c.tpl, r, sess)
sess := session.New(h.store, request.SessionID(r))
view := view.New(h.tpl, r, sess)
view.Set("form", categoryForm)
view.Set("menu", "categories")
view.Set("user", user)
view.Set("countUnread", c.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", c.store.CountErrorFeeds(user.ID))
view.Set("countUnread", h.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", h.store.CountErrorFeeds(user.ID))
if err := categoryForm.Validate(); err != nil {
view.Set("errorMessage", err.Error())
@ -41,7 +40,7 @@ func (c *Controller) SaveCategory(w http.ResponseWriter, r *http.Request) {
return
}
duplicateCategory, err := c.store.CategoryByTitle(user.ID, categoryForm.Title)
duplicateCategory, err := h.store.CategoryByTitle(user.ID, categoryForm.Title)
if err != nil {
html.ServerError(w, r, err)
return
@ -58,12 +57,12 @@ func (c *Controller) SaveCategory(w http.ResponseWriter, r *http.Request) {
UserID: user.ID,
}
if err = c.store.CreateCategory(&category); err != nil {
if err = h.store.CreateCategory(&category); err != nil {
logger.Error("[Controller:CreateCategory] %v", err)
view.Set("errorMessage", "error.unable_to_create_category")
html.OK(w, r, view.Render("create_category"))
return
}
html.Redirect(w, r, route.Path(c.router, "categories"))
html.Redirect(w, r, route.Path(h.router, "categories"))
}

View File

@ -16,16 +16,15 @@ import (
"miniflux.app/ui/view"
)
// UpdateCategory validates and updates a category.
func (c *Controller) UpdateCategory(w http.ResponseWriter, r *http.Request) {
user, err := c.store.UserByID(request.UserID(r))
func (h *handler) updateCategory(w http.ResponseWriter, r *http.Request) {
user, err := h.store.UserByID(request.UserID(r))
if err != nil {
html.ServerError(w, r, err)
return
}
categoryID := request.RouteInt64Param(r, "categoryID")
category, err := c.store.Category(request.UserID(r), categoryID)
category, err := h.store.Category(request.UserID(r), categoryID)
if err != nil {
html.ServerError(w, r, err)
return
@ -38,14 +37,14 @@ func (c *Controller) UpdateCategory(w http.ResponseWriter, r *http.Request) {
categoryForm := form.NewCategoryForm(r)
sess := session.New(c.store, request.SessionID(r))
view := view.New(c.tpl, r, sess)
sess := session.New(h.store, request.SessionID(r))
view := view.New(h.tpl, r, sess)
view.Set("form", categoryForm)
view.Set("category", category)
view.Set("menu", "categories")
view.Set("user", user)
view.Set("countUnread", c.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", c.store.CountErrorFeeds(user.ID))
view.Set("countUnread", h.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", h.store.CountErrorFeeds(user.ID))
if err := categoryForm.Validate(); err != nil {
view.Set("errorMessage", err.Error())
@ -53,13 +52,13 @@ func (c *Controller) UpdateCategory(w http.ResponseWriter, r *http.Request) {
return
}
if c.store.AnotherCategoryExists(user.ID, category.ID, categoryForm.Title) {
if h.store.AnotherCategoryExists(user.ID, category.ID, categoryForm.Title) {
view.Set("errorMessage", "error.category_already_exists")
html.OK(w, r, view.Render("edit_category"))
return
}
err = c.store.UpdateCategory(categoryForm.Merge(category))
err = h.store.UpdateCategory(categoryForm.Merge(category))
if err != nil {
logger.Error("[Controller:UpdateCategory] %v", err)
view.Set("errorMessage", "error.unable_to_update_category")
@ -67,5 +66,5 @@ func (c *Controller) UpdateCategory(w http.ResponseWriter, r *http.Request) {
return
}
html.Redirect(w, r, route.Path(c.router, "categories"))
html.Redirect(w, r, route.Path(h.router, "categories"))
}

View File

@ -16,16 +16,15 @@ import (
"miniflux.app/ui/view"
)
// ShowStarredEntry shows a single feed entry in "starred" mode.
func (c *Controller) ShowStarredEntry(w http.ResponseWriter, r *http.Request) {
user, err := c.store.UserByID(request.UserID(r))
func (h *handler) showStarredEntryPage(w http.ResponseWriter, r *http.Request) {
user, err := h.store.UserByID(request.UserID(r))
if err != nil {
html.ServerError(w, r, err)
return
}
entryID := request.RouteInt64Param(r, "entryID")
builder := c.store.NewEntryQueryBuilder(user.ID)
builder := h.store.NewEntryQueryBuilder(user.ID)
builder.WithEntryID(entryID)
builder.WithoutStatus(model.EntryStatusRemoved)
@ -41,7 +40,7 @@ func (c *Controller) ShowStarredEntry(w http.ResponseWriter, r *http.Request) {
}
if entry.Status == model.EntryStatusUnread {
err = c.store.SetEntriesStatus(user.ID, []int64{entry.ID}, model.EntryStatusRead)
err = h.store.SetEntriesStatus(user.ID, []int64{entry.ID}, model.EntryStatusRead)
if err != nil {
html.ServerError(w, r, err)
return
@ -50,7 +49,7 @@ func (c *Controller) ShowStarredEntry(w http.ResponseWriter, r *http.Request) {
entry.Status = model.EntryStatusRead
}
entryPaginationBuilder := storage.NewEntryPaginationBuilder(c.store, user.ID, entry.ID, user.EntryDirection)
entryPaginationBuilder := storage.NewEntryPaginationBuilder(h.store, user.ID, entry.ID, user.EntryDirection)
entryPaginationBuilder.WithStarred()
prevEntry, nextEntry, err := entryPaginationBuilder.Entries()
if err != nil {
@ -60,16 +59,16 @@ func (c *Controller) ShowStarredEntry(w http.ResponseWriter, r *http.Request) {
nextEntryRoute := ""
if nextEntry != nil {
nextEntryRoute = route.Path(c.router, "starredEntry", "entryID", nextEntry.ID)
nextEntryRoute = route.Path(h.router, "starredEntry", "entryID", nextEntry.ID)
}
prevEntryRoute := ""
if prevEntry != nil {
prevEntryRoute = route.Path(c.router, "starredEntry", "entryID", prevEntry.ID)
prevEntryRoute = route.Path(h.router, "starredEntry", "entryID", prevEntry.ID)
}
sess := session.New(c.store, request.SessionID(r))
view := view.New(c.tpl, r, sess)
sess := session.New(h.store, request.SessionID(r))
view := view.New(h.tpl, r, sess)
view.Set("entry", entry)
view.Set("prevEntry", prevEntry)
view.Set("nextEntry", nextEntry)
@ -77,9 +76,9 @@ func (c *Controller) ShowStarredEntry(w http.ResponseWriter, r *http.Request) {
view.Set("prevEntryRoute", prevEntryRoute)
view.Set("menu", "starred")
view.Set("user", user)
view.Set("countUnread", c.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", c.store.CountErrorFeeds(user.ID))
view.Set("hasSaveEntry", c.store.HasSaveEntry(user.ID))
view.Set("countUnread", h.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", h.store.CountErrorFeeds(user.ID))
view.Set("hasSaveEntry", h.store.HasSaveEntry(user.ID))
html.OK(w, r, view.Render("entry"))
}

View File

@ -16,9 +16,8 @@ import (
"miniflux.app/ui/view"
)
// ShowCategoryEntry shows a single feed entry in "category" mode.
func (c *Controller) ShowCategoryEntry(w http.ResponseWriter, r *http.Request) {
user, err := c.store.UserByID(request.UserID(r))
func (h *handler) showCategoryEntryPage(w http.ResponseWriter, r *http.Request) {
user, err := h.store.UserByID(request.UserID(r))
if err != nil {
html.ServerError(w, r, err)
return
@ -27,7 +26,7 @@ func (c *Controller) ShowCategoryEntry(w http.ResponseWriter, r *http.Request) {
categoryID := request.RouteInt64Param(r, "categoryID")
entryID := request.RouteInt64Param(r, "entryID")
builder := c.store.NewEntryQueryBuilder(user.ID)
builder := h.store.NewEntryQueryBuilder(user.ID)
builder.WithCategoryID(categoryID)
builder.WithEntryID(entryID)
builder.WithoutStatus(model.EntryStatusRemoved)
@ -44,7 +43,7 @@ func (c *Controller) ShowCategoryEntry(w http.ResponseWriter, r *http.Request) {
}
if entry.Status == model.EntryStatusUnread {
err = c.store.SetEntriesStatus(user.ID, []int64{entry.ID}, model.EntryStatusRead)
err = h.store.SetEntriesStatus(user.ID, []int64{entry.ID}, model.EntryStatusRead)
if err != nil {
html.ServerError(w, r, err)
return
@ -53,7 +52,7 @@ func (c *Controller) ShowCategoryEntry(w http.ResponseWriter, r *http.Request) {
entry.Status = model.EntryStatusRead
}
entryPaginationBuilder := storage.NewEntryPaginationBuilder(c.store, user.ID, entry.ID, user.EntryDirection)
entryPaginationBuilder := storage.NewEntryPaginationBuilder(h.store, user.ID, entry.ID, user.EntryDirection)
entryPaginationBuilder.WithCategoryID(categoryID)
prevEntry, nextEntry, err := entryPaginationBuilder.Entries()
if err != nil {
@ -63,16 +62,16 @@ func (c *Controller) ShowCategoryEntry(w http.ResponseWriter, r *http.Request) {
nextEntryRoute := ""
if nextEntry != nil {
nextEntryRoute = route.Path(c.router, "categoryEntry", "categoryID", categoryID, "entryID", nextEntry.ID)
nextEntryRoute = route.Path(h.router, "categoryEntry", "categoryID", categoryID, "entryID", nextEntry.ID)
}
prevEntryRoute := ""
if prevEntry != nil {
prevEntryRoute = route.Path(c.router, "categoryEntry", "categoryID", categoryID, "entryID", prevEntry.ID)
prevEntryRoute = route.Path(h.router, "categoryEntry", "categoryID", categoryID, "entryID", prevEntry.ID)
}
sess := session.New(c.store, request.SessionID(r))
view := view.New(c.tpl, r, sess)
sess := session.New(h.store, request.SessionID(r))
view := view.New(h.tpl, r, sess)
view.Set("entry", entry)
view.Set("prevEntry", prevEntry)
view.Set("nextEntry", nextEntry)
@ -80,9 +79,9 @@ func (c *Controller) ShowCategoryEntry(w http.ResponseWriter, r *http.Request) {
view.Set("prevEntryRoute", prevEntryRoute)
view.Set("menu", "categories")
view.Set("user", user)
view.Set("countUnread", c.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", c.store.CountErrorFeeds(user.ID))
view.Set("hasSaveEntry", c.store.HasSaveEntry(user.ID))
view.Set("countUnread", h.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", h.store.CountErrorFeeds(user.ID))
view.Set("hasSaveEntry", h.store.HasSaveEntry(user.ID))
html.OK(w, r, view.Render("entry"))
}

View File

@ -16,9 +16,8 @@ import (
"miniflux.app/ui/view"
)
// ShowFeedEntry shows a single feed entry in "feed" mode.
func (c *Controller) ShowFeedEntry(w http.ResponseWriter, r *http.Request) {
user, err := c.store.UserByID(request.UserID(r))
func (h *handler) showFeedEntryPage(w http.ResponseWriter, r *http.Request) {
user, err := h.store.UserByID(request.UserID(r))
if err != nil {
html.ServerError(w, r, err)
return
@ -27,7 +26,7 @@ func (c *Controller) ShowFeedEntry(w http.ResponseWriter, r *http.Request) {
entryID := request.RouteInt64Param(r, "entryID")
feedID := request.RouteInt64Param(r, "feedID")
builder := c.store.NewEntryQueryBuilder(user.ID)
builder := h.store.NewEntryQueryBuilder(user.ID)
builder.WithFeedID(feedID)
builder.WithEntryID(entryID)
builder.WithoutStatus(model.EntryStatusRemoved)
@ -44,7 +43,7 @@ func (c *Controller) ShowFeedEntry(w http.ResponseWriter, r *http.Request) {
}
if entry.Status == model.EntryStatusUnread {
err = c.store.SetEntriesStatus(user.ID, []int64{entry.ID}, model.EntryStatusRead)
err = h.store.SetEntriesStatus(user.ID, []int64{entry.ID}, model.EntryStatusRead)
if err != nil {
html.ServerError(w, r, err)
return
@ -53,7 +52,7 @@ func (c *Controller) ShowFeedEntry(w http.ResponseWriter, r *http.Request) {
entry.Status = model.EntryStatusRead
}
entryPaginationBuilder := storage.NewEntryPaginationBuilder(c.store, user.ID, entry.ID, user.EntryDirection)
entryPaginationBuilder := storage.NewEntryPaginationBuilder(h.store, user.ID, entry.ID, user.EntryDirection)
entryPaginationBuilder.WithFeedID(feedID)
prevEntry, nextEntry, err := entryPaginationBuilder.Entries()
if err != nil {
@ -63,16 +62,16 @@ func (c *Controller) ShowFeedEntry(w http.ResponseWriter, r *http.Request) {
nextEntryRoute := ""
if nextEntry != nil {
nextEntryRoute = route.Path(c.router, "feedEntry", "feedID", feedID, "entryID", nextEntry.ID)
nextEntryRoute = route.Path(h.router, "feedEntry", "feedID", feedID, "entryID", nextEntry.ID)
}
prevEntryRoute := ""
if prevEntry != nil {
prevEntryRoute = route.Path(c.router, "feedEntry", "feedID", feedID, "entryID", prevEntry.ID)
prevEntryRoute = route.Path(h.router, "feedEntry", "feedID", feedID, "entryID", prevEntry.ID)
}
sess := session.New(c.store, request.SessionID(r))
view := view.New(c.tpl, r, sess)
sess := session.New(h.store, request.SessionID(r))
view := view.New(h.tpl, r, sess)
view.Set("entry", entry)
view.Set("prevEntry", prevEntry)
view.Set("nextEntry", nextEntry)
@ -80,9 +79,9 @@ func (c *Controller) ShowFeedEntry(w http.ResponseWriter, r *http.Request) {
view.Set("prevEntryRoute", prevEntryRoute)
view.Set("menu", "feeds")
view.Set("user", user)
view.Set("countUnread", c.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", c.store.CountErrorFeeds(user.ID))
view.Set("hasSaveEntry", c.store.HasSaveEntry(user.ID))
view.Set("countUnread", h.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", h.store.CountErrorFeeds(user.ID))
view.Set("hasSaveEntry", h.store.HasSaveEntry(user.ID))
html.OK(w, r, view.Render("entry"))
}

View File

@ -16,16 +16,15 @@ import (
"miniflux.app/ui/view"
)
// ShowReadEntry shows a single feed entry in "history" mode.
func (c *Controller) ShowReadEntry(w http.ResponseWriter, r *http.Request) {
user, err := c.store.UserByID(request.UserID(r))
func (h *handler) showReadEntryPage(w http.ResponseWriter, r *http.Request) {
user, err := h.store.UserByID(request.UserID(r))
if err != nil {
html.ServerError(w, r, err)
return
}
entryID := request.RouteInt64Param(r, "entryID")
builder := c.store.NewEntryQueryBuilder(user.ID)
builder := h.store.NewEntryQueryBuilder(user.ID)
builder.WithEntryID(entryID)
builder.WithoutStatus(model.EntryStatusRemoved)
@ -40,7 +39,7 @@ func (c *Controller) ShowReadEntry(w http.ResponseWriter, r *http.Request) {
return
}
entryPaginationBuilder := storage.NewEntryPaginationBuilder(c.store, user.ID, entry.ID, user.EntryDirection)
entryPaginationBuilder := storage.NewEntryPaginationBuilder(h.store, user.ID, entry.ID, user.EntryDirection)
entryPaginationBuilder.WithStatus(model.EntryStatusRead)
prevEntry, nextEntry, err := entryPaginationBuilder.Entries()
if err != nil {
@ -50,16 +49,16 @@ func (c *Controller) ShowReadEntry(w http.ResponseWriter, r *http.Request) {
nextEntryRoute := ""
if nextEntry != nil {
nextEntryRoute = route.Path(c.router, "readEntry", "entryID", nextEntry.ID)
nextEntryRoute = route.Path(h.router, "readEntry", "entryID", nextEntry.ID)
}
prevEntryRoute := ""
if prevEntry != nil {
prevEntryRoute = route.Path(c.router, "readEntry", "entryID", prevEntry.ID)
prevEntryRoute = route.Path(h.router, "readEntry", "entryID", prevEntry.ID)
}
sess := session.New(c.store, request.SessionID(r))
view := view.New(c.tpl, r, sess)
sess := session.New(h.store, request.SessionID(r))
view := view.New(h.tpl, r, sess)
view.Set("entry", entry)
view.Set("prevEntry", prevEntry)
view.Set("nextEntry", nextEntry)
@ -67,9 +66,9 @@ func (c *Controller) ShowReadEntry(w http.ResponseWriter, r *http.Request) {
view.Set("prevEntryRoute", prevEntryRoute)
view.Set("menu", "history")
view.Set("user", user)
view.Set("countUnread", c.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", c.store.CountErrorFeeds(user.ID))
view.Set("hasSaveEntry", c.store.HasSaveEntry(user.ID))
view.Set("countUnread", h.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", h.store.CountErrorFeeds(user.ID))
view.Set("hasSaveEntry", h.store.HasSaveEntry(user.ID))
html.OK(w, r, view.Render("entry"))
}

View File

@ -13,10 +13,9 @@ import (
"miniflux.app/model"
)
// SaveEntry send the link to external services.
func (c *Controller) SaveEntry(w http.ResponseWriter, r *http.Request) {
func (h *handler) saveEntry(w http.ResponseWriter, r *http.Request) {
entryID := request.RouteInt64Param(r, "entryID")
builder := c.store.NewEntryQueryBuilder(request.UserID(r))
builder := h.store.NewEntryQueryBuilder(request.UserID(r))
builder.WithEntryID(entryID)
builder.WithoutStatus(model.EntryStatusRemoved)
@ -31,14 +30,14 @@ func (c *Controller) SaveEntry(w http.ResponseWriter, r *http.Request) {
return
}
settings, err := c.store.Integration(request.UserID(r))
settings, err := h.store.Integration(request.UserID(r))
if err != nil {
json.ServerError(w, r, err)
return
}
go func() {
integration.SendEntry(c.cfg, entry, settings)
integration.SendEntry(h.cfg, entry, settings)
}()
json.Created(w, r, map[string]string{"message": "saved"})

View File

@ -14,10 +14,9 @@ import (
"miniflux.app/reader/scraper"
)
// FetchContent downloads the original HTML page and returns relevant contents.
func (c *Controller) FetchContent(w http.ResponseWriter, r *http.Request) {
func (h *handler) fetchContent(w http.ResponseWriter, r *http.Request) {
entryID := request.RouteInt64Param(r, "entryID")
builder := c.store.NewEntryQueryBuilder(request.UserID(r))
builder := h.store.NewEntryQueryBuilder(request.UserID(r))
builder.WithEntryID(entryID)
builder.WithoutStatus(model.EntryStatusRemoved)
@ -39,7 +38,7 @@ func (c *Controller) FetchContent(w http.ResponseWriter, r *http.Request) {
}
entry.Content = sanitizer.Sanitize(entry.URL, content)
c.store.UpdateEntryContent(entry)
h.store.UpdateEntryContent(entry)
json.OK(w, r, map[string]string{"content": entry.Content})
}

View File

@ -17,9 +17,8 @@ import (
"miniflux.app/ui/view"
)
// ShowSearchEntry shows a single entry in "search" mode.
func (c *Controller) ShowSearchEntry(w http.ResponseWriter, r *http.Request) {
user, err := c.store.UserByID(request.UserID(r))
func (h *handler) showSearchEntryPage(w http.ResponseWriter, r *http.Request) {
user, err := h.store.UserByID(request.UserID(r))
if err != nil {
html.ServerError(w, r, err)
return
@ -27,7 +26,7 @@ func (c *Controller) ShowSearchEntry(w http.ResponseWriter, r *http.Request) {
entryID := request.RouteInt64Param(r, "entryID")
searchQuery := request.QueryStringParam(r, "q", "")
builder := c.store.NewEntryQueryBuilder(user.ID)
builder := h.store.NewEntryQueryBuilder(user.ID)
builder.WithSearchQuery(searchQuery)
builder.WithEntryID(entryID)
builder.WithoutStatus(model.EntryStatusRemoved)
@ -44,7 +43,7 @@ func (c *Controller) ShowSearchEntry(w http.ResponseWriter, r *http.Request) {
}
if entry.Status == model.EntryStatusUnread {
err = c.store.SetEntriesStatus(user.ID, []int64{entry.ID}, model.EntryStatusRead)
err = h.store.SetEntriesStatus(user.ID, []int64{entry.ID}, model.EntryStatusRead)
if err != nil {
logger.Error("[Controller:ShowSearchEntry] %v", err)
html.ServerError(w, r, err)
@ -54,7 +53,7 @@ func (c *Controller) ShowSearchEntry(w http.ResponseWriter, r *http.Request) {
entry.Status = model.EntryStatusRead
}
entryPaginationBuilder := storage.NewEntryPaginationBuilder(c.store, user.ID, entry.ID, user.EntryDirection)
entryPaginationBuilder := storage.NewEntryPaginationBuilder(h.store, user.ID, entry.ID, user.EntryDirection)
entryPaginationBuilder.WithSearchQuery(searchQuery)
prevEntry, nextEntry, err := entryPaginationBuilder.Entries()
if err != nil {
@ -64,16 +63,16 @@ func (c *Controller) ShowSearchEntry(w http.ResponseWriter, r *http.Request) {
nextEntryRoute := ""
if nextEntry != nil {
nextEntryRoute = route.Path(c.router, "searchEntry", "entryID", nextEntry.ID)
nextEntryRoute = route.Path(h.router, "searchEntry", "entryID", nextEntry.ID)
}
prevEntryRoute := ""
if prevEntry != nil {
prevEntryRoute = route.Path(c.router, "searchEntry", "entryID", prevEntry.ID)
prevEntryRoute = route.Path(h.router, "searchEntry", "entryID", prevEntry.ID)
}
sess := session.New(c.store, request.SessionID(r))
view := view.New(c.tpl, r, sess)
sess := session.New(h.store, request.SessionID(r))
view := view.New(h.tpl, r, sess)
view.Set("searchQuery", searchQuery)
view.Set("entry", entry)
view.Set("prevEntry", prevEntry)
@ -82,9 +81,9 @@ func (c *Controller) ShowSearchEntry(w http.ResponseWriter, r *http.Request) {
view.Set("prevEntryRoute", prevEntryRoute)
view.Set("menu", "search")
view.Set("user", user)
view.Set("countUnread", c.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", c.store.CountErrorFeeds(user.ID))
view.Set("hasSaveEntry", c.store.HasSaveEntry(user.ID))
view.Set("countUnread", h.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", h.store.CountErrorFeeds(user.ID))
view.Set("hasSaveEntry", h.store.HasSaveEntry(user.ID))
html.OK(w, r, view.Render("entry"))
}

View File

@ -11,10 +11,9 @@ import (
"miniflux.app/http/response/json"
)
// ToggleBookmark handles Ajax request to toggle bookmark value.
func (c *Controller) ToggleBookmark(w http.ResponseWriter, r *http.Request) {
func (h *handler) toggleBookmark(w http.ResponseWriter, r *http.Request) {
entryID := request.RouteInt64Param(r, "entryID")
if err := c.store.ToggleBookmark(request.UserID(r), entryID); err != nil {
if err := h.store.ToggleBookmark(request.UserID(r), entryID); err != nil {
json.ServerError(w, r, err)
return
}

View File

@ -16,16 +16,15 @@ import (
"miniflux.app/ui/view"
)
// ShowUnreadEntry shows a single feed entry in "unread" mode.
func (c *Controller) ShowUnreadEntry(w http.ResponseWriter, r *http.Request) {
user, err := c.store.UserByID(request.UserID(r))
func (h *handler) showUnreadEntryPage(w http.ResponseWriter, r *http.Request) {
user, err := h.store.UserByID(request.UserID(r))
if err != nil {
html.ServerError(w, r, err)
return
}
entryID := request.RouteInt64Param(r, "entryID")
builder := c.store.NewEntryQueryBuilder(user.ID)
builder := h.store.NewEntryQueryBuilder(user.ID)
builder.WithEntryID(entryID)
builder.WithoutStatus(model.EntryStatusRemoved)
@ -42,14 +41,14 @@ func (c *Controller) ShowUnreadEntry(w http.ResponseWriter, r *http.Request) {
// Make sure we always get the pagination in unread mode even if the page is refreshed.
if entry.Status == model.EntryStatusRead {
err = c.store.SetEntriesStatus(user.ID, []int64{entry.ID}, model.EntryStatusUnread)
err = h.store.SetEntriesStatus(user.ID, []int64{entry.ID}, model.EntryStatusUnread)
if err != nil {
html.ServerError(w, r, err)
return
}
}
entryPaginationBuilder := storage.NewEntryPaginationBuilder(c.store, user.ID, entry.ID, user.EntryDirection)
entryPaginationBuilder := storage.NewEntryPaginationBuilder(h.store, user.ID, entry.ID, user.EntryDirection)
entryPaginationBuilder.WithStatus(model.EntryStatusUnread)
prevEntry, nextEntry, err := entryPaginationBuilder.Entries()
if err != nil {
@ -59,24 +58,24 @@ func (c *Controller) ShowUnreadEntry(w http.ResponseWriter, r *http.Request) {
nextEntryRoute := ""
if nextEntry != nil {
nextEntryRoute = route.Path(c.router, "unreadEntry", "entryID", nextEntry.ID)
nextEntryRoute = route.Path(h.router, "unreadEntry", "entryID", nextEntry.ID)
}
prevEntryRoute := ""
if prevEntry != nil {
prevEntryRoute = route.Path(c.router, "unreadEntry", "entryID", prevEntry.ID)
prevEntryRoute = route.Path(h.router, "unreadEntry", "entryID", prevEntry.ID)
}
// Always mark the entry as read after fetching the pagination.
err = c.store.SetEntriesStatus(user.ID, []int64{entry.ID}, model.EntryStatusRead)
err = h.store.SetEntriesStatus(user.ID, []int64{entry.ID}, model.EntryStatusRead)
if err != nil {
html.ServerError(w, r, err)
return
}
entry.Status = model.EntryStatusRead
sess := session.New(c.store, request.SessionID(r))
view := view.New(c.tpl, r, sess)
sess := session.New(h.store, request.SessionID(r))
view := view.New(h.tpl, r, sess)
view.Set("entry", entry)
view.Set("prevEntry", prevEntry)
view.Set("nextEntry", nextEntry)
@ -84,11 +83,11 @@ func (c *Controller) ShowUnreadEntry(w http.ResponseWriter, r *http.Request) {
view.Set("prevEntryRoute", prevEntryRoute)
view.Set("menu", "unread")
view.Set("user", user)
view.Set("hasSaveEntry", c.store.HasSaveEntry(user.ID))
view.Set("countErrorFeeds", c.store.CountErrorFeeds(user.ID))
view.Set("hasSaveEntry", h.store.HasSaveEntry(user.ID))
view.Set("countErrorFeeds", h.store.CountErrorFeeds(user.ID))
// Fetching the counter here avoid to be off by one.
view.Set("countUnread", c.store.CountUnreadEntries(user.ID))
view.Set("countUnread", h.store.CountUnreadEntries(user.ID))
html.OK(w, r, view.Render("entry"))
}

View File

@ -12,8 +12,7 @@ import (
"miniflux.app/http/response/json"
)
// UpdateEntriesStatus updates the status for a list of entries.
func (c *Controller) UpdateEntriesStatus(w http.ResponseWriter, r *http.Request) {
func (h *handler) updateEntriesStatus(w http.ResponseWriter, r *http.Request) {
entryIDs, status, err := decodeEntryStatusPayload(r.Body)
if err != nil {
json.BadRequest(w, r, err)
@ -25,7 +24,7 @@ func (c *Controller) UpdateEntriesStatus(w http.ResponseWriter, r *http.Request)
return
}
err = c.store.SetEntriesStatus(request.UserID(r), entryIDs, status)
err = h.store.SetEntriesStatus(request.UserID(r), entryIDs, status)
if err != nil {
json.ServerError(w, r, err)
return

View File

@ -15,16 +15,15 @@ import (
"miniflux.app/ui/view"
)
// EditFeed shows the form to modify a subscription.
func (c *Controller) EditFeed(w http.ResponseWriter, r *http.Request) {
user, err := c.store.UserByID(request.UserID(r))
func (h *handler) showEditFeedPage(w http.ResponseWriter, r *http.Request) {
user, err := h.store.UserByID(request.UserID(r))
if err != nil {
html.ServerError(w, r, err)
return
}
feedID := request.RouteInt64Param(r, "feedID")
feed, err := c.store.FeedByID(user.ID, feedID)
feed, err := h.store.FeedByID(user.ID, feedID)
if err != nil {
html.ServerError(w, r, err)
return
@ -35,7 +34,7 @@ func (c *Controller) EditFeed(w http.ResponseWriter, r *http.Request) {
return
}
categories, err := c.store.Categories(user.ID)
categories, err := h.store.Categories(user.ID)
if err != nil {
html.ServerError(w, r, err)
return
@ -54,15 +53,15 @@ func (c *Controller) EditFeed(w http.ResponseWriter, r *http.Request) {
Password: feed.Password,
}
sess := session.New(c.store, request.SessionID(r))
view := view.New(c.tpl, r, sess)
sess := session.New(h.store, request.SessionID(r))
view := view.New(h.tpl, r, sess)
view.Set("form", feedForm)
view.Set("categories", categories)
view.Set("feed", feed)
view.Set("menu", "feeds")
view.Set("user", user)
view.Set("countUnread", c.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", c.store.CountErrorFeeds(user.ID))
view.Set("countUnread", h.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", h.store.CountErrorFeeds(user.ID))
view.Set("defaultUserAgent", client.DefaultUserAgent)
html.OK(w, r, view.Render("edit_feed"))

View File

@ -15,16 +15,15 @@ import (
"miniflux.app/ui/view"
)
// ShowFeedEntries shows all entries for the given feed.
func (c *Controller) ShowFeedEntries(w http.ResponseWriter, r *http.Request) {
user, err := c.store.UserByID(request.UserID(r))
func (h *handler) showFeedEntriesPage(w http.ResponseWriter, r *http.Request) {
user, err := h.store.UserByID(request.UserID(r))
if err != nil {
html.ServerError(w, r, err)
return
}
feedID := request.RouteInt64Param(r, "feedID")
feed, err := c.store.FeedByID(user.ID, feedID)
feed, err := h.store.FeedByID(user.ID, feedID)
if err != nil {
html.ServerError(w, r, err)
return
@ -36,7 +35,7 @@ func (c *Controller) ShowFeedEntries(w http.ResponseWriter, r *http.Request) {
}
offset := request.QueryIntParam(r, "offset", 0)
builder := c.store.NewEntryQueryBuilder(user.ID)
builder := h.store.NewEntryQueryBuilder(user.ID)
builder.WithFeedID(feed.ID)
builder.WithoutStatus(model.EntryStatusRemoved)
builder.WithOrder(model.DefaultSortingOrder)
@ -56,17 +55,17 @@ func (c *Controller) ShowFeedEntries(w http.ResponseWriter, r *http.Request) {
return
}
sess := session.New(c.store, request.SessionID(r))
view := view.New(c.tpl, r, sess)
sess := session.New(h.store, request.SessionID(r))
view := view.New(h.tpl, r, sess)
view.Set("feed", feed)
view.Set("entries", entries)
view.Set("total", count)
view.Set("pagination", c.getPagination(route.Path(c.router, "feedEntries", "feedID", feed.ID), count, offset))
view.Set("pagination", getPagination(route.Path(h.router, "feedEntries", "feedID", feed.ID), count, offset))
view.Set("menu", "feeds")
view.Set("user", user)
view.Set("countUnread", c.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", c.store.CountErrorFeeds(user.ID))
view.Set("hasSaveEntry", c.store.HasSaveEntry(user.ID))
view.Set("countUnread", h.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", h.store.CountErrorFeeds(user.ID))
view.Set("hasSaveEntry", h.store.HasSaveEntry(user.ID))
html.OK(w, r, view.Render("feed_entries"))
}

View File

@ -13,10 +13,9 @@ import (
"miniflux.app/http/response/html"
)
// ShowIcon shows the feed icon.
func (c *Controller) ShowIcon(w http.ResponseWriter, r *http.Request) {
func (h *handler) showIcon(w http.ResponseWriter, r *http.Request) {
iconID := request.RouteInt64Param(r, "iconID")
icon, err := c.store.IconByID(iconID)
icon, err := h.store.IconByID(iconID)
if err != nil {
html.ServerError(w, r, err)
return

View File

@ -13,28 +13,27 @@ import (
"miniflux.app/ui/view"
)
// ShowFeedsPage shows the page with all subscriptions.
func (c *Controller) ShowFeedsPage(w http.ResponseWriter, r *http.Request) {
user, err := c.store.UserByID(request.UserID(r))
func (h *handler) showFeedsPage(w http.ResponseWriter, r *http.Request) {
user, err := h.store.UserByID(request.UserID(r))
if err != nil {
html.ServerError(w, r, err)
return
}
feeds, err := c.store.Feeds(user.ID)
feeds, err := h.store.Feeds(user.ID)
if err != nil {
html.ServerError(w, r, err)
return
}
sess := session.New(c.store, request.SessionID(r))
view := view.New(c.tpl, r, sess)
sess := session.New(h.store, request.SessionID(r))
view := view.New(h.tpl, r, sess)
view.Set("feeds", feeds)
view.Set("total", len(feeds))
view.Set("menu", "feeds")
view.Set("user", user)
view.Set("countUnread", c.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", c.store.CountErrorFeeds(user.ID))
view.Set("countUnread", h.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", h.store.CountErrorFeeds(user.ID))
html.OK(w, r, view.Render("feeds"))
}

View File

@ -13,28 +13,26 @@ import (
"miniflux.app/logger"
)
// RefreshFeed refresh a subscription and redirect to the feed entries page.
func (c *Controller) RefreshFeed(w http.ResponseWriter, r *http.Request) {
func (h *handler) refreshFeed(w http.ResponseWriter, r *http.Request) {
feedID := request.RouteInt64Param(r, "feedID")
if err := c.feedHandler.RefreshFeed(request.UserID(r), feedID); err != nil {
if err := h.feedHandler.RefreshFeed(request.UserID(r), feedID); err != nil {
logger.Error("[Controller:RefreshFeed] %v", err)
}
html.Redirect(w, r, route.Path(c.router, "feedEntries", "feedID", feedID))
html.Redirect(w, r, route.Path(h.router, "feedEntries", "feedID", feedID))
}
// RefreshAllFeeds refresh all feeds in the background for the current user.
func (c *Controller) RefreshAllFeeds(w http.ResponseWriter, r *http.Request) {
func (h *handler) refreshAllFeeds(w http.ResponseWriter, r *http.Request) {
userID := request.UserID(r)
jobs, err := c.store.NewUserBatch(userID, c.store.CountFeeds(userID))
jobs, err := h.store.NewUserBatch(userID, h.store.CountFeeds(userID))
if err != nil {
html.ServerError(w, r, err)
return
}
go func() {
c.pool.Push(jobs)
h.pool.Push(jobs)
}()
html.Redirect(w, r, route.Path(c.router, "feeds"))
html.Redirect(w, r, route.Path(h.router, "feeds"))
}

View File

@ -12,13 +12,12 @@ import (
"miniflux.app/http/route"
)
// RemoveFeed deletes a subscription from the database and redirect to the list of feeds page.
func (c *Controller) RemoveFeed(w http.ResponseWriter, r *http.Request) {
func (h *handler) removeFeed(w http.ResponseWriter, r *http.Request) {
feedID := request.RouteInt64Param(r, "feedID")
if err := c.store.RemoveFeed(request.UserID(r), feedID); err != nil {
if err := h.store.RemoveFeed(request.UserID(r), feedID); err != nil {
html.ServerError(w, r, err)
return
}
html.Redirect(w, r, route.Path(c.router, "feeds"))
html.Redirect(w, r, route.Path(h.router, "feeds"))
}

View File

@ -17,16 +17,15 @@ import (
"miniflux.app/ui/view"
)
// UpdateFeed update a subscription and redirect to the feed entries page.
func (c *Controller) UpdateFeed(w http.ResponseWriter, r *http.Request) {
user, err := c.store.UserByID(request.UserID(r))
func (h *handler) updateFeed(w http.ResponseWriter, r *http.Request) {
user, err := h.store.UserByID(request.UserID(r))
if err != nil {
html.ServerError(w, r, err)
return
}
feedID := request.RouteInt64Param(r, "feedID")
feed, err := c.store.FeedByID(user.ID, feedID)
feed, err := h.store.FeedByID(user.ID, feedID)
if err != nil {
html.ServerError(w, r, err)
return
@ -37,7 +36,7 @@ func (c *Controller) UpdateFeed(w http.ResponseWriter, r *http.Request) {
return
}
categories, err := c.store.Categories(user.ID)
categories, err := h.store.Categories(user.ID)
if err != nil {
html.ServerError(w, r, err)
return
@ -45,15 +44,15 @@ func (c *Controller) UpdateFeed(w http.ResponseWriter, r *http.Request) {
feedForm := form.NewFeedForm(r)
sess := session.New(c.store, request.SessionID(r))
view := view.New(c.tpl, r, sess)
sess := session.New(h.store, request.SessionID(r))
view := view.New(h.tpl, r, sess)
view.Set("form", feedForm)
view.Set("categories", categories)
view.Set("feed", feed)
view.Set("menu", "feeds")
view.Set("user", user)
view.Set("countUnread", c.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", c.store.CountErrorFeeds(user.ID))
view.Set("countUnread", h.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", h.store.CountErrorFeeds(user.ID))
view.Set("defaultUserAgent", client.DefaultUserAgent)
if err := feedForm.ValidateModification(); err != nil {
@ -62,7 +61,7 @@ func (c *Controller) UpdateFeed(w http.ResponseWriter, r *http.Request) {
return
}
err = c.store.UpdateFeed(feedForm.Merge(feed))
err = h.store.UpdateFeed(feedForm.Merge(feed))
if err != nil {
logger.Error("[Controller:EditFeed] %v", err)
view.Set("errorMessage", "error.unable_to_update_feed")
@ -70,5 +69,5 @@ func (c *Controller) UpdateFeed(w http.ResponseWriter, r *http.Request) {
return
}
html.Redirect(w, r, route.Path(c.router, "feedEntries", "feedID", feed.ID))
html.Redirect(w, r, route.Path(h.router, "feedEntries", "feedID", feed.ID))
}

View File

@ -14,24 +14,11 @@ import (
"github.com/gorilla/mux"
)
// Controller contains all HTTP handlers for the user interface.
type Controller struct {
type handler struct {
router *mux.Router
cfg *config.Config
store *storage.Storage
tpl *template.Engine
pool *scheduler.WorkerPool
feedHandler *feed.Handler
tpl *template.Engine
router *mux.Router
}
// NewController returns a new Controller.
func NewController(cfg *config.Config, store *storage.Storage, pool *scheduler.WorkerPool, feedHandler *feed.Handler, tpl *template.Engine, router *mux.Router) *Controller {
return &Controller{
cfg: cfg,
store: store,
pool: pool,
feedHandler: feedHandler,
tpl: tpl,
router: router,
}
}

View File

@ -15,16 +15,15 @@ import (
"miniflux.app/ui/view"
)
// ShowHistoryPage renders the page with all read entries.
func (c *Controller) ShowHistoryPage(w http.ResponseWriter, r *http.Request) {
user, err := c.store.UserByID(request.UserID(r))
func (h *handler) showHistoryPage(w http.ResponseWriter, r *http.Request) {
user, err := h.store.UserByID(request.UserID(r))
if err != nil {
html.ServerError(w, r, err)
return
}
offset := request.QueryIntParam(r, "offset", 0)
builder := c.store.NewEntryQueryBuilder(user.ID)
builder := h.store.NewEntryQueryBuilder(user.ID)
builder.WithStatus(model.EntryStatusRead)
builder.WithOrder(model.DefaultSortingOrder)
builder.WithDirection(user.EntryDirection)
@ -43,16 +42,16 @@ func (c *Controller) ShowHistoryPage(w http.ResponseWriter, r *http.Request) {
return
}
sess := session.New(c.store, request.SessionID(r))
view := view.New(c.tpl, r, sess)
sess := session.New(h.store, request.SessionID(r))
view := view.New(h.tpl, r, sess)
view.Set("entries", entries)
view.Set("total", count)
view.Set("pagination", c.getPagination(route.Path(c.router, "history"), count, offset))
view.Set("pagination", getPagination(route.Path(h.router, "history"), count, offset))
view.Set("menu", "history")
view.Set("user", user)
view.Set("countUnread", c.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", c.store.CountErrorFeeds(user.ID))
view.Set("hasSaveEntry", c.store.HasSaveEntry(user.ID))
view.Set("countUnread", h.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", h.store.CountErrorFeeds(user.ID))
view.Set("hasSaveEntry", h.store.HasSaveEntry(user.ID))
html.OK(w, r, view.Render("history_entries"))
}

View File

@ -12,13 +12,12 @@ import (
"miniflux.app/http/route"
)
// FlushHistory changes all "read" items to "removed".
func (c *Controller) FlushHistory(w http.ResponseWriter, r *http.Request) {
err := c.store.FlushHistory(request.UserID(r))
func (h *handler) flushHistory(w http.ResponseWriter, r *http.Request) {
err := h.store.FlushHistory(request.UserID(r))
if err != nil {
html.ServerError(w, r, err)
return
}
html.Redirect(w, r, route.Path(c.router, "history"))
html.Redirect(w, r, route.Path(h.router, "history"))
}

View File

@ -16,29 +16,28 @@ import (
"miniflux.app/ui/session"
)
// PocketAuthorize redirects the end-user to Pocket website to authorize the application.
func (c *Controller) PocketAuthorize(w http.ResponseWriter, r *http.Request) {
func (h *handler) pocketAuthorize(w http.ResponseWriter, r *http.Request) {
printer := locale.NewPrinter(request.UserLanguage(r))
user, err := c.store.UserByID(request.UserID(r))
user, err := h.store.UserByID(request.UserID(r))
if err != nil {
html.ServerError(w, r, err)
return
}
integration, err := c.store.Integration(user.ID)
integration, err := h.store.Integration(user.ID)
if err != nil {
html.ServerError(w, r, err)
return
}
sess := session.New(c.store, request.SessionID(r))
connector := pocket.NewConnector(c.cfg.PocketConsumerKey(integration.PocketConsumerKey))
redirectURL := c.cfg.BaseURL() + route.Path(c.router, "pocketCallback")
sess := session.New(h.store, request.SessionID(r))
connector := pocket.NewConnector(h.cfg.PocketConsumerKey(integration.PocketConsumerKey))
redirectURL := h.cfg.BaseURL() + route.Path(h.router, "pocketCallback")
requestToken, err := connector.RequestToken(redirectURL)
if err != nil {
logger.Error("[Pocket:Authorize] %v", err)
sess.NewFlashErrorMessage(printer.Printf("error.pocket_request_token"))
html.Redirect(w, r, route.Path(c.router, "integrations"))
html.Redirect(w, r, route.Path(h.router, "integrations"))
return
}
@ -46,41 +45,40 @@ func (c *Controller) PocketAuthorize(w http.ResponseWriter, r *http.Request) {
html.Redirect(w, r, connector.AuthorizationURL(requestToken, redirectURL))
}
// PocketCallback saves the personal access token after the authorization step.
func (c *Controller) PocketCallback(w http.ResponseWriter, r *http.Request) {
func (h *handler) pocketCallback(w http.ResponseWriter, r *http.Request) {
printer := locale.NewPrinter(request.UserLanguage(r))
sess := session.New(c.store, request.SessionID(r))
sess := session.New(h.store, request.SessionID(r))
user, err := c.store.UserByID(request.UserID(r))
user, err := h.store.UserByID(request.UserID(r))
if err != nil {
html.ServerError(w, r, err)
return
}
integration, err := c.store.Integration(user.ID)
integration, err := h.store.Integration(user.ID)
if err != nil {
html.ServerError(w, r, err)
return
}
connector := pocket.NewConnector(c.cfg.PocketConsumerKey(integration.PocketConsumerKey))
connector := pocket.NewConnector(h.cfg.PocketConsumerKey(integration.PocketConsumerKey))
accessToken, err := connector.AccessToken(request.PocketRequestToken(r))
if err != nil {
logger.Error("[Pocket:Callback] %v", err)
sess.NewFlashErrorMessage(printer.Printf("error.pocket_access_token"))
html.Redirect(w, r, route.Path(c.router, "integrations"))
html.Redirect(w, r, route.Path(h.router, "integrations"))
return
}
sess.SetPocketRequestToken("")
integration.PocketAccessToken = accessToken
err = c.store.UpdateIntegration(integration)
err = h.store.UpdateIntegration(integration)
if err != nil {
html.ServerError(w, r, err)
return
}
sess.NewFlashMessage(printer.Printf("alert.pocket_linked"))
html.Redirect(w, r, route.Path(c.router, "integrations"))
html.Redirect(w, r, route.Path(h.router, "integrations"))
}

View File

@ -14,15 +14,14 @@ import (
"miniflux.app/ui/view"
)
// ShowIntegrations renders the page with all external integrations.
func (c *Controller) ShowIntegrations(w http.ResponseWriter, r *http.Request) {
user, err := c.store.UserByID(request.UserID(r))
func (h *handler) showIntegrationPage(w http.ResponseWriter, r *http.Request) {
user, err := h.store.UserByID(request.UserID(r))
if err != nil {
html.ServerError(w, r, err)
return
}
integration, err := c.store.Integration(user.ID)
integration, err := h.store.Integration(user.ID)
if err != nil {
html.ServerError(w, r, err)
return
@ -53,14 +52,14 @@ func (c *Controller) ShowIntegrations(w http.ResponseWriter, r *http.Request) {
PocketConsumerKey: integration.PocketConsumerKey,
}
sess := session.New(c.store, request.SessionID(r))
view := view.New(c.tpl, r, sess)
sess := session.New(h.store, request.SessionID(r))
view := view.New(h.tpl, r, sess)
view.Set("form", integrationForm)
view.Set("menu", "settings")
view.Set("user", user)
view.Set("countUnread", c.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", c.store.CountErrorFeeds(user.ID))
view.Set("hasPocketConsumerKeyConfigured", c.cfg.PocketConsumerKey("") != "")
view.Set("countUnread", h.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", h.store.CountErrorFeeds(user.ID))
view.Set("hasPocketConsumerKeyConfigured", h.cfg.PocketConsumerKey("") != "")
html.OK(w, r, view.Render("integrations"))
}

View File

@ -17,17 +17,16 @@ import (
"miniflux.app/ui/session"
)
// UpdateIntegration updates integration settings.
func (c *Controller) UpdateIntegration(w http.ResponseWriter, r *http.Request) {
func (h *handler) updateIntegration(w http.ResponseWriter, r *http.Request) {
printer := locale.NewPrinter(request.UserLanguage(r))
sess := session.New(c.store, request.SessionID(r))
user, err := c.store.UserByID(request.UserID(r))
sess := session.New(h.store, request.SessionID(r))
user, err := h.store.UserByID(request.UserID(r))
if err != nil {
html.ServerError(w, r, err)
return
}
integration, err := c.store.Integration(user.ID)
integration, err := h.store.Integration(user.ID)
if err != nil {
html.ServerError(w, r, err)
return
@ -36,9 +35,9 @@ func (c *Controller) UpdateIntegration(w http.ResponseWriter, r *http.Request) {
integrationForm := form.NewIntegrationForm(r)
integrationForm.Merge(integration)
if integration.FeverUsername != "" && c.store.HasDuplicateFeverUsername(user.ID, integration.FeverUsername) {
if integration.FeverUsername != "" && h.store.HasDuplicateFeverUsername(user.ID, integration.FeverUsername) {
sess.NewFlashErrorMessage(printer.Printf("error.duplicate_fever_username"))
html.Redirect(w, r, route.Path(c.router, "integrations"))
html.Redirect(w, r, route.Path(h.router, "integrations"))
return
}
@ -48,12 +47,12 @@ func (c *Controller) UpdateIntegration(w http.ResponseWriter, r *http.Request) {
integration.FeverToken = ""
}
err = c.store.UpdateIntegration(integration)
err = h.store.UpdateIntegration(integration)
if err != nil {
html.ServerError(w, r, err)
return
}
sess.NewFlashMessage(printer.Printf("alert.prefs_saved"))
html.Redirect(w, r, route.Path(c.router, "integrations"))
html.Redirect(w, r, route.Path(h.router, "integrations"))
}

View File

@ -13,13 +13,12 @@ import (
"miniflux.app/ui/view"
)
// CheckLogin validates the username/password and redirects the user to the unread page.
func (c *Controller) CheckLogin(w http.ResponseWriter, r *http.Request) {
func (h *handler) checkLogin(w http.ResponseWriter, r *http.Request) {
clientIP := request.ClientIP(r)
sess := session.New(c.store, request.SessionID(r))
sess := session.New(h.store, request.SessionID(r))
authForm := form.NewAuthForm(r)
view := view.New(c.tpl, r, sess)
view := view.New(h.tpl, r, sess)
view.Set("errorMessage", "error.bad_credentials")
view.Set("form", authForm)
@ -29,22 +28,22 @@ func (c *Controller) CheckLogin(w http.ResponseWriter, r *http.Request) {
return
}
if err := c.store.CheckPassword(authForm.Username, authForm.Password); err != nil {
if err := h.store.CheckPassword(authForm.Username, authForm.Password); err != nil {
logger.Error("[Controller:CheckLogin] [ClientIP=%s] %v", clientIP, err)
html.OK(w, r, view.Render("login"))
return
}
sessionToken, userID, err := c.store.CreateUserSession(authForm.Username, r.UserAgent(), clientIP)
sessionToken, userID, err := h.store.CreateUserSession(authForm.Username, r.UserAgent(), clientIP)
if err != nil {
html.ServerError(w, r, err)
return
}
logger.Info("[Controller:CheckLogin] username=%s just logged in", authForm.Username)
c.store.SetLastLogin(userID)
h.store.SetLastLogin(userID)
user, err := c.store.UserByID(userID)
user, err := h.store.UserByID(userID)
if err != nil {
html.ServerError(w, r, err)
return
@ -56,9 +55,9 @@ func (c *Controller) CheckLogin(w http.ResponseWriter, r *http.Request) {
http.SetCookie(w, cookie.New(
cookie.CookieUserSessionID,
sessionToken,
c.cfg.IsHTTPS,
c.cfg.BasePath(),
h.cfg.IsHTTPS,
h.cfg.BasePath(),
))
html.Redirect(w, r, route.Path(c.router, "unread"))
html.Redirect(w, r, route.Path(h.router, "unread"))
}

View File

@ -14,14 +14,13 @@ import (
"miniflux.app/ui/view"
)
// ShowLoginPage shows the login form.
func (c *Controller) ShowLoginPage(w http.ResponseWriter, r *http.Request) {
func (h *handler) showLoginPage(w http.ResponseWriter, r *http.Request) {
if request.IsAuthenticated(r) {
html.Redirect(w, r, route.Path(c.router, "unread"))
html.Redirect(w, r, route.Path(h.router, "unread"))
return
}
sess := session.New(c.store, request.SessionID(r))
view := view.New(c.tpl, r, sess)
sess := session.New(h.store, request.SessionID(r))
view := view.New(h.tpl, r, sess)
html.OK(w, r, view.Render("login"))
}

View File

@ -15,10 +15,9 @@ import (
"miniflux.app/ui/session"
)
// Logout destroy the session and redirects the user to the login page.
func (c *Controller) Logout(w http.ResponseWriter, r *http.Request) {
sess := session.New(c.store, request.SessionID(r))
user, err := c.store.UserByID(request.UserID(r))
func (h *handler) logout(w http.ResponseWriter, r *http.Request) {
sess := session.New(h.store, request.SessionID(r))
user, err := h.store.UserByID(request.UserID(r))
if err != nil {
html.ServerError(w, r, err)
return
@ -27,15 +26,15 @@ func (c *Controller) Logout(w http.ResponseWriter, r *http.Request) {
sess.SetLanguage(user.Language)
sess.SetTheme(user.Theme)
if err := c.store.RemoveUserSessionByToken(user.ID, request.UserSessionToken(r)); err != nil {
if err := h.store.RemoveUserSessionByToken(user.ID, request.UserSessionToken(r)); err != nil {
logger.Error("[Controller:Logout] %v", err)
}
http.SetCookie(w, cookie.Expired(
cookie.CookieUserSessionID,
c.cfg.IsHTTPS,
c.cfg.BasePath(),
h.cfg.IsHTTPS,
h.cfg.BasePath(),
))
html.Redirect(w, r, route.Path(c.router, "login"))
html.Redirect(w, r, route.Path(h.router, "login"))
}

149
ui/middleware.go Normal file
View File

@ -0,0 +1,149 @@
// Copyright 2018 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
package ui // import "miniflux.app/ui"
import (
"context"
"errors"
"net/http"
"miniflux.app/config"
"miniflux.app/http/cookie"
"miniflux.app/http/request"
"miniflux.app/http/response/html"
"miniflux.app/http/route"
"miniflux.app/storage"
"miniflux.app/logger"
"miniflux.app/model"
"github.com/gorilla/mux"
)
type middleware struct {
router *mux.Router
cfg *config.Config
store *storage.Storage
}
func newMiddleware(router *mux.Router, cfg *config.Config, store *storage.Storage) *middleware {
return &middleware{router, cfg, store}
}
func (m *middleware) handleUserSession(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
session := m.getUserSessionFromCookie(r)
if session == nil {
logger.Debug("[UserSession] Session not found")
if m.isPublicRoute(r) {
next.ServeHTTP(w, r)
} else {
html.Redirect(w, r, route.Path(m.router, "login"))
}
} else {
logger.Debug("[UserSession] %s", session)
ctx := r.Context()
ctx = context.WithValue(ctx, request.UserIDContextKey, session.UserID)
ctx = context.WithValue(ctx, request.IsAuthenticatedContextKey, true)
ctx = context.WithValue(ctx, request.UserSessionTokenContextKey, session.Token)
next.ServeHTTP(w, r.WithContext(ctx))
}
})
}
func (m *middleware) handleAppSession(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var err error
session := m.getAppSessionValueFromCookie(r)
if session == nil {
logger.Debug("[AppSession] Session not found")
session, err = m.store.CreateSession()
if err != nil {
html.ServerError(w, r, err)
return
}
http.SetCookie(w, cookie.New(cookie.CookieSessionID, session.ID, m.cfg.IsHTTPS, m.cfg.BasePath()))
} else {
logger.Debug("[AppSession] %s", session)
}
if r.Method == "POST" {
formValue := r.FormValue("csrf")
headerValue := r.Header.Get("X-Csrf-Token")
if session.Data.CSRF != formValue && session.Data.CSRF != headerValue {
logger.Error(`[AppSession] Invalid or missing CSRF token: Form="%s", Header="%s"`, formValue, headerValue)
html.BadRequest(w, r, errors.New("Invalid or missing CSRF"))
return
}
}
ctx := r.Context()
ctx = context.WithValue(ctx, request.SessionIDContextKey, session.ID)
ctx = context.WithValue(ctx, request.CSRFContextKey, session.Data.CSRF)
ctx = context.WithValue(ctx, request.OAuth2StateContextKey, session.Data.OAuth2State)
ctx = context.WithValue(ctx, request.FlashMessageContextKey, session.Data.FlashMessage)
ctx = context.WithValue(ctx, request.FlashErrorMessageContextKey, session.Data.FlashErrorMessage)
ctx = context.WithValue(ctx, request.UserLanguageContextKey, session.Data.Language)
ctx = context.WithValue(ctx, request.UserThemeContextKey, session.Data.Theme)
ctx = context.WithValue(ctx, request.PocketRequestTokenContextKey, session.Data.PocketRequestToken)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func (m *middleware) getAppSessionValueFromCookie(r *http.Request) *model.Session {
cookieValue := request.CookieValue(r, cookie.CookieSessionID)
if cookieValue == "" {
return nil
}
session, err := m.store.Session(cookieValue)
if err != nil {
logger.Error("[AppSession] %v", err)
return nil
}
return session
}
func (m *middleware) isPublicRoute(r *http.Request) bool {
route := mux.CurrentRoute(r)
switch route.GetName() {
case "login",
"checkLogin",
"stylesheet",
"javascript",
"oauth2Redirect",
"oauth2Callback",
"appIcon",
"favicon",
"webManifest",
"robots",
"healthcheck":
return true
default:
return false
}
}
func (m *middleware) getUserSessionFromCookie(r *http.Request) *model.UserSession {
cookieValue := request.CookieValue(r, cookie.CookieUserSessionID)
if cookieValue == "" {
return nil
}
session, err := m.store.UserSessionByToken(cookieValue)
if err != nil {
logger.Error("[UserSession] %v", err)
return nil
}
return session
}

View File

@ -17,51 +17,50 @@ import (
"miniflux.app/ui/session"
)
// OAuth2Callback receives the authorization code and create a new session.
func (c *Controller) OAuth2Callback(w http.ResponseWriter, r *http.Request) {
func (h *handler) oauth2Callback(w http.ResponseWriter, r *http.Request) {
clientIP := request.ClientIP(r)
printer := locale.NewPrinter(request.UserLanguage(r))
sess := session.New(c.store, request.SessionID(r))
sess := session.New(h.store, request.SessionID(r))
provider := request.RouteStringParam(r, "provider")
if provider == "" {
logger.Error("[OAuth2] Invalid or missing provider")
html.Redirect(w, r, route.Path(c.router, "login"))
html.Redirect(w, r, route.Path(h.router, "login"))
return
}
code := request.QueryStringParam(r, "code", "")
if code == "" {
logger.Error("[OAuth2] No code received on callback")
html.Redirect(w, r, route.Path(c.router, "login"))
html.Redirect(w, r, route.Path(h.router, "login"))
return
}
state := request.QueryStringParam(r, "state", "")
if state == "" || state != request.OAuth2State(r) {
logger.Error(`[OAuth2] Invalid state value: got "%s" instead of "%s"`, state, request.OAuth2State(r))
html.Redirect(w, r, route.Path(c.router, "login"))
html.Redirect(w, r, route.Path(h.router, "login"))
return
}
authProvider, err := getOAuth2Manager(c.cfg).Provider(provider)
authProvider, err := getOAuth2Manager(h.cfg).Provider(provider)
if err != nil {
logger.Error("[OAuth2] %v", err)
html.Redirect(w, r, route.Path(c.router, "login"))
html.Redirect(w, r, route.Path(h.router, "login"))
return
}
profile, err := authProvider.GetProfile(code)
if err != nil {
logger.Error("[OAuth2] %v", err)
html.Redirect(w, r, route.Path(c.router, "login"))
html.Redirect(w, r, route.Path(h.router, "login"))
return
}
logger.Info("[OAuth2] [ClientIP=%s] Successful auth for %s", clientIP, profile)
if request.IsAuthenticated(r) {
user, err := c.store.UserByExtraField(profile.Key, profile.ID)
user, err := h.store.UserByExtraField(profile.Key, profile.ID)
if err != nil {
html.ServerError(w, r, err)
return
@ -70,28 +69,28 @@ func (c *Controller) OAuth2Callback(w http.ResponseWriter, r *http.Request) {
if user != nil {
logger.Error("[OAuth2] User #%d cannot be associated because %s is already associated", request.UserID(r), user.Username)
sess.NewFlashErrorMessage(printer.Printf("error.duplicate_linked_account"))
html.Redirect(w, r, route.Path(c.router, "settings"))
html.Redirect(w, r, route.Path(h.router, "settings"))
return
}
if err := c.store.UpdateExtraField(request.UserID(r), profile.Key, profile.ID); err != nil {
if err := h.store.UpdateExtraField(request.UserID(r), profile.Key, profile.ID); err != nil {
html.ServerError(w, r, err)
return
}
sess.NewFlashMessage(printer.Printf("alert.account_linked"))
html.Redirect(w, r, route.Path(c.router, "settings"))
html.Redirect(w, r, route.Path(h.router, "settings"))
return
}
user, err := c.store.UserByExtraField(profile.Key, profile.ID)
user, err := h.store.UserByExtraField(profile.Key, profile.ID)
if err != nil {
html.ServerError(w, r, err)
return
}
if user == nil {
if !c.cfg.IsOAuth2UserCreationAllowed() {
if !h.cfg.IsOAuth2UserCreationAllowed() {
html.Forbidden(w, r)
return
}
@ -101,13 +100,13 @@ func (c *Controller) OAuth2Callback(w http.ResponseWriter, r *http.Request) {
user.IsAdmin = false
user.Extra[profile.Key] = profile.ID
if err := c.store.CreateUser(user); err != nil {
if err := h.store.CreateUser(user); err != nil {
html.ServerError(w, r, err)
return
}
}
sessionToken, _, err := c.store.CreateUserSession(user.Username, r.UserAgent(), clientIP)
sessionToken, _, err := h.store.CreateUserSession(user.Username, r.UserAgent(), clientIP)
if err != nil {
html.ServerError(w, r, err)
return
@ -115,16 +114,16 @@ func (c *Controller) OAuth2Callback(w http.ResponseWriter, r *http.Request) {
logger.Info("[OAuth2] [ClientIP=%s] username=%s (%s) just logged in", clientIP, user.Username, profile)
c.store.SetLastLogin(user.ID)
h.store.SetLastLogin(user.ID)
sess.SetLanguage(user.Language)
sess.SetTheme(user.Theme)
http.SetCookie(w, cookie.New(
cookie.CookieUserSessionID,
sessionToken,
c.cfg.IsHTTPS,
c.cfg.BasePath(),
h.cfg.IsHTTPS,
h.cfg.BasePath(),
))
html.Redirect(w, r, route.Path(c.router, "unread"))
html.Redirect(w, r, route.Path(h.router, "unread"))
}

View File

@ -14,21 +14,20 @@ import (
"miniflux.app/ui/session"
)
// OAuth2Redirect redirects the user to the consent page to ask for permission.
func (c *Controller) OAuth2Redirect(w http.ResponseWriter, r *http.Request) {
sess := session.New(c.store, request.SessionID(r))
func (h *handler) oauth2Redirect(w http.ResponseWriter, r *http.Request) {
sess := session.New(h.store, request.SessionID(r))
provider := request.RouteStringParam(r, "provider")
if provider == "" {
logger.Error("[OAuth2] Invalid or missing provider: %s", provider)
html.Redirect(w, r, route.Path(c.router, "login"))
html.Redirect(w, r, route.Path(h.router, "login"))
return
}
authProvider, err := getOAuth2Manager(c.cfg).Provider(provider)
authProvider, err := getOAuth2Manager(h.cfg).Provider(provider)
if err != nil {
logger.Error("[OAuth2] %v", err)
html.Redirect(w, r, route.Path(c.router, "login"))
html.Redirect(w, r, route.Path(h.router, "login"))
return
}

View File

@ -15,26 +15,25 @@ import (
"miniflux.app/ui/session"
)
// OAuth2Unlink unlink an account from the external provider.
func (c *Controller) OAuth2Unlink(w http.ResponseWriter, r *http.Request) {
func (h *handler) oauth2Unlink(w http.ResponseWriter, r *http.Request) {
printer := locale.NewPrinter(request.UserLanguage(r))
provider := request.RouteStringParam(r, "provider")
if provider == "" {
logger.Info("[OAuth2] Invalid or missing provider")
html.Redirect(w, r, route.Path(c.router, "login"))
html.Redirect(w, r, route.Path(h.router, "login"))
return
}
authProvider, err := getOAuth2Manager(c.cfg).Provider(provider)
authProvider, err := getOAuth2Manager(h.cfg).Provider(provider)
if err != nil {
logger.Error("[OAuth2] %v", err)
html.Redirect(w, r, route.Path(c.router, "settings"))
html.Redirect(w, r, route.Path(h.router, "settings"))
return
}
sess := session.New(c.store, request.SessionID(r))
sess := session.New(h.store, request.SessionID(r))
hasPassword, err := c.store.HasPassword(request.UserID(r))
hasPassword, err := h.store.HasPassword(request.UserID(r))
if err != nil {
html.ServerError(w, r, err)
return
@ -42,15 +41,15 @@ func (c *Controller) OAuth2Unlink(w http.ResponseWriter, r *http.Request) {
if !hasPassword {
sess.NewFlashErrorMessage(printer.Printf("error.unlink_account_without_password"))
html.Redirect(w, r, route.Path(c.router, "settings"))
html.Redirect(w, r, route.Path(h.router, "settings"))
return
}
if err := c.store.RemoveExtraField(request.UserID(r), authProvider.GetUserExtraKey()); err != nil {
if err := h.store.RemoveExtraField(request.UserID(r), authProvider.GetUserExtraKey()); err != nil {
html.ServerError(w, r, err)
return
}
sess.NewFlashMessage(printer.Printf("alert.account_unlinked"))
html.Redirect(w, r, route.Path(c.router, "settings"))
html.Redirect(w, r, route.Path(h.router, "settings"))
}

View File

@ -13,9 +13,8 @@ import (
"miniflux.app/reader/opml"
)
// Export generates the OPML file.
func (c *Controller) Export(w http.ResponseWriter, r *http.Request) {
opml, err := opml.NewHandler(c.store).Export(request.UserID(r))
func (h *handler) exportFeeds(w http.ResponseWriter, r *http.Request) {
opml, err := opml.NewHandler(h.store).Export(request.UserID(r))
if err != nil {
html.ServerError(w, r, err)
return

View File

@ -13,20 +13,19 @@ import (
"miniflux.app/ui/view"
)
// Import shows the import form.
func (c *Controller) Import(w http.ResponseWriter, r *http.Request) {
user, err := c.store.UserByID(request.UserID(r))
func (h *handler) showImportPage(w http.ResponseWriter, r *http.Request) {
user, err := h.store.UserByID(request.UserID(r))
if err != nil {
html.ServerError(w, r, err)
return
}
sess := session.New(c.store, request.SessionID(r))
view := view.New(c.tpl, r, sess)
sess := session.New(h.store, request.SessionID(r))
view := view.New(h.tpl, r, sess)
view.Set("menu", "feeds")
view.Set("user", user)
view.Set("countUnread", c.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", c.store.CountErrorFeeds(user.ID))
view.Set("countUnread", h.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", h.store.CountErrorFeeds(user.ID))
html.OK(w, r, view.Render("import"))
}

View File

@ -16,9 +16,8 @@ import (
"miniflux.app/ui/view"
)
// UploadOPML handles OPML file importation.
func (c *Controller) UploadOPML(w http.ResponseWriter, r *http.Request) {
user, err := c.store.UserByID(request.UserID(r))
func (h *handler) uploadOPML(w http.ResponseWriter, r *http.Request) {
user, err := h.store.UserByID(request.UserID(r))
if err != nil {
html.ServerError(w, r, err)
return
@ -27,7 +26,7 @@ func (c *Controller) UploadOPML(w http.ResponseWriter, r *http.Request) {
file, fileHeader, err := r.FormFile("file")
if err != nil {
logger.Error("[Controller:UploadOPML] %v", err)
html.Redirect(w, r, route.Path(c.router, "import"))
html.Redirect(w, r, route.Path(h.router, "import"))
return
}
defer file.Close()
@ -39,12 +38,12 @@ func (c *Controller) UploadOPML(w http.ResponseWriter, r *http.Request) {
fileHeader.Size,
)
sess := session.New(c.store, request.SessionID(r))
view := view.New(c.tpl, r, sess)
sess := session.New(h.store, request.SessionID(r))
view := view.New(h.tpl, r, sess)
view.Set("menu", "feeds")
view.Set("user", user)
view.Set("countUnread", c.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", c.store.CountErrorFeeds(user.ID))
view.Set("countUnread", h.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", h.store.CountErrorFeeds(user.ID))
if fileHeader.Size == 0 {
view.Set("errorMessage", "error.empty_file")
@ -52,11 +51,11 @@ func (c *Controller) UploadOPML(w http.ResponseWriter, r *http.Request) {
return
}
if impErr := opml.NewHandler(c.store).Import(user.ID, file); impErr != nil {
if impErr := opml.NewHandler(h.store).Import(user.ID, file); impErr != nil {
view.Set("errorMessage", impErr)
html.OK(w, r, view.Render("import"))
return
}
html.Redirect(w, r, route.Path(c.router, "feeds"))
html.Redirect(w, r, route.Path(h.router, "feeds"))
}

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
package ui // import "miniflux.app/ui"
package ui // import "miniflux.app/ui"
const (
nbItemsPerPage = 100
@ -20,7 +20,7 @@ type pagination struct {
SearchQuery string
}
func (c *Controller) getPagination(route string, total, offset int) pagination {
func getPagination(route string, total, offset int) pagination {
nextOffset := 0
prevOffset := 0
showNext := (total - offset) > nbItemsPerPage

View File

@ -18,8 +18,7 @@ import (
"miniflux.app/http/response/html"
)
// ImageProxy fetch an image from a remote server and sent it back to the browser.
func (c *Controller) ImageProxy(w http.ResponseWriter, r *http.Request) {
func (h *handler) imageProxy(w http.ResponseWriter, r *http.Request) {
// If we receive a "If-None-Match" header, we assume the image is already stored in browser cache.
if r.Header.Get("If-None-Match") != "" {
w.WriteHeader(http.StatusNotModified)

View File

@ -15,9 +15,8 @@ import (
"miniflux.app/ui/view"
)
// ShowSearchEntries shows all entries for the given feed.
func (c *Controller) ShowSearchEntries(w http.ResponseWriter, r *http.Request) {
user, err := c.store.UserByID(request.UserID(r))
func (h *handler) showSearchEntriesPage(w http.ResponseWriter, r *http.Request) {
user, err := h.store.UserByID(request.UserID(r))
if err != nil {
html.ServerError(w, r, err)
return
@ -25,7 +24,7 @@ func (c *Controller) ShowSearchEntries(w http.ResponseWriter, r *http.Request) {
searchQuery := request.QueryStringParam(r, "q", "")
offset := request.QueryIntParam(r, "offset", 0)
builder := c.store.NewEntryQueryBuilder(user.ID)
builder := h.store.NewEntryQueryBuilder(user.ID)
builder.WithSearchQuery(searchQuery)
builder.WithoutStatus(model.EntryStatusRemoved)
builder.WithOrder(model.DefaultSortingOrder)
@ -45,9 +44,9 @@ func (c *Controller) ShowSearchEntries(w http.ResponseWriter, r *http.Request) {
return
}
sess := session.New(c.store, request.SessionID(r))
view := view.New(c.tpl, r, sess)
pagination := c.getPagination(route.Path(c.router, "searchEntries"), count, offset)
sess := session.New(h.store, request.SessionID(r))
view := view.New(h.tpl, r, sess)
pagination := getPagination(route.Path(h.router, "searchEntries"), count, offset)
pagination.SearchQuery = searchQuery
view.Set("searchQuery", searchQuery)
@ -56,9 +55,9 @@ func (c *Controller) ShowSearchEntries(w http.ResponseWriter, r *http.Request) {
view.Set("pagination", pagination)
view.Set("menu", "search")
view.Set("user", user)
view.Set("countUnread", c.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", c.store.CountErrorFeeds(user.ID))
view.Set("hasSaveEntry", c.store.HasSaveEntry(user.ID))
view.Set("countUnread", h.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", h.store.CountErrorFeeds(user.ID))
view.Set("hasSaveEntry", h.store.HasSaveEntry(user.ID))
html.OK(w, r, view.Render("search_entries"))
}

View File

@ -13,18 +13,17 @@ import (
"miniflux.app/ui/view"
)
// ShowSessions shows the list of active user sessions.
func (c *Controller) ShowSessions(w http.ResponseWriter, r *http.Request) {
sess := session.New(c.store, request.SessionID(r))
view := view.New(c.tpl, r, sess)
func (h *handler) showSessionsPage(w http.ResponseWriter, r *http.Request) {
sess := session.New(h.store, request.SessionID(r))
view := view.New(h.tpl, r, sess)
user, err := c.store.UserByID(request.UserID(r))
user, err := h.store.UserByID(request.UserID(r))
if err != nil {
html.ServerError(w, r, err)
return
}
sessions, err := c.store.UserSessions(user.ID)
sessions, err := h.store.UserSessions(user.ID)
if err != nil {
html.ServerError(w, r, err)
return
@ -36,8 +35,8 @@ func (c *Controller) ShowSessions(w http.ResponseWriter, r *http.Request) {
view.Set("sessions", sessions)
view.Set("menu", "settings")
view.Set("user", user)
view.Set("countUnread", c.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", c.store.CountErrorFeeds(user.ID))
view.Set("countUnread", h.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", h.store.CountErrorFeeds(user.ID))
html.OK(w, r, view.Render("sessions"))
}

View File

@ -13,13 +13,12 @@ import (
"miniflux.app/logger"
)
// RemoveSession remove a user session.
func (c *Controller) RemoveSession(w http.ResponseWriter, r *http.Request) {
func (h *handler) removeSession(w http.ResponseWriter, r *http.Request) {
sessionID := request.RouteInt64Param(r, "sessionID")
err := c.store.RemoveUserSessionByID(request.UserID(r), sessionID)
err := h.store.RemoveUserSessionByID(request.UserID(r), sessionID)
if err != nil {
logger.Error("[Controller:RemoveSession] %v", err)
}
html.Redirect(w, r, route.Path(c.router, "sessions"))
html.Redirect(w, r, route.Path(h.router, "sessions"))
}

View File

@ -16,12 +16,11 @@ import (
"miniflux.app/ui/view"
)
// ShowSettings shows the settings page.
func (c *Controller) ShowSettings(w http.ResponseWriter, r *http.Request) {
sess := session.New(c.store, request.SessionID(r))
view := view.New(c.tpl, r, sess)
func (h *handler) showSettingsPage(w http.ResponseWriter, r *http.Request) {
sess := session.New(h.store, request.SessionID(r))
view := view.New(h.tpl, r, sess)
user, err := c.store.UserByID(request.UserID(r))
user, err := h.store.UserByID(request.UserID(r))
if err != nil {
html.ServerError(w, r, err)
return
@ -35,7 +34,7 @@ func (c *Controller) ShowSettings(w http.ResponseWriter, r *http.Request) {
EntryDirection: user.EntryDirection,
}
timezones, err := c.store.Timezones()
timezones, err := h.store.Timezones()
if err != nil {
html.ServerError(w, r, err)
return
@ -47,8 +46,8 @@ func (c *Controller) ShowSettings(w http.ResponseWriter, r *http.Request) {
view.Set("timezones", timezones)
view.Set("menu", "settings")
view.Set("user", user)
view.Set("countUnread", c.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", c.store.CountErrorFeeds(user.ID))
view.Set("countUnread", h.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", h.store.CountErrorFeeds(user.ID))
html.OK(w, r, view.Render("settings"))
}

View File

@ -18,18 +18,17 @@ import (
"miniflux.app/ui/view"
)
// UpdateSettings update the settings.
func (c *Controller) UpdateSettings(w http.ResponseWriter, r *http.Request) {
sess := session.New(c.store, request.SessionID(r))
view := view.New(c.tpl, r, sess)
func (h *handler) updateSettings(w http.ResponseWriter, r *http.Request) {
sess := session.New(h.store, request.SessionID(r))
view := view.New(h.tpl, r, sess)
user, err := c.store.UserByID(request.UserID(r))
user, err := h.store.UserByID(request.UserID(r))
if err != nil {
html.ServerError(w, r, err)
return
}
timezones, err := c.store.Timezones()
timezones, err := h.store.Timezones()
if err != nil {
html.ServerError(w, r, err)
return
@ -43,8 +42,8 @@ func (c *Controller) UpdateSettings(w http.ResponseWriter, r *http.Request) {
view.Set("timezones", timezones)
view.Set("menu", "settings")
view.Set("user", user)
view.Set("countUnread", c.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", c.store.CountErrorFeeds(user.ID))
view.Set("countUnread", h.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", h.store.CountErrorFeeds(user.ID))
if err := settingsForm.Validate(); err != nil {
view.Set("errorMessage", err.Error())
@ -52,13 +51,13 @@ func (c *Controller) UpdateSettings(w http.ResponseWriter, r *http.Request) {
return
}
if c.store.AnotherUserExists(user.ID, settingsForm.Username) {
if h.store.AnotherUserExists(user.ID, settingsForm.Username) {
view.Set("errorMessage", "error.user_already_exists")
html.OK(w, r, view.Render("settings"))
return
}
err = c.store.UpdateUser(settingsForm.Merge(user))
err = h.store.UpdateUser(settingsForm.Merge(user))
if err != nil {
logger.Error("[Controller:UpdateSettings] %v", err)
view.Set("errorMessage", "error.unable_to_update_user")
@ -69,5 +68,5 @@ func (c *Controller) UpdateSettings(w http.ResponseWriter, r *http.Request) {
sess.SetLanguage(user.Language)
sess.SetTheme(user.Theme)
sess.NewFlashMessage(locale.NewPrinter(request.UserLanguage(r)).Printf("alert.prefs_saved"))
html.Redirect(w, r, route.Path(c.router, "settings"))
html.Redirect(w, r, route.Path(h.router, "settings"))
}

View File

@ -15,8 +15,7 @@ import (
"miniflux.app/ui/static"
)
// AppIcon shows application icons.
func (c *Controller) AppIcon(w http.ResponseWriter, r *http.Request) {
func (h *handler) showAppIcon(w http.ResponseWriter, r *http.Request) {
filename := request.RouteStringParam(r, "filename")
etag, found := static.BinariesChecksums[filename]
if !found {

View File

@ -14,8 +14,7 @@ import (
"miniflux.app/ui/static"
)
// Favicon shows the application favicon.
func (c *Controller) Favicon(w http.ResponseWriter, r *http.Request) {
func (h *handler) showFavicon(w http.ResponseWriter, r *http.Request) {
etag, found := static.BinariesChecksums["favicon.ico"]
if !found {
html.NotFound(w, r)

View File

@ -14,8 +14,7 @@ import (
"miniflux.app/ui/static"
)
// Javascript renders application client side code.
func (c *Controller) Javascript(w http.ResponseWriter, r *http.Request) {
func (h *handler) showJavascript(w http.ResponseWriter, r *http.Request) {
filename := request.RouteStringParam(r, "name")
etag, found := static.JavascriptsChecksums[filename]
if !found {

View File

@ -13,8 +13,7 @@ import (
"miniflux.app/model"
)
// WebManifest renders web manifest file.
func (c *Controller) WebManifest(w http.ResponseWriter, r *http.Request) {
func (h *handler) showWebManifest(w http.ResponseWriter, r *http.Request) {
type webManifestIcon struct {
Source string `json:"src"`
Sizes string `json:"sizes"`
@ -38,13 +37,13 @@ func (c *Controller) WebManifest(w http.ResponseWriter, r *http.Request) {
ShortName: "Miniflux",
Description: "Minimalist Feed Reader",
Display: "minimal-ui",
StartURL: route.Path(c.router, "unread"),
StartURL: route.Path(h.router, "unread"),
ThemeColor: themeColor,
BackgroundColor: themeColor,
Icons: []webManifestIcon{
webManifestIcon{Source: route.Path(c.router, "appIcon", "filename", "icon-120.png"), Sizes: "120x120", Type: "image/png"},
webManifestIcon{Source: route.Path(c.router, "appIcon", "filename", "icon-192.png"), Sizes: "192x192", Type: "image/png"},
webManifestIcon{Source: route.Path(c.router, "appIcon", "filename", "icon-512.png"), Sizes: "512x512", Type: "image/png"},
webManifestIcon{Source: route.Path(h.router, "appIcon", "filename", "icon-120.png"), Sizes: "120x120", Type: "image/png"},
webManifestIcon{Source: route.Path(h.router, "appIcon", "filename", "icon-192.png"), Sizes: "192x192", Type: "image/png"},
webManifestIcon{Source: route.Path(h.router, "appIcon", "filename", "icon-512.png"), Sizes: "512x512", Type: "image/png"},
},
}

View File

@ -14,8 +14,7 @@ import (
"miniflux.app/ui/static"
)
// Stylesheet renders the CSS.
func (c *Controller) Stylesheet(w http.ResponseWriter, r *http.Request) {
func (h *handler) showStylesheet(w http.ResponseWriter, r *http.Request) {
filename := request.RouteStringParam(r, "name")
etag, found := static.StylesheetsChecksums[filename]
if !found {

View File

@ -14,18 +14,17 @@ import (
"miniflux.app/ui/view"
)
// AddSubscription shows the form to add a new feed.
func (c *Controller) AddSubscription(w http.ResponseWriter, r *http.Request) {
sess := session.New(c.store, request.SessionID(r))
view := view.New(c.tpl, r, sess)
func (h *handler) showAddSubscriptionPage(w http.ResponseWriter, r *http.Request) {
sess := session.New(h.store, request.SessionID(r))
view := view.New(h.tpl, r, sess)
user, err := c.store.UserByID(request.UserID(r))
user, err := h.store.UserByID(request.UserID(r))
if err != nil {
html.ServerError(w, r, err)
return
}
categories, err := c.store.Categories(user.ID)
categories, err := h.store.Categories(user.ID)
if err != nil {
html.ServerError(w, r, err)
return
@ -34,8 +33,8 @@ func (c *Controller) AddSubscription(w http.ResponseWriter, r *http.Request) {
view.Set("categories", categories)
view.Set("menu", "feeds")
view.Set("user", user)
view.Set("countUnread", c.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", c.store.CountErrorFeeds(user.ID))
view.Set("countUnread", h.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", h.store.CountErrorFeeds(user.ID))
view.Set("defaultUserAgent", client.DefaultUserAgent)
html.OK(w, r, view.Render("add_subscription"))

View File

@ -15,18 +15,17 @@ import (
"miniflux.app/ui/view"
)
// Bookmarklet prefill the form to add a subscription from the URL provided by the bookmarklet.
func (c *Controller) Bookmarklet(w http.ResponseWriter, r *http.Request) {
sess := session.New(c.store, request.SessionID(r))
view := view.New(c.tpl, r, sess)
func (h *handler) bookmarklet(w http.ResponseWriter, r *http.Request) {
sess := session.New(h.store, request.SessionID(r))
view := view.New(h.tpl, r, sess)
user, err := c.store.UserByID(request.UserID(r))
user, err := h.store.UserByID(request.UserID(r))
if err != nil {
html.ServerError(w, r, err)
return
}
categories, err := c.store.Categories(user.ID)
categories, err := h.store.Categories(user.ID)
if err != nil {
html.ServerError(w, r, err)
return
@ -38,8 +37,8 @@ func (c *Controller) Bookmarklet(w http.ResponseWriter, r *http.Request) {
view.Set("categories", categories)
view.Set("menu", "feeds")
view.Set("user", user)
view.Set("countUnread", c.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", c.store.CountErrorFeeds(user.ID))
view.Set("countUnread", h.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", h.store.CountErrorFeeds(user.ID))
view.Set("defaultUserAgent", client.DefaultUserAgent)
html.OK(w, r, view.Render("add_subscription"))

View File

@ -16,18 +16,17 @@ import (
"miniflux.app/ui/view"
)
// ChooseSubscription shows a page to choose a subscription.
func (c *Controller) ChooseSubscription(w http.ResponseWriter, r *http.Request) {
sess := session.New(c.store, request.SessionID(r))
view := view.New(c.tpl, r, sess)
func (h *handler) showChooseSubscriptionPage(w http.ResponseWriter, r *http.Request) {
sess := session.New(h.store, request.SessionID(r))
view := view.New(h.tpl, r, sess)
user, err := c.store.UserByID(request.UserID(r))
user, err := h.store.UserByID(request.UserID(r))
if err != nil {
html.ServerError(w, r, err)
return
}
categories, err := c.store.Categories(user.ID)
categories, err := h.store.Categories(user.ID)
if err != nil {
html.ServerError(w, r, err)
return
@ -36,8 +35,8 @@ func (c *Controller) ChooseSubscription(w http.ResponseWriter, r *http.Request)
view.Set("categories", categories)
view.Set("menu", "feeds")
view.Set("user", user)
view.Set("countUnread", c.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", c.store.CountErrorFeeds(user.ID))
view.Set("countUnread", h.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", h.store.CountErrorFeeds(user.ID))
view.Set("defaultUserAgent", client.DefaultUserAgent)
subscriptionForm := form.NewSubscriptionForm(r)
@ -48,7 +47,7 @@ func (c *Controller) ChooseSubscription(w http.ResponseWriter, r *http.Request)
return
}
feed, err := c.feedHandler.CreateFeed(
feed, err := h.feedHandler.CreateFeed(
user.ID,
subscriptionForm.CategoryID,
subscriptionForm.URL,
@ -64,5 +63,5 @@ func (c *Controller) ChooseSubscription(w http.ResponseWriter, r *http.Request)
return
}
html.Redirect(w, r, route.Path(c.router, "feedEntries", "feedID", feed.ID))
html.Redirect(w, r, route.Path(h.router, "feedEntries", "feedID", feed.ID))
}

View File

@ -18,18 +18,17 @@ import (
"miniflux.app/ui/view"
)
// SubmitSubscription try to find a feed from the URL provided by the user.
func (c *Controller) SubmitSubscription(w http.ResponseWriter, r *http.Request) {
sess := session.New(c.store, request.SessionID(r))
v := view.New(c.tpl, r, sess)
func (h *handler) submitSubscription(w http.ResponseWriter, r *http.Request) {
sess := session.New(h.store, request.SessionID(r))
v := view.New(h.tpl, r, sess)
user, err := c.store.UserByID(request.UserID(r))
user, err := h.store.UserByID(request.UserID(r))
if err != nil {
html.ServerError(w, r, err)
return
}
categories, err := c.store.Categories(user.ID)
categories, err := h.store.Categories(user.ID)
if err != nil {
html.ServerError(w, r, err)
return
@ -38,8 +37,8 @@ func (c *Controller) SubmitSubscription(w http.ResponseWriter, r *http.Request)
v.Set("categories", categories)
v.Set("menu", "feeds")
v.Set("user", user)
v.Set("countUnread", c.store.CountUnreadEntries(user.ID))
v.Set("countErrorFeeds", c.store.CountErrorFeeds(user.ID))
v.Set("countUnread", h.store.CountUnreadEntries(user.ID))
v.Set("countErrorFeeds", h.store.CountErrorFeeds(user.ID))
v.Set("defaultUserAgent", client.DefaultUserAgent)
subscriptionForm := form.NewSubscriptionForm(r)
@ -73,7 +72,7 @@ func (c *Controller) SubmitSubscription(w http.ResponseWriter, r *http.Request)
v.Set("errorMessage", "error.subscription_not_found")
html.OK(w, r, v.Render("add_subscription"))
case n == 1:
feed, err := c.feedHandler.CreateFeed(
feed, err := h.feedHandler.CreateFeed(
user.ID,
subscriptionForm.CategoryID,
subscriptions[0].URL,
@ -89,15 +88,15 @@ func (c *Controller) SubmitSubscription(w http.ResponseWriter, r *http.Request)
return
}
html.Redirect(w, r, route.Path(c.router, "feedEntries", "feedID", feed.ID))
html.Redirect(w, r, route.Path(h.router, "feedEntries", "feedID", feed.ID))
case n > 1:
v := view.New(c.tpl, r, sess)
v := view.New(h.tpl, r, sess)
v.Set("subscriptions", subscriptions)
v.Set("form", subscriptionForm)
v.Set("menu", "feeds")
v.Set("user", user)
v.Set("countUnread", c.store.CountUnreadEntries(user.ID))
v.Set("countErrorFeeds", c.store.CountErrorFeeds(user.ID))
v.Set("countUnread", h.store.CountUnreadEntries(user.ID))
v.Set("countErrorFeeds", h.store.CountErrorFeeds(user.ID))
html.OK(w, r, v.Render("choose_subscription"))
}

133
ui/ui.go Normal file
View File

@ -0,0 +1,133 @@
// Copyright 2018 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
package ui // import "miniflux.app/ui"
import (
"net/http"
"miniflux.app/config"
"miniflux.app/reader/feed"
"miniflux.app/scheduler"
"miniflux.app/storage"
"miniflux.app/template"
"github.com/gorilla/mux"
)
// Serve declares all routes for the user interface.
func Serve(router *mux.Router, cfg *config.Config, store *storage.Storage, pool *scheduler.WorkerPool, feedHandler *feed.Handler) {
middleware := newMiddleware(router, cfg, store)
handler := &handler{router, cfg, store, template.NewEngine(cfg, router), pool, feedHandler}
uiRouter := router.NewRoute().Subrouter()
uiRouter.Use(middleware.handleAppSession)
uiRouter.Use(middleware.handleUserSession)
// Static assets.
uiRouter.HandleFunc("/stylesheets/{name}.css", handler.showStylesheet).Name("stylesheet").Methods("GET")
uiRouter.HandleFunc("/{name}.js", handler.showJavascript).Name("javascript").Methods("GET")
uiRouter.HandleFunc("/favicon.ico", handler.showFavicon).Name("favicon").Methods("GET")
uiRouter.HandleFunc("/icon/{filename}", handler.showAppIcon).Name("appIcon").Methods("GET")
uiRouter.HandleFunc("/manifest.json", handler.showWebManifest).Name("webManifest").Methods("GET")
// New subscription pages.
uiRouter.HandleFunc("/subscribe", handler.showAddSubscriptionPage).Name("addSubscription").Methods("GET")
uiRouter.HandleFunc("/subscribe", handler.submitSubscription).Name("submitSubscription").Methods("POST")
uiRouter.HandleFunc("/subscriptions", handler.showChooseSubscriptionPage).Name("chooseSubscription").Methods("POST")
uiRouter.HandleFunc("/bookmarklet", handler.bookmarklet).Name("bookmarklet").Methods("GET")
// Unread page.
uiRouter.HandleFunc("/mark-all-as-read", handler.markAllAsRead).Name("markAllAsRead").Methods("GET")
uiRouter.HandleFunc("/unread", handler.showUnreadPage).Name("unread").Methods("GET")
uiRouter.HandleFunc("/unread/entry/{entryID}", handler.showUnreadEntryPage).Name("unreadEntry").Methods("GET")
// History pages.
uiRouter.HandleFunc("/history", handler.showHistoryPage).Name("history").Methods("GET")
uiRouter.HandleFunc("/history/entry/{entryID}", handler.showReadEntryPage).Name("readEntry").Methods("GET")
uiRouter.HandleFunc("/history/flush", handler.flushHistory).Name("flushHistory").Methods("GET")
// Bookmark pages.
uiRouter.HandleFunc("/starred", handler.showStarredPage).Name("starred").Methods("GET")
uiRouter.HandleFunc("/starred/entry/{entryID}", handler.showStarredEntryPage).Name("starredEntry").Methods("GET")
// Search pages.
uiRouter.HandleFunc("/search", handler.showSearchEntriesPage).Name("searchEntries").Methods("GET")
uiRouter.HandleFunc("/search/entry/{entryID}", handler.showSearchEntryPage).Name("searchEntry").Methods("GET")
// Feed listing pages.
uiRouter.HandleFunc("/feeds", handler.showFeedsPage).Name("feeds").Methods("GET")
uiRouter.HandleFunc("/feeds/refresh", handler.refreshAllFeeds).Name("refreshAllFeeds").Methods("GET")
// Individual feed pages.
uiRouter.HandleFunc("/feed/{feedID}/refresh", handler.refreshFeed).Name("refreshFeed").Methods("GET")
uiRouter.HandleFunc("/feed/{feedID}/edit", handler.showEditFeedPage).Name("editFeed").Methods("GET")
uiRouter.HandleFunc("/feed/{feedID}/remove", handler.removeFeed).Name("removeFeed").Methods("POST")
uiRouter.HandleFunc("/feed/{feedID}/update", handler.updateFeed).Name("updateFeed").Methods("POST")
uiRouter.HandleFunc("/feed/{feedID}/entries", handler.showFeedEntriesPage).Name("feedEntries").Methods("GET")
uiRouter.HandleFunc("/feed/{feedID}/entry/{entryID}", handler.showFeedEntryPage).Name("feedEntry").Methods("GET")
uiRouter.HandleFunc("/feed/icon/{iconID}", handler.showIcon).Name("icon").Methods("GET")
// Category pages.
uiRouter.HandleFunc("/category/{categoryID}/entry/{entryID}", handler.showCategoryEntryPage).Name("categoryEntry").Methods("GET")
uiRouter.HandleFunc("/categories", handler.showCategoryListPage).Name("categories").Methods("GET")
uiRouter.HandleFunc("/category/create", handler.showCreateCategoryPage).Name("createCategory").Methods("GET")
uiRouter.HandleFunc("/category/save", handler.saveCategory).Name("saveCategory").Methods("POST")
uiRouter.HandleFunc("/category/{categoryID}/entries", handler.showCategoryEntriesPage).Name("categoryEntries").Methods("GET")
uiRouter.HandleFunc("/category/{categoryID}/edit", handler.showEditCategoryPage).Name("editCategory").Methods("GET")
uiRouter.HandleFunc("/category/{categoryID}/update", handler.updateCategory).Name("updateCategory").Methods("POST")
uiRouter.HandleFunc("/category/{categoryID}/remove", handler.removeCategory).Name("removeCategory").Methods("POST")
// Entry pages.
uiRouter.HandleFunc("/entry/status", handler.updateEntriesStatus).Name("updateEntriesStatus").Methods("POST")
uiRouter.HandleFunc("/entry/save/{entryID}", handler.saveEntry).Name("saveEntry").Methods("POST")
uiRouter.HandleFunc("/entry/download/{entryID}", handler.fetchContent).Name("fetchContent").Methods("POST")
uiRouter.HandleFunc("/proxy/{encodedURL}", handler.imageProxy).Name("proxy").Methods("GET")
uiRouter.HandleFunc("/entry/bookmark/{entryID}", handler.toggleBookmark).Name("toggleBookmark").Methods("POST")
// User pages.
uiRouter.HandleFunc("/users", handler.showUsersPage).Name("users").Methods("GET")
uiRouter.HandleFunc("/user/create", handler.showCreateUserPage).Name("createUser").Methods("GET")
uiRouter.HandleFunc("/user/save", handler.saveUser).Name("saveUser").Methods("POST")
uiRouter.HandleFunc("/users/{userID}/edit", handler.showEditUserPage).Name("editUser").Methods("GET")
uiRouter.HandleFunc("/users/{userID}/update", handler.updateUser).Name("updateUser").Methods("POST")
uiRouter.HandleFunc("/users/{userID}/remove", handler.removeUser).Name("removeUser").Methods("POST")
// Settings pages.
uiRouter.HandleFunc("/settings", handler.showSettingsPage).Name("settings").Methods("GET")
uiRouter.HandleFunc("/settings", handler.updateSettings).Name("updateSettings").Methods("POST")
uiRouter.HandleFunc("/integrations", handler.showIntegrationPage).Name("integrations").Methods("GET")
uiRouter.HandleFunc("/integration", handler.updateIntegration).Name("updateIntegration").Methods("POST")
uiRouter.HandleFunc("/integration/pocket/authorize", handler.pocketAuthorize).Name("pocketAuthorize").Methods("GET")
uiRouter.HandleFunc("/integration/pocket/callback", handler.pocketCallback).Name("pocketCallback").Methods("GET")
uiRouter.HandleFunc("/about", handler.showAboutPage).Name("about").Methods("GET")
// Session pages.
uiRouter.HandleFunc("/sessions", handler.showSessionsPage).Name("sessions").Methods("GET")
uiRouter.HandleFunc("/sessions/{sessionID}/remove", handler.removeSession).Name("removeSession").Methods("POST")
// OPML pages.
uiRouter.HandleFunc("/export", handler.exportFeeds).Name("export").Methods("GET")
uiRouter.HandleFunc("/import", handler.showImportPage).Name("import").Methods("GET")
uiRouter.HandleFunc("/upload", handler.uploadOPML).Name("uploadOPML").Methods("POST")
// OAuth2 flow.
uiRouter.HandleFunc("/oauth2/{provider}/unlink", handler.oauth2Unlink).Name("oauth2Unlink").Methods("GET")
uiRouter.HandleFunc("/oauth2/{provider}/redirect", handler.oauth2Redirect).Name("oauth2Redirect").Methods("GET")
uiRouter.HandleFunc("/oauth2/{provider}/callback", handler.oauth2Callback).Name("oauth2Callback").Methods("GET")
// Authentication pages.
uiRouter.HandleFunc("/login", handler.checkLogin).Name("checkLogin").Methods("POST")
uiRouter.HandleFunc("/logout", handler.logout).Name("logout").Methods("GET")
uiRouter.HandleFunc("/", handler.showLoginPage).Name("login").Methods("GET")
router.HandleFunc("/healthcheck", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("OK"))
}).Name("healthcheck")
router.HandleFunc("/robots.txt", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
w.Write([]byte("User-agent: *\nDisallow: /"))
}).Name("robots")
}

View File

@ -15,19 +15,18 @@ import (
"miniflux.app/ui/view"
)
// ShowUnreadPage render the page with all unread entries.
func (c *Controller) ShowUnreadPage(w http.ResponseWriter, r *http.Request) {
sess := session.New(c.store, request.SessionID(r))
view := view.New(c.tpl, r, sess)
func (h *handler) showUnreadPage(w http.ResponseWriter, r *http.Request) {
sess := session.New(h.store, request.SessionID(r))
view := view.New(h.tpl, r, sess)
user, err := c.store.UserByID(request.UserID(r))
user, err := h.store.UserByID(request.UserID(r))
if err != nil {
html.ServerError(w, r, err)
return
}
offset := request.QueryIntParam(r, "offset", 0)
builder := c.store.NewEntryQueryBuilder(user.ID)
builder := h.store.NewEntryQueryBuilder(user.ID)
builder.WithStatus(model.EntryStatusUnread)
countUnread, err := builder.CountEntries()
if err != nil {
@ -39,7 +38,7 @@ func (c *Controller) ShowUnreadPage(w http.ResponseWriter, r *http.Request) {
offset = 0
}
builder = c.store.NewEntryQueryBuilder(user.ID)
builder = h.store.NewEntryQueryBuilder(user.ID)
builder.WithStatus(model.EntryStatusUnread)
builder.WithOrder(model.DefaultSortingOrder)
builder.WithDirection(user.EntryDirection)
@ -52,12 +51,12 @@ func (c *Controller) ShowUnreadPage(w http.ResponseWriter, r *http.Request) {
}
view.Set("entries", entries)
view.Set("pagination", c.getPagination(route.Path(c.router, "unread"), countUnread, offset))
view.Set("pagination", getPagination(route.Path(h.router, "unread"), countUnread, offset))
view.Set("menu", "unread")
view.Set("user", user)
view.Set("countUnread", countUnread)
view.Set("countErrorFeeds", c.store.CountErrorFeeds(user.ID))
view.Set("hasSaveEntry", c.store.HasSaveEntry(user.ID))
view.Set("countErrorFeeds", h.store.CountErrorFeeds(user.ID))
view.Set("hasSaveEntry", h.store.HasSaveEntry(user.ID))
html.OK(w, r, view.Render("unread_entries"))
}

View File

@ -13,11 +13,10 @@ import (
"miniflux.app/logger"
)
// MarkAllAsRead marks all unread entries as read.
func (c *Controller) MarkAllAsRead(w http.ResponseWriter, r *http.Request) {
if err := c.store.MarkAllAsRead(request.UserID(r)); err != nil {
func (h *handler) markAllAsRead(w http.ResponseWriter, r *http.Request) {
if err := h.store.MarkAllAsRead(request.UserID(r)); err != nil {
logger.Error("[MarkAllAsRead] %v", err)
}
html.Redirect(w, r, route.Path(c.router, "unread"))
html.Redirect(w, r, route.Path(h.router, "unread"))
}

View File

@ -14,12 +14,11 @@ import (
"miniflux.app/ui/view"
)
// CreateUser shows the user creation form.
func (c *Controller) CreateUser(w http.ResponseWriter, r *http.Request) {
sess := session.New(c.store, request.SessionID(r))
view := view.New(c.tpl, r, sess)
func (h *handler) showCreateUserPage(w http.ResponseWriter, r *http.Request) {
sess := session.New(h.store, request.SessionID(r))
view := view.New(h.tpl, r, sess)
user, err := c.store.UserByID(request.UserID(r))
user, err := h.store.UserByID(request.UserID(r))
if err != nil {
html.ServerError(w, r, err)
return
@ -33,8 +32,8 @@ func (c *Controller) CreateUser(w http.ResponseWriter, r *http.Request) {
view.Set("form", &form.UserForm{})
view.Set("menu", "settings")
view.Set("user", user)
view.Set("countUnread", c.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", c.store.CountErrorFeeds(user.ID))
view.Set("countUnread", h.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", h.store.CountErrorFeeds(user.ID))
html.OK(w, r, view.Render("create_user"))
}

View File

@ -15,11 +15,11 @@ import (
)
// EditUser shows the form to edit a user.
func (c *Controller) EditUser(w http.ResponseWriter, r *http.Request) {
sess := session.New(c.store, request.SessionID(r))
view := view.New(c.tpl, r, sess)
func (h *handler) showEditUserPage(w http.ResponseWriter, r *http.Request) {
sess := session.New(h.store, request.SessionID(r))
view := view.New(h.tpl, r, sess)
user, err := c.store.UserByID(request.UserID(r))
user, err := h.store.UserByID(request.UserID(r))
if err != nil {
html.ServerError(w, r, err)
return
@ -31,7 +31,7 @@ func (c *Controller) EditUser(w http.ResponseWriter, r *http.Request) {
}
userID := request.RouteInt64Param(r, "userID")
selectedUser, err := c.store.UserByID(userID)
selectedUser, err := h.store.UserByID(userID)
if err != nil {
html.ServerError(w, r, err)
return
@ -51,8 +51,8 @@ func (c *Controller) EditUser(w http.ResponseWriter, r *http.Request) {
view.Set("selected_user", selectedUser)
view.Set("menu", "settings")
view.Set("user", user)
view.Set("countUnread", c.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", c.store.CountErrorFeeds(user.ID))
view.Set("countUnread", h.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", h.store.CountErrorFeeds(user.ID))
html.OK(w, r, view.Render("edit_user"))
}

View File

@ -13,12 +13,11 @@ import (
"miniflux.app/ui/view"
)
// ShowUsers renders the list of users.
func (c *Controller) ShowUsers(w http.ResponseWriter, r *http.Request) {
sess := session.New(c.store, request.SessionID(r))
view := view.New(c.tpl, r, sess)
func (h *handler) showUsersPage(w http.ResponseWriter, r *http.Request) {
sess := session.New(h.store, request.SessionID(r))
view := view.New(h.tpl, r, sess)
user, err := c.store.UserByID(request.UserID(r))
user, err := h.store.UserByID(request.UserID(r))
if err != nil {
html.ServerError(w, r, err)
return
@ -29,7 +28,7 @@ func (c *Controller) ShowUsers(w http.ResponseWriter, r *http.Request) {
return
}
users, err := c.store.Users()
users, err := h.store.Users()
if err != nil {
html.ServerError(w, r, err)
return
@ -40,8 +39,8 @@ func (c *Controller) ShowUsers(w http.ResponseWriter, r *http.Request) {
view.Set("users", users)
view.Set("menu", "settings")
view.Set("user", user)
view.Set("countUnread", c.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", c.store.CountErrorFeeds(user.ID))
view.Set("countUnread", h.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", h.store.CountErrorFeeds(user.ID))
html.OK(w, r, view.Render("users"))
}

View File

@ -12,9 +12,8 @@ import (
"miniflux.app/http/route"
)
// RemoveUser deletes a user from the database.
func (c *Controller) RemoveUser(w http.ResponseWriter, r *http.Request) {
user, err := c.store.UserByID(request.UserID(r))
func (h *handler) removeUser(w http.ResponseWriter, r *http.Request) {
user, err := h.store.UserByID(request.UserID(r))
if err != nil {
html.ServerError(w, r, err)
return
@ -26,7 +25,7 @@ func (c *Controller) RemoveUser(w http.ResponseWriter, r *http.Request) {
}
userID := request.RouteInt64Param(r, "userID")
selectedUser, err := c.store.UserByID(userID)
selectedUser, err := h.store.UserByID(userID)
if err != nil {
html.ServerError(w, r, err)
return
@ -37,10 +36,10 @@ func (c *Controller) RemoveUser(w http.ResponseWriter, r *http.Request) {
return
}
if err := c.store.RemoveUser(selectedUser.ID); err != nil {
if err := h.store.RemoveUser(selectedUser.ID); err != nil {
html.ServerError(w, r, err)
return
}
html.Redirect(w, r, route.Path(c.router, "users"))
html.Redirect(w, r, route.Path(h.router, "users"))
}

View File

@ -16,9 +16,8 @@ import (
"miniflux.app/ui/view"
)
// SaveUser validate and save the new user into the database.
func (c *Controller) SaveUser(w http.ResponseWriter, r *http.Request) {
user, err := c.store.UserByID(request.UserID(r))
func (h *handler) saveUser(w http.ResponseWriter, r *http.Request) {
user, err := h.store.UserByID(request.UserID(r))
if err != nil {
html.ServerError(w, r, err)
return
@ -31,12 +30,12 @@ func (c *Controller) SaveUser(w http.ResponseWriter, r *http.Request) {
userForm := form.NewUserForm(r)
sess := session.New(c.store, request.SessionID(r))
view := view.New(c.tpl, r, sess)
sess := session.New(h.store, request.SessionID(r))
view := view.New(h.tpl, r, sess)
view.Set("menu", "settings")
view.Set("user", user)
view.Set("countUnread", c.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", c.store.CountErrorFeeds(user.ID))
view.Set("countUnread", h.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", h.store.CountErrorFeeds(user.ID))
view.Set("form", userForm)
if err := userForm.ValidateCreation(); err != nil {
@ -45,19 +44,19 @@ func (c *Controller) SaveUser(w http.ResponseWriter, r *http.Request) {
return
}
if c.store.UserExists(userForm.Username) {
if h.store.UserExists(userForm.Username) {
view.Set("errorMessage", "error.user_already_exists")
html.OK(w, r, view.Render("create_user"))
return
}
newUser := userForm.ToUser()
if err := c.store.CreateUser(newUser); err != nil {
if err := h.store.CreateUser(newUser); err != nil {
logger.Error("[Controller:SaveUser] %v", err)
view.Set("errorMessage", "error.unable_to_create_user")
html.OK(w, r, view.Render("create_user"))
return
}
html.Redirect(w, r, route.Path(c.router, "users"))
html.Redirect(w, r, route.Path(h.router, "users"))
}

View File

@ -16,9 +16,8 @@ import (
"miniflux.app/ui/view"
)
// UpdateUser validate and update a user.
func (c *Controller) UpdateUser(w http.ResponseWriter, r *http.Request) {
user, err := c.store.UserByID(request.UserID(r))
func (h *handler) updateUser(w http.ResponseWriter, r *http.Request) {
user, err := h.store.UserByID(request.UserID(r))
if err != nil {
html.ServerError(w, r, err)
return
@ -30,7 +29,7 @@ func (c *Controller) UpdateUser(w http.ResponseWriter, r *http.Request) {
}
userID := request.RouteInt64Param(r, "userID")
selectedUser, err := c.store.UserByID(userID)
selectedUser, err := h.store.UserByID(userID)
if err != nil {
html.ServerError(w, r, err)
return
@ -43,12 +42,12 @@ func (c *Controller) UpdateUser(w http.ResponseWriter, r *http.Request) {
userForm := form.NewUserForm(r)
sess := session.New(c.store, request.SessionID(r))
view := view.New(c.tpl, r, sess)
sess := session.New(h.store, request.SessionID(r))
view := view.New(h.tpl, r, sess)
view.Set("menu", "settings")
view.Set("user", user)
view.Set("countUnread", c.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", c.store.CountErrorFeeds(user.ID))
view.Set("countUnread", h.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", h.store.CountErrorFeeds(user.ID))
view.Set("selected_user", selectedUser)
view.Set("form", userForm)
@ -58,19 +57,19 @@ func (c *Controller) UpdateUser(w http.ResponseWriter, r *http.Request) {
return
}
if c.store.AnotherUserExists(selectedUser.ID, userForm.Username) {
if h.store.AnotherUserExists(selectedUser.ID, userForm.Username) {
view.Set("errorMessage", "error.user_already_exists")
html.OK(w, r, view.Render("edit_user"))
return
}
userForm.Merge(selectedUser)
if err := c.store.UpdateUser(selectedUser); err != nil {
if err := h.store.UpdateUser(selectedUser); err != nil {
logger.Error("[Controller:UpdateUser] %v", err)
view.Set("errorMessage", "error.unable_to_update_user")
html.OK(w, r, view.Render("edit_user"))
return
}
html.Redirect(w, r, route.Path(c.router, "users"))
html.Redirect(w, r, route.Path(h.router, "users"))
}