mirror of
https://github.com/miniflux/v2.git
synced 2025-08-01 17:38:37 +00:00
First commit
This commit is contained in:
commit
8ffb773f43
2121 changed files with 1118910 additions and 0 deletions
24
server/ui/controller/about.go
Normal file
24
server/ui/controller/about.go
Normal file
|
@ -0,0 +1,24 @@
|
|||
// 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 controller
|
||||
|
||||
import (
|
||||
"github.com/miniflux/miniflux2/server/core"
|
||||
"github.com/miniflux/miniflux2/version"
|
||||
)
|
||||
|
||||
func (c *Controller) AboutPage(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
args, err := c.getCommonTemplateArgs(ctx)
|
||||
if err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
response.Html().Render("about", args.Merge(tplParams{
|
||||
"version": version.Version,
|
||||
"build_date": version.BuildDate,
|
||||
"menu": "settings",
|
||||
}))
|
||||
}
|
228
server/ui/controller/category.go
Normal file
228
server/ui/controller/category.go
Normal file
|
@ -0,0 +1,228 @@
|
|||
// 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 controller
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/miniflux/miniflux2/model"
|
||||
"github.com/miniflux/miniflux2/server/core"
|
||||
"github.com/miniflux/miniflux2/server/ui/form"
|
||||
"log"
|
||||
)
|
||||
|
||||
func (c *Controller) ShowCategories(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
args, err := c.getCommonTemplateArgs(ctx)
|
||||
if err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
user := ctx.GetLoggedUser()
|
||||
categories, err := c.store.GetCategoriesWithFeedCount(user.ID)
|
||||
if err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
response.Html().Render("categories", args.Merge(tplParams{
|
||||
"categories": categories,
|
||||
"total": len(categories),
|
||||
"menu": "categories",
|
||||
}))
|
||||
}
|
||||
|
||||
func (c *Controller) ShowCategoryEntries(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
user := ctx.GetLoggedUser()
|
||||
offset := request.GetQueryIntegerParam("offset", 0)
|
||||
|
||||
args, err := c.getCommonTemplateArgs(ctx)
|
||||
if err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
category, err := c.getCategoryFromURL(ctx, request, response)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
builder := c.store.GetEntryQueryBuilder(user.ID, user.Timezone)
|
||||
builder.WithCategoryID(category.ID)
|
||||
builder.WithOrder(model.DefaultSortingOrder)
|
||||
builder.WithDirection(model.DefaultSortingDirection)
|
||||
builder.WithOffset(offset)
|
||||
builder.WithLimit(NbItemsPerPage)
|
||||
|
||||
entries, err := builder.GetEntries()
|
||||
if err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
count, err := builder.CountEntries()
|
||||
if err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
response.Html().Render("category_entries", args.Merge(tplParams{
|
||||
"category": category,
|
||||
"entries": entries,
|
||||
"total": count,
|
||||
"pagination": c.getPagination(ctx.GetRoute("categoryEntries", "categoryID", category.ID), count, offset),
|
||||
"menu": "categories",
|
||||
}))
|
||||
}
|
||||
|
||||
func (c *Controller) CreateCategory(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
args, err := c.getCommonTemplateArgs(ctx)
|
||||
if err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
response.Html().Render("create_category", args.Merge(tplParams{
|
||||
"menu": "categories",
|
||||
}))
|
||||
}
|
||||
|
||||
func (c *Controller) SaveCategory(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
user := ctx.GetLoggedUser()
|
||||
args, err := c.getCommonTemplateArgs(ctx)
|
||||
if err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
categoryForm := form.NewCategoryForm(request.GetRequest())
|
||||
if err := categoryForm.Validate(); err != nil {
|
||||
response.Html().Render("create_category", args.Merge(tplParams{
|
||||
"errorMessage": err.Error(),
|
||||
}))
|
||||
return
|
||||
}
|
||||
|
||||
category := model.Category{Title: categoryForm.Title, UserID: user.ID}
|
||||
err = c.store.CreateCategory(&category)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
response.Html().Render("create_category", args.Merge(tplParams{
|
||||
"errorMessage": "Unable to create this category.",
|
||||
}))
|
||||
return
|
||||
}
|
||||
|
||||
response.Redirect(ctx.GetRoute("categories"))
|
||||
}
|
||||
|
||||
func (c *Controller) EditCategory(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
user := ctx.GetLoggedUser()
|
||||
|
||||
category, err := c.getCategoryFromURL(ctx, request, response)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
args, err := c.getCategoryFormTemplateArgs(ctx, user, category, nil)
|
||||
if err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
response.Html().Render("edit_category", args)
|
||||
}
|
||||
|
||||
func (c *Controller) UpdateCategory(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
user := ctx.GetLoggedUser()
|
||||
|
||||
category, err := c.getCategoryFromURL(ctx, request, response)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
categoryForm := form.NewCategoryForm(request.GetRequest())
|
||||
args, err := c.getCategoryFormTemplateArgs(ctx, user, category, categoryForm)
|
||||
if err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := categoryForm.Validate(); err != nil {
|
||||
response.Html().Render("edit_category", args.Merge(tplParams{
|
||||
"errorMessage": err.Error(),
|
||||
}))
|
||||
return
|
||||
}
|
||||
|
||||
err = c.store.UpdateCategory(categoryForm.Merge(category))
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
response.Html().Render("edit_category", args.Merge(tplParams{
|
||||
"errorMessage": "Unable to update this category.",
|
||||
}))
|
||||
return
|
||||
}
|
||||
|
||||
response.Redirect(ctx.GetRoute("categories"))
|
||||
}
|
||||
|
||||
func (c *Controller) RemoveCategory(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
user := ctx.GetLoggedUser()
|
||||
|
||||
category, err := c.getCategoryFromURL(ctx, request, response)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := c.store.RemoveCategory(user.ID, category.ID); err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
response.Redirect(ctx.GetRoute("categories"))
|
||||
}
|
||||
|
||||
func (c *Controller) getCategoryFromURL(ctx *core.Context, request *core.Request, response *core.Response) (*model.Category, error) {
|
||||
categoryID, err := request.GetIntegerParam("categoryID")
|
||||
if err != nil {
|
||||
response.Html().BadRequest(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
user := ctx.GetLoggedUser()
|
||||
category, err := c.store.GetCategory(user.ID, categoryID)
|
||||
if err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if category == nil {
|
||||
response.Html().NotFound()
|
||||
return nil, errors.New("Category not found")
|
||||
}
|
||||
|
||||
return category, nil
|
||||
}
|
||||
|
||||
func (c *Controller) getCategoryFormTemplateArgs(ctx *core.Context, user *model.User, category *model.Category, categoryForm *form.CategoryForm) (tplParams, error) {
|
||||
args, err := c.getCommonTemplateArgs(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if categoryForm == nil {
|
||||
args["form"] = form.CategoryForm{
|
||||
Title: category.Title,
|
||||
}
|
||||
} else {
|
||||
args["form"] = categoryForm
|
||||
}
|
||||
|
||||
args["category"] = category
|
||||
args["menu"] = "categories"
|
||||
return args, nil
|
||||
}
|
56
server/ui/controller/controller.go
Normal file
56
server/ui/controller/controller.go
Normal file
|
@ -0,0 +1,56 @@
|
|||
// 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 controller
|
||||
|
||||
import (
|
||||
"github.com/miniflux/miniflux2/model"
|
||||
"github.com/miniflux/miniflux2/reader/feed"
|
||||
"github.com/miniflux/miniflux2/reader/opml"
|
||||
"github.com/miniflux/miniflux2/server/core"
|
||||
"github.com/miniflux/miniflux2/storage"
|
||||
)
|
||||
|
||||
type tplParams map[string]interface{}
|
||||
|
||||
func (t tplParams) Merge(d tplParams) tplParams {
|
||||
for k, v := range d {
|
||||
t[k] = v
|
||||
}
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
type Controller struct {
|
||||
store *storage.Storage
|
||||
feedHandler *feed.Handler
|
||||
opmlHandler *opml.OpmlHandler
|
||||
}
|
||||
|
||||
func (c *Controller) getCommonTemplateArgs(ctx *core.Context) (tplParams, error) {
|
||||
user := ctx.GetLoggedUser()
|
||||
builder := c.store.GetEntryQueryBuilder(user.ID, user.Timezone)
|
||||
builder.WithStatus(model.EntryStatusUnread)
|
||||
|
||||
countUnread, err := builder.CountEntries()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
params := tplParams{
|
||||
"menu": "",
|
||||
"user": user,
|
||||
"countUnread": countUnread,
|
||||
"csrf": ctx.GetCsrfToken(),
|
||||
}
|
||||
return params, nil
|
||||
}
|
||||
|
||||
func NewController(store *storage.Storage, feedHandler *feed.Handler, opmlHandler *opml.OpmlHandler) *Controller {
|
||||
return &Controller{
|
||||
store: store,
|
||||
feedHandler: feedHandler,
|
||||
opmlHandler: opmlHandler,
|
||||
}
|
||||
}
|
375
server/ui/controller/entry.go
Normal file
375
server/ui/controller/entry.go
Normal file
|
@ -0,0 +1,375 @@
|
|||
// 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 controller
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/miniflux/miniflux2/model"
|
||||
"github.com/miniflux/miniflux2/server/core"
|
||||
"github.com/miniflux/miniflux2/server/ui/payload"
|
||||
"log"
|
||||
)
|
||||
|
||||
func (c *Controller) ShowFeedEntry(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
user := ctx.GetLoggedUser()
|
||||
sortingDirection := model.DefaultSortingDirection
|
||||
|
||||
entryID, err := request.GetIntegerParam("entryID")
|
||||
if err != nil {
|
||||
response.Html().BadRequest(err)
|
||||
return
|
||||
}
|
||||
|
||||
feedID, err := request.GetIntegerParam("feedID")
|
||||
if err != nil {
|
||||
response.Html().BadRequest(err)
|
||||
return
|
||||
}
|
||||
|
||||
builder := c.store.GetEntryQueryBuilder(user.ID, user.Timezone)
|
||||
builder.WithFeedID(feedID)
|
||||
builder.WithEntryID(entryID)
|
||||
|
||||
entry, err := builder.GetEntry()
|
||||
if err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
if entry == nil {
|
||||
response.Html().NotFound()
|
||||
return
|
||||
}
|
||||
|
||||
args, err := c.getCommonTemplateArgs(ctx)
|
||||
if err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
builder = c.store.GetEntryQueryBuilder(user.ID, user.Timezone)
|
||||
builder.WithFeedID(feedID)
|
||||
builder.WithCondition("e.id", "!=", entryID)
|
||||
builder.WithCondition("e.published_at", "<=", entry.Date)
|
||||
builder.WithOrder(model.DefaultSortingOrder)
|
||||
builder.WithDirection(model.DefaultSortingDirection)
|
||||
nextEntry, err := builder.GetEntry()
|
||||
if err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
builder = c.store.GetEntryQueryBuilder(user.ID, user.Timezone)
|
||||
builder.WithFeedID(feedID)
|
||||
builder.WithCondition("e.id", "!=", entryID)
|
||||
builder.WithCondition("e.published_at", ">=", entry.Date)
|
||||
builder.WithOrder(model.DefaultSortingOrder)
|
||||
builder.WithDirection(model.GetOppositeDirection(sortingDirection))
|
||||
prevEntry, err := builder.GetEntry()
|
||||
if err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
nextEntryRoute := ""
|
||||
if nextEntry != nil {
|
||||
nextEntryRoute = ctx.GetRoute("feedEntry", "feedID", feedID, "entryID", nextEntry.ID)
|
||||
}
|
||||
|
||||
prevEntryRoute := ""
|
||||
if prevEntry != nil {
|
||||
prevEntryRoute = ctx.GetRoute("feedEntry", "feedID", feedID, "entryID", prevEntry.ID)
|
||||
}
|
||||
|
||||
if entry.Status == model.EntryStatusUnread {
|
||||
err = c.store.SetEntriesStatus(user.ID, []int64{entry.ID}, model.EntryStatusRead)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
response.Html().ServerError(nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
response.Html().Render("entry", args.Merge(tplParams{
|
||||
"entry": entry,
|
||||
"prevEntry": prevEntry,
|
||||
"nextEntry": nextEntry,
|
||||
"nextEntryRoute": nextEntryRoute,
|
||||
"prevEntryRoute": prevEntryRoute,
|
||||
"menu": "feeds",
|
||||
}))
|
||||
}
|
||||
|
||||
func (c *Controller) ShowCategoryEntry(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
user := ctx.GetLoggedUser()
|
||||
sortingDirection := model.DefaultSortingDirection
|
||||
|
||||
categoryID, err := request.GetIntegerParam("categoryID")
|
||||
if err != nil {
|
||||
response.Html().BadRequest(err)
|
||||
return
|
||||
}
|
||||
|
||||
entryID, err := request.GetIntegerParam("entryID")
|
||||
if err != nil {
|
||||
response.Html().BadRequest(err)
|
||||
return
|
||||
}
|
||||
|
||||
builder := c.store.GetEntryQueryBuilder(user.ID, user.Timezone)
|
||||
builder.WithCategoryID(categoryID)
|
||||
builder.WithEntryID(entryID)
|
||||
|
||||
entry, err := builder.GetEntry()
|
||||
if err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
if entry == nil {
|
||||
response.Html().NotFound()
|
||||
return
|
||||
}
|
||||
|
||||
args, err := c.getCommonTemplateArgs(ctx)
|
||||
if err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
builder = c.store.GetEntryQueryBuilder(user.ID, user.Timezone)
|
||||
builder.WithCategoryID(categoryID)
|
||||
builder.WithCondition("e.id", "!=", entryID)
|
||||
builder.WithCondition("e.published_at", "<=", entry.Date)
|
||||
builder.WithOrder(model.DefaultSortingOrder)
|
||||
builder.WithDirection(sortingDirection)
|
||||
nextEntry, err := builder.GetEntry()
|
||||
if err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
builder = c.store.GetEntryQueryBuilder(user.ID, user.Timezone)
|
||||
builder.WithCategoryID(categoryID)
|
||||
builder.WithCondition("e.id", "!=", entryID)
|
||||
builder.WithCondition("e.published_at", ">=", entry.Date)
|
||||
builder.WithOrder(model.DefaultSortingOrder)
|
||||
builder.WithDirection(model.GetOppositeDirection(sortingDirection))
|
||||
prevEntry, err := builder.GetEntry()
|
||||
if err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
nextEntryRoute := ""
|
||||
if nextEntry != nil {
|
||||
nextEntryRoute = ctx.GetRoute("categoryEntry", "categoryID", categoryID, "entryID", nextEntry.ID)
|
||||
}
|
||||
|
||||
prevEntryRoute := ""
|
||||
if prevEntry != nil {
|
||||
prevEntryRoute = ctx.GetRoute("categoryEntry", "categoryID", categoryID, "entryID", prevEntry.ID)
|
||||
}
|
||||
|
||||
if entry.Status == model.EntryStatusUnread {
|
||||
err = c.store.SetEntriesStatus(user.ID, []int64{entry.ID}, model.EntryStatusRead)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
response.Html().ServerError(nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
response.Html().Render("entry", args.Merge(tplParams{
|
||||
"entry": entry,
|
||||
"prevEntry": prevEntry,
|
||||
"nextEntry": nextEntry,
|
||||
"nextEntryRoute": nextEntryRoute,
|
||||
"prevEntryRoute": prevEntryRoute,
|
||||
"menu": "categories",
|
||||
}))
|
||||
}
|
||||
|
||||
func (c *Controller) ShowUnreadEntry(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
user := ctx.GetLoggedUser()
|
||||
sortingDirection := model.DefaultSortingDirection
|
||||
|
||||
entryID, err := request.GetIntegerParam("entryID")
|
||||
if err != nil {
|
||||
response.Html().BadRequest(err)
|
||||
return
|
||||
}
|
||||
|
||||
builder := c.store.GetEntryQueryBuilder(user.ID, user.Timezone)
|
||||
builder.WithEntryID(entryID)
|
||||
|
||||
entry, err := builder.GetEntry()
|
||||
if err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
if entry == nil {
|
||||
response.Html().NotFound()
|
||||
return
|
||||
}
|
||||
|
||||
args, err := c.getCommonTemplateArgs(ctx)
|
||||
if err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
builder = c.store.GetEntryQueryBuilder(user.ID, user.Timezone)
|
||||
builder.WithStatus(model.EntryStatusUnread)
|
||||
builder.WithCondition("e.id", "!=", entryID)
|
||||
builder.WithCondition("e.published_at", "<=", entry.Date)
|
||||
builder.WithOrder(model.DefaultSortingOrder)
|
||||
builder.WithDirection(sortingDirection)
|
||||
nextEntry, err := builder.GetEntry()
|
||||
if err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
builder = c.store.GetEntryQueryBuilder(user.ID, user.Timezone)
|
||||
builder.WithStatus(model.EntryStatusUnread)
|
||||
builder.WithCondition("e.id", "!=", entryID)
|
||||
builder.WithCondition("e.published_at", ">=", entry.Date)
|
||||
builder.WithOrder(model.DefaultSortingOrder)
|
||||
builder.WithDirection(model.GetOppositeDirection(sortingDirection))
|
||||
prevEntry, err := builder.GetEntry()
|
||||
if err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
nextEntryRoute := ""
|
||||
if nextEntry != nil {
|
||||
nextEntryRoute = ctx.GetRoute("unreadEntry", "entryID", nextEntry.ID)
|
||||
}
|
||||
|
||||
prevEntryRoute := ""
|
||||
if prevEntry != nil {
|
||||
prevEntryRoute = ctx.GetRoute("unreadEntry", "entryID", prevEntry.ID)
|
||||
}
|
||||
|
||||
if entry.Status == model.EntryStatusUnread {
|
||||
err = c.store.SetEntriesStatus(user.ID, []int64{entry.ID}, model.EntryStatusRead)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
response.Html().ServerError(nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
response.Html().Render("entry", args.Merge(tplParams{
|
||||
"entry": entry,
|
||||
"prevEntry": prevEntry,
|
||||
"nextEntry": nextEntry,
|
||||
"nextEntryRoute": nextEntryRoute,
|
||||
"prevEntryRoute": prevEntryRoute,
|
||||
"menu": "unread",
|
||||
}))
|
||||
}
|
||||
|
||||
func (c *Controller) ShowReadEntry(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
user := ctx.GetLoggedUser()
|
||||
sortingDirection := model.DefaultSortingDirection
|
||||
|
||||
entryID, err := request.GetIntegerParam("entryID")
|
||||
if err != nil {
|
||||
response.Html().BadRequest(err)
|
||||
return
|
||||
}
|
||||
|
||||
builder := c.store.GetEntryQueryBuilder(user.ID, user.Timezone)
|
||||
builder.WithEntryID(entryID)
|
||||
|
||||
entry, err := builder.GetEntry()
|
||||
if err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
if entry == nil {
|
||||
response.Html().NotFound()
|
||||
return
|
||||
}
|
||||
|
||||
args, err := c.getCommonTemplateArgs(ctx)
|
||||
if err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
builder = c.store.GetEntryQueryBuilder(user.ID, user.Timezone)
|
||||
builder.WithStatus(model.EntryStatusRead)
|
||||
builder.WithCondition("e.id", "!=", entryID)
|
||||
builder.WithCondition("e.published_at", "<=", entry.Date)
|
||||
builder.WithOrder(model.DefaultSortingOrder)
|
||||
builder.WithDirection(sortingDirection)
|
||||
nextEntry, err := builder.GetEntry()
|
||||
if err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
builder = c.store.GetEntryQueryBuilder(user.ID, user.Timezone)
|
||||
builder.WithStatus(model.EntryStatusRead)
|
||||
builder.WithCondition("e.id", "!=", entryID)
|
||||
builder.WithCondition("e.published_at", ">=", entry.Date)
|
||||
builder.WithOrder(model.DefaultSortingOrder)
|
||||
builder.WithDirection(model.GetOppositeDirection(sortingDirection))
|
||||
prevEntry, err := builder.GetEntry()
|
||||
if err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
nextEntryRoute := ""
|
||||
if nextEntry != nil {
|
||||
nextEntryRoute = ctx.GetRoute("readEntry", "entryID", nextEntry.ID)
|
||||
}
|
||||
|
||||
prevEntryRoute := ""
|
||||
if prevEntry != nil {
|
||||
prevEntryRoute = ctx.GetRoute("readEntry", "entryID", prevEntry.ID)
|
||||
}
|
||||
|
||||
response.Html().Render("entry", args.Merge(tplParams{
|
||||
"entry": entry,
|
||||
"prevEntry": prevEntry,
|
||||
"nextEntry": nextEntry,
|
||||
"nextEntryRoute": nextEntryRoute,
|
||||
"prevEntryRoute": prevEntryRoute,
|
||||
"menu": "history",
|
||||
}))
|
||||
}
|
||||
|
||||
func (c *Controller) UpdateEntriesStatus(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
user := ctx.GetLoggedUser()
|
||||
|
||||
entryIDs, status, err := payload.DecodeEntryStatusPayload(request.GetBody())
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
response.Json().BadRequest(nil)
|
||||
return
|
||||
}
|
||||
|
||||
if len(entryIDs) == 0 {
|
||||
response.Html().BadRequest(errors.New("The list of entryID is empty"))
|
||||
return
|
||||
}
|
||||
|
||||
err = c.store.SetEntriesStatus(user.ID, entryIDs, status)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
response.Html().ServerError(nil)
|
||||
return
|
||||
}
|
||||
|
||||
response.Json().Standard("OK")
|
||||
}
|
209
server/ui/controller/feed.go
Normal file
209
server/ui/controller/feed.go
Normal file
|
@ -0,0 +1,209 @@
|
|||
// 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 controller
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/miniflux/miniflux2/model"
|
||||
"github.com/miniflux/miniflux2/server/core"
|
||||
"github.com/miniflux/miniflux2/server/ui/form"
|
||||
"log"
|
||||
)
|
||||
|
||||
func (c *Controller) ShowFeedsPage(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
user := ctx.GetLoggedUser()
|
||||
|
||||
args, err := c.getCommonTemplateArgs(ctx)
|
||||
if err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
feeds, err := c.store.GetFeeds(user.ID)
|
||||
if err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
response.Html().Render("feeds", args.Merge(tplParams{
|
||||
"feeds": feeds,
|
||||
"total": len(feeds),
|
||||
"menu": "feeds",
|
||||
}))
|
||||
}
|
||||
|
||||
func (c *Controller) ShowFeedEntries(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
user := ctx.GetLoggedUser()
|
||||
offset := request.GetQueryIntegerParam("offset", 0)
|
||||
|
||||
args, err := c.getCommonTemplateArgs(ctx)
|
||||
if err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
feed, err := c.getFeedFromURL(request, response, user)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
builder := c.store.GetEntryQueryBuilder(user.ID, user.Timezone)
|
||||
builder.WithFeedID(feed.ID)
|
||||
builder.WithOrder(model.DefaultSortingOrder)
|
||||
builder.WithDirection(model.DefaultSortingDirection)
|
||||
builder.WithOffset(offset)
|
||||
builder.WithLimit(NbItemsPerPage)
|
||||
|
||||
entries, err := builder.GetEntries()
|
||||
if err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
count, err := builder.CountEntries()
|
||||
if err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
response.Html().Render("feed_entries", args.Merge(tplParams{
|
||||
"feed": feed,
|
||||
"entries": entries,
|
||||
"total": count,
|
||||
"pagination": c.getPagination(ctx.GetRoute("feedEntries", "feedID", feed.ID), count, offset),
|
||||
"menu": "feeds",
|
||||
}))
|
||||
}
|
||||
|
||||
func (c *Controller) EditFeed(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
user := ctx.GetLoggedUser()
|
||||
|
||||
feed, err := c.getFeedFromURL(request, response, user)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
args, err := c.getFeedFormTemplateArgs(ctx, user, feed, nil)
|
||||
if err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
response.Html().Render("edit_feed", args)
|
||||
}
|
||||
|
||||
func (c *Controller) UpdateFeed(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
user := ctx.GetLoggedUser()
|
||||
|
||||
feed, err := c.getFeedFromURL(request, response, user)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
feedForm := form.NewFeedForm(request.GetRequest())
|
||||
args, err := c.getFeedFormTemplateArgs(ctx, user, feed, feedForm)
|
||||
if err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := feedForm.ValidateModification(); err != nil {
|
||||
response.Html().Render("edit_feed", args.Merge(tplParams{
|
||||
"errorMessage": err.Error(),
|
||||
}))
|
||||
return
|
||||
}
|
||||
|
||||
err = c.store.UpdateFeed(feedForm.Merge(feed))
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
response.Html().Render("edit_feed", args.Merge(tplParams{
|
||||
"errorMessage": "Unable to update this feed.",
|
||||
}))
|
||||
return
|
||||
}
|
||||
|
||||
response.Redirect(ctx.GetRoute("feeds"))
|
||||
}
|
||||
|
||||
func (c *Controller) RemoveFeed(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
feedID, err := request.GetIntegerParam("feedID")
|
||||
if err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
user := ctx.GetLoggedUser()
|
||||
if err := c.store.RemoveFeed(user.ID, feedID); err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
response.Redirect(ctx.GetRoute("feeds"))
|
||||
}
|
||||
|
||||
func (c *Controller) RefreshFeed(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
feedID, err := request.GetIntegerParam("feedID")
|
||||
if err != nil {
|
||||
response.Html().BadRequest(err)
|
||||
return
|
||||
}
|
||||
|
||||
user := ctx.GetLoggedUser()
|
||||
if err := c.feedHandler.RefreshFeed(user.ID, feedID); err != nil {
|
||||
log.Println("[UI:RefreshFeed]", err)
|
||||
}
|
||||
|
||||
response.Redirect(ctx.GetRoute("feedEntries", "feedID", feedID))
|
||||
}
|
||||
|
||||
func (c *Controller) getFeedFromURL(request *core.Request, response *core.Response, user *model.User) (*model.Feed, error) {
|
||||
feedID, err := request.GetIntegerParam("feedID")
|
||||
if err != nil {
|
||||
response.Html().BadRequest(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
feed, err := c.store.GetFeedById(user.ID, feedID)
|
||||
if err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if feed == nil {
|
||||
response.Html().NotFound()
|
||||
return nil, errors.New("Feed not found")
|
||||
}
|
||||
|
||||
return feed, nil
|
||||
}
|
||||
|
||||
func (c *Controller) getFeedFormTemplateArgs(ctx *core.Context, user *model.User, feed *model.Feed, feedForm *form.FeedForm) (tplParams, error) {
|
||||
args, err := c.getCommonTemplateArgs(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
categories, err := c.store.GetCategories(user.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if feedForm == nil {
|
||||
args["form"] = form.FeedForm{
|
||||
SiteURL: feed.SiteURL,
|
||||
FeedURL: feed.FeedURL,
|
||||
Title: feed.Title,
|
||||
CategoryID: feed.Category.ID,
|
||||
}
|
||||
} else {
|
||||
args["form"] = feedForm
|
||||
}
|
||||
|
||||
args["categories"] = categories
|
||||
args["feed"] = feed
|
||||
args["menu"] = "feeds"
|
||||
return args, nil
|
||||
}
|
47
server/ui/controller/history.go
Normal file
47
server/ui/controller/history.go
Normal file
|
@ -0,0 +1,47 @@
|
|||
// 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 controller
|
||||
|
||||
import (
|
||||
"github.com/miniflux/miniflux2/model"
|
||||
"github.com/miniflux/miniflux2/server/core"
|
||||
)
|
||||
|
||||
func (c *Controller) ShowHistoryPage(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
user := ctx.GetLoggedUser()
|
||||
offset := request.GetQueryIntegerParam("offset", 0)
|
||||
|
||||
args, err := c.getCommonTemplateArgs(ctx)
|
||||
if err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
builder := c.store.GetEntryQueryBuilder(user.ID, user.Timezone)
|
||||
builder.WithStatus(model.EntryStatusRead)
|
||||
builder.WithOrder(model.DefaultSortingOrder)
|
||||
builder.WithDirection(model.DefaultSortingDirection)
|
||||
builder.WithOffset(offset)
|
||||
builder.WithLimit(NbItemsPerPage)
|
||||
|
||||
entries, err := builder.GetEntries()
|
||||
if err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
count, err := builder.CountEntries()
|
||||
if err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
response.Html().Render("history", args.Merge(tplParams{
|
||||
"entries": entries,
|
||||
"total": count,
|
||||
"pagination": c.getPagination(ctx.GetRoute("history"), count, offset),
|
||||
"menu": "history",
|
||||
}))
|
||||
}
|
31
server/ui/controller/icon.go
Normal file
31
server/ui/controller/icon.go
Normal file
|
@ -0,0 +1,31 @@
|
|||
// 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 controller
|
||||
|
||||
import (
|
||||
"github.com/miniflux/miniflux2/server/core"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (c *Controller) ShowIcon(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
iconID, err := request.GetIntegerParam("iconID")
|
||||
if err != nil {
|
||||
response.Html().BadRequest(err)
|
||||
return
|
||||
}
|
||||
|
||||
icon, err := c.store.GetIconByID(iconID)
|
||||
if err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
if icon == nil {
|
||||
response.Html().NotFound()
|
||||
return
|
||||
}
|
||||
|
||||
response.Cache(icon.MimeType, icon.Hash, icon.Content, 72*time.Hour)
|
||||
}
|
91
server/ui/controller/login.go
Normal file
91
server/ui/controller/login.go
Normal file
|
@ -0,0 +1,91 @@
|
|||
// 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 controller
|
||||
|
||||
import (
|
||||
"github.com/miniflux/miniflux2/server/core"
|
||||
"github.com/miniflux/miniflux2/server/ui/form"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/tomasen/realip"
|
||||
)
|
||||
|
||||
func (c *Controller) ShowLoginPage(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
if ctx.IsAuthenticated() {
|
||||
response.Redirect(ctx.GetRoute("unread"))
|
||||
return
|
||||
}
|
||||
|
||||
response.Html().Render("login", tplParams{
|
||||
"csrf": ctx.GetCsrfToken(),
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Controller) CheckLogin(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
authForm := form.NewAuthForm(request.GetRequest())
|
||||
tplParams := tplParams{
|
||||
"errorMessage": "Invalid username or password.",
|
||||
"csrf": ctx.GetCsrfToken(),
|
||||
}
|
||||
|
||||
if err := authForm.Validate(); err != nil {
|
||||
log.Println(err)
|
||||
response.Html().Render("login", tplParams)
|
||||
return
|
||||
}
|
||||
|
||||
if err := c.store.CheckPassword(authForm.Username, authForm.Password); err != nil {
|
||||
log.Println(err)
|
||||
response.Html().Render("login", tplParams)
|
||||
return
|
||||
}
|
||||
|
||||
sessionToken, err := c.store.CreateSession(
|
||||
authForm.Username,
|
||||
request.GetHeaders().Get("User-Agent"),
|
||||
realip.RealIP(request.GetRequest()),
|
||||
)
|
||||
if err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("[UI:CheckLogin] username=%s just logged in\n", authForm.Username)
|
||||
|
||||
cookie := &http.Cookie{
|
||||
Name: "sessionID",
|
||||
Value: sessionToken,
|
||||
Path: "/",
|
||||
Secure: request.IsHTTPS(),
|
||||
HttpOnly: true,
|
||||
}
|
||||
|
||||
response.SetCookie(cookie)
|
||||
response.Redirect(ctx.GetRoute("unread"))
|
||||
}
|
||||
|
||||
func (c *Controller) Logout(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
user := ctx.GetLoggedUser()
|
||||
|
||||
sessionCookie := request.GetCookie("sessionID")
|
||||
if err := c.store.RemoveSessionByToken(user.ID, sessionCookie); err != nil {
|
||||
log.Printf("[UI:Logout] %v", err)
|
||||
}
|
||||
|
||||
cookie := &http.Cookie{
|
||||
Name: "sessionID",
|
||||
Value: "",
|
||||
Path: "/",
|
||||
Secure: request.IsHTTPS(),
|
||||
HttpOnly: true,
|
||||
MaxAge: -1,
|
||||
Expires: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
}
|
||||
|
||||
response.SetCookie(cookie)
|
||||
response.Redirect(ctx.GetRoute("login"))
|
||||
}
|
63
server/ui/controller/opml.go
Normal file
63
server/ui/controller/opml.go
Normal file
|
@ -0,0 +1,63 @@
|
|||
// 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 controller
|
||||
|
||||
import (
|
||||
"github.com/miniflux/miniflux2/server/core"
|
||||
"log"
|
||||
)
|
||||
|
||||
func (c *Controller) Export(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
user := ctx.GetLoggedUser()
|
||||
opml, err := c.opmlHandler.Export(user.ID)
|
||||
if err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
response.Xml().Download("feeds.opml", opml)
|
||||
}
|
||||
|
||||
func (c *Controller) Import(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
args, err := c.getCommonTemplateArgs(ctx)
|
||||
if err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
response.Html().Render("import", args.Merge(tplParams{
|
||||
"menu": "feeds",
|
||||
}))
|
||||
}
|
||||
|
||||
func (c *Controller) UploadOPML(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
file, fileHeader, err := request.GetFile("file")
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
response.Redirect(ctx.GetRoute("import"))
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
user := ctx.GetLoggedUser()
|
||||
log.Printf("[UI:UploadOPML] User #%d uploaded this file: %s (%d bytes)\n", user.ID, fileHeader.Filename, fileHeader.Size)
|
||||
|
||||
if impErr := c.opmlHandler.Import(user.ID, file); impErr != nil {
|
||||
args, err := c.getCommonTemplateArgs(ctx)
|
||||
if err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
response.Html().Render("import", args.Merge(tplParams{
|
||||
"errorMessage": impErr.Error(),
|
||||
"menu": "feeds",
|
||||
}))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
response.Redirect(ctx.GetRoute("feeds"))
|
||||
}
|
46
server/ui/controller/pagination.go
Normal file
46
server/ui/controller/pagination.go
Normal file
|
@ -0,0 +1,46 @@
|
|||
// 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 controller
|
||||
|
||||
const (
|
||||
NbItemsPerPage = 100
|
||||
)
|
||||
|
||||
type Pagination struct {
|
||||
Route string
|
||||
Total int
|
||||
Offset int
|
||||
ItemsPerPage int
|
||||
ShowNext bool
|
||||
ShowPrev bool
|
||||
NextOffset int
|
||||
PrevOffset int
|
||||
}
|
||||
|
||||
func (c *Controller) getPagination(route string, total, offset int) Pagination {
|
||||
nextOffset := 0
|
||||
prevOffset := 0
|
||||
showNext := (total - offset) > NbItemsPerPage
|
||||
showPrev := offset > 0
|
||||
|
||||
if showNext {
|
||||
nextOffset = offset + NbItemsPerPage
|
||||
}
|
||||
|
||||
if showPrev {
|
||||
prevOffset = offset - NbItemsPerPage
|
||||
}
|
||||
|
||||
return Pagination{
|
||||
Route: route,
|
||||
Total: total,
|
||||
Offset: offset,
|
||||
ItemsPerPage: NbItemsPerPage,
|
||||
ShowNext: showNext,
|
||||
NextOffset: nextOffset,
|
||||
ShowPrev: showPrev,
|
||||
PrevOffset: prevOffset,
|
||||
}
|
||||
}
|
49
server/ui/controller/proxy.go
Normal file
49
server/ui/controller/proxy.go
Normal file
|
@ -0,0 +1,49 @@
|
|||
// 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 controller
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"github.com/miniflux/miniflux2/helper"
|
||||
"github.com/miniflux/miniflux2/server/core"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (c *Controller) ImageProxy(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
encodedURL := request.GetStringParam("encodedURL", "")
|
||||
if encodedURL == "" {
|
||||
response.Html().BadRequest(errors.New("No URL provided"))
|
||||
return
|
||||
}
|
||||
|
||||
decodedURL, err := base64.StdEncoding.DecodeString(encodedURL)
|
||||
if err != nil {
|
||||
response.Html().BadRequest(errors.New("Unable to decode this URL"))
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := http.Get(string(decodedURL))
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
response.Html().NotFound()
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
response.Html().NotFound()
|
||||
return
|
||||
}
|
||||
|
||||
body, _ := ioutil.ReadAll(resp.Body)
|
||||
etag := helper.HashFromBytes(body)
|
||||
contentType := resp.Header.Get("Content-Type")
|
||||
|
||||
response.Cache(contentType, etag, body, 72*time.Hour)
|
||||
}
|
49
server/ui/controller/session.go
Normal file
49
server/ui/controller/session.go
Normal file
|
@ -0,0 +1,49 @@
|
|||
// 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 controller
|
||||
|
||||
import (
|
||||
"github.com/miniflux/miniflux2/server/core"
|
||||
"log"
|
||||
)
|
||||
|
||||
func (c *Controller) ShowSessions(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
user := ctx.GetLoggedUser()
|
||||
args, err := c.getCommonTemplateArgs(ctx)
|
||||
if err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
sessions, err := c.store.GetSessions(user.ID)
|
||||
if err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
sessionCookie := request.GetCookie("sessionID")
|
||||
response.Html().Render("sessions", args.Merge(tplParams{
|
||||
"sessions": sessions,
|
||||
"currentSessionToken": sessionCookie,
|
||||
"menu": "settings",
|
||||
}))
|
||||
}
|
||||
|
||||
func (c *Controller) RemoveSession(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
user := ctx.GetLoggedUser()
|
||||
|
||||
sessionID, err := request.GetIntegerParam("sessionID")
|
||||
if err != nil {
|
||||
response.Html().BadRequest(err)
|
||||
return
|
||||
}
|
||||
|
||||
err = c.store.RemoveSessionByID(user.ID, sessionID)
|
||||
if err != nil {
|
||||
log.Println("[UI:RemoveSession]", err)
|
||||
}
|
||||
|
||||
response.Redirect(ctx.GetRoute("sessions"))
|
||||
}
|
92
server/ui/controller/settings.go
Normal file
92
server/ui/controller/settings.go
Normal file
|
@ -0,0 +1,92 @@
|
|||
// 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 controller
|
||||
|
||||
import (
|
||||
"github.com/miniflux/miniflux2/locale"
|
||||
"github.com/miniflux/miniflux2/model"
|
||||
"github.com/miniflux/miniflux2/server/core"
|
||||
"github.com/miniflux/miniflux2/server/ui/form"
|
||||
"log"
|
||||
)
|
||||
|
||||
func (c *Controller) ShowSettings(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
user := ctx.GetLoggedUser()
|
||||
|
||||
args, err := c.getSettingsFormTemplateArgs(ctx, user, nil)
|
||||
if err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
response.Html().Render("settings", args)
|
||||
}
|
||||
|
||||
func (c *Controller) UpdateSettings(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
user := ctx.GetLoggedUser()
|
||||
|
||||
settingsForm := form.NewSettingsForm(request.GetRequest())
|
||||
args, err := c.getSettingsFormTemplateArgs(ctx, user, settingsForm)
|
||||
if err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := settingsForm.Validate(); err != nil {
|
||||
response.Html().Render("settings", args.Merge(tplParams{
|
||||
"form": settingsForm,
|
||||
"errorMessage": err.Error(),
|
||||
}))
|
||||
return
|
||||
}
|
||||
|
||||
if c.store.AnotherUserExists(user.ID, settingsForm.Username) {
|
||||
response.Html().Render("settings", args.Merge(tplParams{
|
||||
"form": settingsForm,
|
||||
"errorMessage": "This user already exists.",
|
||||
}))
|
||||
return
|
||||
}
|
||||
|
||||
err = c.store.UpdateUser(settingsForm.Merge(user))
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
response.Html().Render("settings", args.Merge(tplParams{
|
||||
"form": settingsForm,
|
||||
"errorMessage": "Unable to update this user.",
|
||||
}))
|
||||
return
|
||||
}
|
||||
|
||||
response.Redirect(ctx.GetRoute("settings"))
|
||||
}
|
||||
|
||||
func (c *Controller) getSettingsFormTemplateArgs(ctx *core.Context, user *model.User, settingsForm *form.SettingsForm) (tplParams, error) {
|
||||
args, err := c.getCommonTemplateArgs(ctx)
|
||||
if err != nil {
|
||||
return args, err
|
||||
}
|
||||
|
||||
if settingsForm == nil {
|
||||
args["form"] = form.SettingsForm{
|
||||
Username: user.Username,
|
||||
Theme: user.Theme,
|
||||
Language: user.Language,
|
||||
Timezone: user.Timezone,
|
||||
}
|
||||
} else {
|
||||
args["form"] = settingsForm
|
||||
}
|
||||
|
||||
args["menu"] = "settings"
|
||||
args["themes"] = model.GetThemes()
|
||||
args["languages"] = locale.GetAvailableLanguages()
|
||||
args["timezones"], err = c.store.GetTimezones()
|
||||
if err != nil {
|
||||
return args, err
|
||||
}
|
||||
|
||||
return args, nil
|
||||
}
|
41
server/ui/controller/static.go
Normal file
41
server/ui/controller/static.go
Normal file
|
@ -0,0 +1,41 @@
|
|||
// 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 controller
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"github.com/miniflux/miniflux2/server/core"
|
||||
"github.com/miniflux/miniflux2/server/static"
|
||||
"log"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (c *Controller) Stylesheet(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
stylesheet := request.GetStringParam("name", "white")
|
||||
body := static.Stylesheets["common"]
|
||||
etag := static.StylesheetsChecksums["common"]
|
||||
|
||||
if theme, found := static.Stylesheets[stylesheet]; found {
|
||||
body += theme
|
||||
etag += static.StylesheetsChecksums[stylesheet]
|
||||
}
|
||||
|
||||
response.Cache("text/css", etag, []byte(body), 48*time.Hour)
|
||||
}
|
||||
|
||||
func (c *Controller) Javascript(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
response.Cache("text/javascript", static.JavascriptChecksums["app"], []byte(static.Javascript["app"]), 48*time.Hour)
|
||||
}
|
||||
|
||||
func (c *Controller) Favicon(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
blob, err := base64.StdEncoding.DecodeString(static.Binaries["favicon.ico"])
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
response.Html().NotFound()
|
||||
return
|
||||
}
|
||||
|
||||
response.Cache("image/x-icon", static.BinariesChecksums["favicon.ico"], blob, 48*time.Hour)
|
||||
}
|
127
server/ui/controller/subscription.go
Normal file
127
server/ui/controller/subscription.go
Normal file
|
@ -0,0 +1,127 @@
|
|||
// 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 controller
|
||||
|
||||
import (
|
||||
"github.com/miniflux/miniflux2/model"
|
||||
"github.com/miniflux/miniflux2/reader/subscription"
|
||||
"github.com/miniflux/miniflux2/server/core"
|
||||
"github.com/miniflux/miniflux2/server/ui/form"
|
||||
"log"
|
||||
)
|
||||
|
||||
func (c *Controller) AddSubscription(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
user := ctx.GetLoggedUser()
|
||||
|
||||
args, err := c.getSubscriptionFormTemplateArgs(ctx, user)
|
||||
if err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
response.Html().Render("add_subscription", args)
|
||||
}
|
||||
|
||||
func (c *Controller) SubmitSubscription(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
user := ctx.GetLoggedUser()
|
||||
|
||||
args, err := c.getSubscriptionFormTemplateArgs(ctx, user)
|
||||
if err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
subscriptionForm := form.NewSubscriptionForm(request.GetRequest())
|
||||
if err := subscriptionForm.Validate(); err != nil {
|
||||
response.Html().Render("add_subscription", args.Merge(tplParams{
|
||||
"form": subscriptionForm,
|
||||
"errorMessage": err.Error(),
|
||||
}))
|
||||
return
|
||||
}
|
||||
|
||||
subscriptions, err := subscription.FindSubscriptions(subscriptionForm.URL)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
response.Html().Render("add_subscription", args.Merge(tplParams{
|
||||
"form": subscriptionForm,
|
||||
"errorMessage": err,
|
||||
}))
|
||||
return
|
||||
}
|
||||
|
||||
log.Println("[UI:SubmitSubscription]", subscriptions)
|
||||
|
||||
n := len(subscriptions)
|
||||
switch {
|
||||
case n == 0:
|
||||
response.Html().Render("add_subscription", args.Merge(tplParams{
|
||||
"form": subscriptionForm,
|
||||
"errorMessage": "Unable to find any subscription.",
|
||||
}))
|
||||
case n == 1:
|
||||
feed, err := c.feedHandler.CreateFeed(user.ID, subscriptionForm.CategoryID, subscriptions[0].URL)
|
||||
if err != nil {
|
||||
response.Html().Render("add_subscription", args.Merge(tplParams{
|
||||
"form": subscriptionForm,
|
||||
"errorMessage": err,
|
||||
}))
|
||||
return
|
||||
}
|
||||
|
||||
response.Redirect(ctx.GetRoute("feedEntries", "feedID", feed.ID))
|
||||
case n > 1:
|
||||
response.Html().Render("choose_subscription", args.Merge(tplParams{
|
||||
"categoryID": subscriptionForm.CategoryID,
|
||||
"subscriptions": subscriptions,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Controller) ChooseSubscription(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
user := ctx.GetLoggedUser()
|
||||
|
||||
args, err := c.getSubscriptionFormTemplateArgs(ctx, user)
|
||||
if err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
subscriptionForm := form.NewSubscriptionForm(request.GetRequest())
|
||||
if err := subscriptionForm.Validate(); err != nil {
|
||||
response.Html().Render("add_subscription", args.Merge(tplParams{
|
||||
"form": subscriptionForm,
|
||||
"errorMessage": err.Error(),
|
||||
}))
|
||||
return
|
||||
}
|
||||
|
||||
feed, err := c.feedHandler.CreateFeed(user.ID, subscriptionForm.CategoryID, subscriptionForm.URL)
|
||||
if err != nil {
|
||||
response.Html().Render("add_subscription", args.Merge(tplParams{
|
||||
"form": subscriptionForm,
|
||||
"errorMessage": err,
|
||||
}))
|
||||
return
|
||||
}
|
||||
|
||||
response.Redirect(ctx.GetRoute("feedEntries", "feedID", feed.ID))
|
||||
}
|
||||
|
||||
func (c *Controller) getSubscriptionFormTemplateArgs(ctx *core.Context, user *model.User) (tplParams, error) {
|
||||
args, err := c.getCommonTemplateArgs(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
categories, err := c.store.GetCategories(user.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
args["categories"] = categories
|
||||
args["menu"] = "feeds"
|
||||
return args, nil
|
||||
}
|
43
server/ui/controller/unread.go
Normal file
43
server/ui/controller/unread.go
Normal file
|
@ -0,0 +1,43 @@
|
|||
// 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 controller
|
||||
|
||||
import (
|
||||
"github.com/miniflux/miniflux2/model"
|
||||
"github.com/miniflux/miniflux2/server/core"
|
||||
)
|
||||
|
||||
func (c *Controller) ShowUnreadPage(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
user := ctx.GetLoggedUser()
|
||||
offset := request.GetQueryIntegerParam("offset", 0)
|
||||
|
||||
builder := c.store.GetEntryQueryBuilder(user.ID, user.Timezone)
|
||||
builder.WithStatus(model.EntryStatusUnread)
|
||||
builder.WithOrder(model.DefaultSortingOrder)
|
||||
builder.WithDirection(model.DefaultSortingDirection)
|
||||
builder.WithOffset(offset)
|
||||
builder.WithLimit(NbItemsPerPage)
|
||||
|
||||
entries, err := builder.GetEntries()
|
||||
if err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
countUnread, err := builder.CountEntries()
|
||||
if err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
response.Html().Render("unread", tplParams{
|
||||
"user": user,
|
||||
"countUnread": countUnread,
|
||||
"entries": entries,
|
||||
"pagination": c.getPagination(ctx.GetRoute("unread"), countUnread, offset),
|
||||
"menu": "unread",
|
||||
"csrf": ctx.GetCsrfToken(),
|
||||
})
|
||||
}
|
231
server/ui/controller/user.go
Normal file
231
server/ui/controller/user.go
Normal file
|
@ -0,0 +1,231 @@
|
|||
// 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 controller
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/miniflux/miniflux2/model"
|
||||
"github.com/miniflux/miniflux2/server/core"
|
||||
"github.com/miniflux/miniflux2/server/ui/form"
|
||||
"log"
|
||||
)
|
||||
|
||||
func (c *Controller) ShowUsers(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
user := ctx.GetLoggedUser()
|
||||
|
||||
if !user.IsAdmin {
|
||||
response.Html().Forbidden()
|
||||
return
|
||||
}
|
||||
|
||||
args, err := c.getCommonTemplateArgs(ctx)
|
||||
if err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
users, err := c.store.GetUsers()
|
||||
if err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
response.Html().Render("users", args.Merge(tplParams{
|
||||
"users": users,
|
||||
"menu": "settings",
|
||||
}))
|
||||
}
|
||||
|
||||
func (c *Controller) CreateUser(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
user := ctx.GetLoggedUser()
|
||||
|
||||
if !user.IsAdmin {
|
||||
response.Html().Forbidden()
|
||||
return
|
||||
}
|
||||
|
||||
args, err := c.getCommonTemplateArgs(ctx)
|
||||
if err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
response.Html().Render("create_user", args.Merge(tplParams{
|
||||
"menu": "settings",
|
||||
"form": &form.UserForm{},
|
||||
}))
|
||||
}
|
||||
|
||||
func (c *Controller) SaveUser(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
user := ctx.GetLoggedUser()
|
||||
|
||||
if !user.IsAdmin {
|
||||
response.Html().Forbidden()
|
||||
return
|
||||
}
|
||||
|
||||
args, err := c.getCommonTemplateArgs(ctx)
|
||||
if err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
userForm := form.NewUserForm(request.GetRequest())
|
||||
if err := userForm.ValidateCreation(); err != nil {
|
||||
response.Html().Render("create_user", args.Merge(tplParams{
|
||||
"menu": "settings",
|
||||
"form": userForm,
|
||||
"errorMessage": err.Error(),
|
||||
}))
|
||||
return
|
||||
}
|
||||
|
||||
if c.store.UserExists(userForm.Username) {
|
||||
response.Html().Render("create_user", args.Merge(tplParams{
|
||||
"menu": "settings",
|
||||
"form": userForm,
|
||||
"errorMessage": "This user already exists.",
|
||||
}))
|
||||
return
|
||||
}
|
||||
|
||||
newUser := userForm.ToUser()
|
||||
if err := c.store.CreateUser(newUser); err != nil {
|
||||
log.Println(err)
|
||||
response.Html().Render("edit_user", args.Merge(tplParams{
|
||||
"menu": "settings",
|
||||
"form": userForm,
|
||||
"errorMessage": "Unable to create this user.",
|
||||
}))
|
||||
return
|
||||
}
|
||||
|
||||
response.Redirect(ctx.GetRoute("users"))
|
||||
}
|
||||
|
||||
func (c *Controller) EditUser(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
user := ctx.GetLoggedUser()
|
||||
|
||||
if !user.IsAdmin {
|
||||
response.Html().Forbidden()
|
||||
return
|
||||
}
|
||||
|
||||
args, err := c.getCommonTemplateArgs(ctx)
|
||||
if err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
selectedUser, err := c.getUserFromURL(ctx, request, response)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
response.Html().Render("edit_user", args.Merge(tplParams{
|
||||
"menu": "settings",
|
||||
"selected_user": selectedUser,
|
||||
"form": &form.UserForm{
|
||||
Username: selectedUser.Username,
|
||||
IsAdmin: selectedUser.IsAdmin,
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
func (c *Controller) UpdateUser(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
user := ctx.GetLoggedUser()
|
||||
|
||||
if !user.IsAdmin {
|
||||
response.Html().Forbidden()
|
||||
return
|
||||
}
|
||||
|
||||
args, err := c.getCommonTemplateArgs(ctx)
|
||||
if err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
selectedUser, err := c.getUserFromURL(ctx, request, response)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
userForm := form.NewUserForm(request.GetRequest())
|
||||
if err := userForm.ValidateModification(); err != nil {
|
||||
response.Html().Render("edit_user", args.Merge(tplParams{
|
||||
"menu": "settings",
|
||||
"selected_user": selectedUser,
|
||||
"form": userForm,
|
||||
"errorMessage": err.Error(),
|
||||
}))
|
||||
return
|
||||
}
|
||||
|
||||
if c.store.AnotherUserExists(selectedUser.ID, userForm.Username) {
|
||||
response.Html().Render("edit_user", args.Merge(tplParams{
|
||||
"menu": "settings",
|
||||
"selected_user": selectedUser,
|
||||
"form": userForm,
|
||||
"errorMessage": "This user already exists.",
|
||||
}))
|
||||
return
|
||||
}
|
||||
|
||||
userForm.Merge(selectedUser)
|
||||
if err := c.store.UpdateUser(selectedUser); err != nil {
|
||||
log.Println(err)
|
||||
response.Html().Render("edit_user", args.Merge(tplParams{
|
||||
"menu": "settings",
|
||||
"selected_user": selectedUser,
|
||||
"form": userForm,
|
||||
"errorMessage": "Unable to update this user.",
|
||||
}))
|
||||
return
|
||||
}
|
||||
|
||||
response.Redirect(ctx.GetRoute("users"))
|
||||
}
|
||||
|
||||
func (c *Controller) RemoveUser(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
user := ctx.GetLoggedUser()
|
||||
if !user.IsAdmin {
|
||||
response.Html().Forbidden()
|
||||
return
|
||||
}
|
||||
|
||||
selectedUser, err := c.getUserFromURL(ctx, request, response)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := c.store.RemoveUser(selectedUser.ID); err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
response.Redirect(ctx.GetRoute("users"))
|
||||
}
|
||||
|
||||
func (c *Controller) getUserFromURL(ctx *core.Context, request *core.Request, response *core.Response) (*model.User, error) {
|
||||
userID, err := request.GetIntegerParam("userID")
|
||||
if err != nil {
|
||||
response.Html().BadRequest(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
user, err := c.store.GetUserById(userID)
|
||||
if err != nil {
|
||||
response.Html().ServerError(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if user == nil {
|
||||
response.Html().NotFound()
|
||||
return nil, errors.New("User not found")
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue