1
0
Fork 0
mirror of https://github.com/miniflux/v2.git synced 2025-08-01 17:38:37 +00:00

First commit

This commit is contained in:
Frédéric Guillot 2017-11-19 21:10:04 -08:00
commit 8ffb773f43
2121 changed files with 1118910 additions and 0 deletions

View 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",
}))
}

View 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
}

View 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,
}
}

View 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")
}

View 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
}

View 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",
}))
}

View 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)
}

View 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"))
}

View 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"))
}

View 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,
}
}

View 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)
}

View 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"))
}

View 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
}

View 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)
}

View 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
}

View 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(),
})
}

View 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
}