diff options
Diffstat (limited to 'server/ui')
30 files changed, 0 insertions, 2939 deletions
diff --git a/server/ui/controller/about.go b/server/ui/controller/about.go deleted file mode 100644 index d6bfc279..00000000 --- a/server/ui/controller/about.go +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2017 Frédéric Guillot. All rights reserved. -// Use of this source code is governed by the Apache 2.0 -// license that can be found in the LICENSE file. - -package controller - -import ( - "github.com/miniflux/miniflux/server/core" - "github.com/miniflux/miniflux/version" -) - -// AboutPage shows the about page. -func (c *Controller) AboutPage(ctx *core.Context, request *core.Request, response *core.Response) { - args, err := c.getCommonTemplateArgs(ctx) - if err != nil { - response.HTML().ServerError(err) - return - } - - response.HTML().Render("about", args.Merge(tplParams{ - "version": version.Version, - "build_date": version.BuildDate, - "menu": "settings", - })) -} diff --git a/server/ui/controller/category.go b/server/ui/controller/category.go deleted file mode 100644 index cf378c6d..00000000 --- a/server/ui/controller/category.go +++ /dev/null @@ -1,257 +0,0 @@ -// Copyright 2017 Frédéric Guillot. All rights reserved. -// Use of this source code is governed by the Apache 2.0 -// license that can be found in the LICENSE file. - -package controller - -import ( - "errors" - - "github.com/miniflux/miniflux/logger" - "github.com/miniflux/miniflux/model" - "github.com/miniflux/miniflux/server/core" - "github.com/miniflux/miniflux/server/ui/form" -) - -// ShowCategories shows the page with all categories. -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.LoggedUser() - categories, err := c.store.CategoriesWithFeedCount(user.ID) - if err != nil { - response.HTML().ServerError(err) - return - } - - response.HTML().Render("categories", args.Merge(tplParams{ - "categories": categories, - "total": len(categories), - "menu": "categories", - })) -} - -// ShowCategoryEntries shows all entries for the given category. -func (c *Controller) ShowCategoryEntries(ctx *core.Context, request *core.Request, response *core.Response) { - user := ctx.LoggedUser() - offset := request.QueryIntegerParam("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.NewEntryQueryBuilder(user.ID) - builder.WithCategoryID(category.ID) - builder.WithOrder(model.DefaultSortingOrder) - builder.WithDirection(user.EntryDirection) - builder.WithoutStatus(model.EntryStatusRemoved) - 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.Route("categoryEntries", "categoryID", category.ID), count, offset), - "menu": "categories", - })) -} - -// CreateCategory shows the form to create a new category. -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", - })) -} - -// SaveCategory validate and save the new category into the database. -func (c *Controller) SaveCategory(ctx *core.Context, request *core.Request, response *core.Response) { - user := ctx.LoggedUser() - args, err := c.getCommonTemplateArgs(ctx) - if err != nil { - response.HTML().ServerError(err) - return - } - - categoryForm := form.NewCategoryForm(request.Request()) - if err := categoryForm.Validate(); err != nil { - response.HTML().Render("create_category", args.Merge(tplParams{ - "errorMessage": err.Error(), - })) - return - } - - duplicateCategory, err := c.store.CategoryByTitle(user.ID, categoryForm.Title) - if err != nil { - response.HTML().ServerError(err) - return - } - - if duplicateCategory != nil { - response.HTML().Render("create_category", args.Merge(tplParams{ - "errorMessage": "This category already exists.", - })) - return - } - - category := model.Category{Title: categoryForm.Title, UserID: user.ID} - err = c.store.CreateCategory(&category) - if err != nil { - logger.Info("[Controller:CreateCategory] %v", err) - response.HTML().Render("create_category", args.Merge(tplParams{ - "errorMessage": "Unable to create this category.", - })) - return - } - - response.Redirect(ctx.Route("categories")) -} - -// EditCategory shows the form to modify a category. -func (c *Controller) EditCategory(ctx *core.Context, request *core.Request, response *core.Response) { - user := ctx.LoggedUser() - - category, err := c.getCategoryFromURL(ctx, request, response) - if err != nil { - logger.Error("[Controller:EditCategory] %v", err) - return - } - - args, err := c.getCategoryFormTemplateArgs(ctx, user, category, nil) - if err != nil { - response.HTML().ServerError(err) - return - } - - response.HTML().Render("edit_category", args) -} - -// UpdateCategory validate and update a category. -func (c *Controller) UpdateCategory(ctx *core.Context, request *core.Request, response *core.Response) { - user := ctx.LoggedUser() - - category, err := c.getCategoryFromURL(ctx, request, response) - if err != nil { - logger.Error("[Controller:UpdateCategory] %v", err) - return - } - - categoryForm := form.NewCategoryForm(request.Request()) - 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 - } - - if c.store.AnotherCategoryExists(user.ID, category.ID, categoryForm.Title) { - response.HTML().Render("edit_category", args.Merge(tplParams{ - "errorMessage": "This category already exists.", - })) - return - } - - err = c.store.UpdateCategory(categoryForm.Merge(category)) - if err != nil { - logger.Error("[Controller:UpdateCategory] %v", err) - response.HTML().Render("edit_category", args.Merge(tplParams{ - "errorMessage": "Unable to update this category.", - })) - return - } - - response.Redirect(ctx.Route("categories")) -} - -// RemoveCategory delete a category from the database. -func (c *Controller) RemoveCategory(ctx *core.Context, request *core.Request, response *core.Response) { - user := ctx.LoggedUser() - - 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.Route("categories")) -} - -func (c *Controller) getCategoryFromURL(ctx *core.Context, request *core.Request, response *core.Response) (*model.Category, error) { - categoryID, err := request.IntegerParam("categoryID") - if err != nil { - response.HTML().BadRequest(err) - return nil, err - } - - user := ctx.LoggedUser() - category, err := c.store.Category(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 -} diff --git a/server/ui/controller/controller.go b/server/ui/controller/controller.go deleted file mode 100644 index 8555c7b8..00000000 --- a/server/ui/controller/controller.go +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2017 Frédéric Guillot. All rights reserved. -// Use of this source code is governed by the Apache 2.0 -// license that can be found in the LICENSE file. - -package controller - -import ( - "github.com/miniflux/miniflux/config" - "github.com/miniflux/miniflux/model" - "github.com/miniflux/miniflux/reader/feed" - "github.com/miniflux/miniflux/reader/opml" - "github.com/miniflux/miniflux/scheduler" - "github.com/miniflux/miniflux/server/core" - "github.com/miniflux/miniflux/storage" -) - -type tplParams map[string]interface{} - -func (t tplParams) Merge(d tplParams) tplParams { - for k, v := range d { - t[k] = v - } - - return t -} - -// Controller contains all HTTP handlers for the user interface. -type Controller struct { - cfg *config.Config - store *storage.Storage - pool *scheduler.WorkerPool - feedHandler *feed.Handler - opmlHandler *opml.Handler -} - -func (c *Controller) getCommonTemplateArgs(ctx *core.Context) (tplParams, error) { - user := ctx.LoggedUser() - builder := c.store.NewEntryQueryBuilder(user.ID) - builder.WithStatus(model.EntryStatusUnread) - - countUnread, err := builder.CountEntries() - if err != nil { - return nil, err - } - - params := tplParams{ - "menu": "", - "user": user, - "countUnread": countUnread, - "csrf": ctx.CSRF(), - "flashMessage": ctx.FlashMessage(), - "flashErrorMessage": ctx.FlashErrorMessage(), - } - return params, nil -} - -// NewController returns a new Controller. -func NewController(cfg *config.Config, store *storage.Storage, pool *scheduler.WorkerPool, feedHandler *feed.Handler, opmlHandler *opml.Handler) *Controller { - return &Controller{ - cfg: cfg, - store: store, - pool: pool, - feedHandler: feedHandler, - opmlHandler: opmlHandler, - } -} diff --git a/server/ui/controller/entry.go b/server/ui/controller/entry.go deleted file mode 100644 index ca9f44af..00000000 --- a/server/ui/controller/entry.go +++ /dev/null @@ -1,495 +0,0 @@ -// Copyright 2017 Frédéric Guillot. All rights reserved. -// Use of this source code is governed by the Apache 2.0 -// license that can be found in the LICENSE file. - -package controller - -import ( - "errors" - - "github.com/miniflux/miniflux/logger" - "github.com/miniflux/miniflux/reader/sanitizer" - - "github.com/miniflux/miniflux/integration" - "github.com/miniflux/miniflux/model" - "github.com/miniflux/miniflux/reader/scraper" - "github.com/miniflux/miniflux/server/core" - "github.com/miniflux/miniflux/server/ui/payload" - "github.com/miniflux/miniflux/storage" -) - -// FetchContent downloads the original HTML page and returns relevant contents. -func (c *Controller) FetchContent(ctx *core.Context, request *core.Request, response *core.Response) { - entryID, err := request.IntegerParam("entryID") - if err != nil { - response.HTML().BadRequest(err) - return - } - - user := ctx.LoggedUser() - builder := c.store.NewEntryQueryBuilder(user.ID) - builder.WithEntryID(entryID) - builder.WithoutStatus(model.EntryStatusRemoved) - - entry, err := builder.GetEntry() - if err != nil { - response.JSON().ServerError(err) - return - } - - if entry == nil { - response.JSON().NotFound(errors.New("Entry not found")) - return - } - - content, err := scraper.Fetch(entry.URL, entry.Feed.ScraperRules) - if err != nil { - response.JSON().ServerError(err) - return - } - - entry.Content = sanitizer.Sanitize(entry.URL, content) - c.store.UpdateEntryContent(entry) - - response.JSON().Created(map[string]string{"content": entry.Content}) -} - -// SaveEntry send the link to external services. -func (c *Controller) SaveEntry(ctx *core.Context, request *core.Request, response *core.Response) { - entryID, err := request.IntegerParam("entryID") - if err != nil { - response.HTML().BadRequest(err) - return - } - - user := ctx.LoggedUser() - builder := c.store.NewEntryQueryBuilder(user.ID) - builder.WithEntryID(entryID) - builder.WithoutStatus(model.EntryStatusRemoved) - - entry, err := builder.GetEntry() - if err != nil { - response.JSON().ServerError(err) - return - } - - if entry == nil { - response.JSON().NotFound(errors.New("Entry not found")) - return - } - - settings, err := c.store.Integration(user.ID) - if err != nil { - response.JSON().ServerError(err) - return - } - - go func() { - integration.SendEntry(entry, settings) - }() - - response.JSON().Created(map[string]string{"message": "saved"}) -} - -// ShowFeedEntry shows a single feed entry in "feed" mode. -func (c *Controller) ShowFeedEntry(ctx *core.Context, request *core.Request, response *core.Response) { - user := ctx.LoggedUser() - - entryID, err := request.IntegerParam("entryID") - if err != nil { - response.HTML().BadRequest(err) - return - } - - feedID, err := request.IntegerParam("feedID") - if err != nil { - response.HTML().BadRequest(err) - return - } - - builder := c.store.NewEntryQueryBuilder(user.ID) - builder.WithFeedID(feedID) - builder.WithEntryID(entryID) - builder.WithoutStatus(model.EntryStatusRemoved) - - entry, err := builder.GetEntry() - if err != nil { - response.HTML().ServerError(err) - return - } - - if entry == nil { - response.HTML().NotFound() - return - } - - if entry.Status == model.EntryStatusUnread { - err = c.store.SetEntriesStatus(user.ID, []int64{entry.ID}, model.EntryStatusRead) - if err != nil { - logger.Error("[Controller:ShowFeedEntry] %v", err) - response.HTML().ServerError(nil) - return - } - } - - args, err := c.getCommonTemplateArgs(ctx) - if err != nil { - response.HTML().ServerError(err) - return - } - - builder = c.store.NewEntryQueryBuilder(user.ID) - builder.WithFeedID(feedID) - - prevEntry, nextEntry, err := c.getEntryPrevNext(user, builder, entry.ID) - if err != nil { - response.HTML().ServerError(err) - return - } - - nextEntryRoute := "" - if nextEntry != nil { - nextEntryRoute = ctx.Route("feedEntry", "feedID", feedID, "entryID", nextEntry.ID) - } - - prevEntryRoute := "" - if prevEntry != nil { - prevEntryRoute = ctx.Route("feedEntry", "feedID", feedID, "entryID", prevEntry.ID) - } - - response.HTML().Render("entry", args.Merge(tplParams{ - "entry": entry, - "prevEntry": prevEntry, - "nextEntry": nextEntry, - "nextEntryRoute": nextEntryRoute, - "prevEntryRoute": prevEntryRoute, - "menu": "feeds", - })) -} - -// ShowCategoryEntry shows a single feed entry in "category" mode. -func (c *Controller) ShowCategoryEntry(ctx *core.Context, request *core.Request, response *core.Response) { - user := ctx.LoggedUser() - - categoryID, err := request.IntegerParam("categoryID") - if err != nil { - response.HTML().BadRequest(err) - return - } - - entryID, err := request.IntegerParam("entryID") - if err != nil { - response.HTML().BadRequest(err) - return - } - - builder := c.store.NewEntryQueryBuilder(user.ID) - builder.WithCategoryID(categoryID) - builder.WithEntryID(entryID) - builder.WithoutStatus(model.EntryStatusRemoved) - - entry, err := builder.GetEntry() - if err != nil { - response.HTML().ServerError(err) - return - } - - if entry == nil { - response.HTML().NotFound() - return - } - - if entry.Status == model.EntryStatusUnread { - err = c.store.SetEntriesStatus(user.ID, []int64{entry.ID}, model.EntryStatusRead) - if err != nil { - logger.Error("[Controller:ShowCategoryEntry] %v", err) - response.HTML().ServerError(nil) - return - } - } - - args, err := c.getCommonTemplateArgs(ctx) - if err != nil { - response.HTML().ServerError(err) - return - } - - builder = c.store.NewEntryQueryBuilder(user.ID) - builder.WithCategoryID(categoryID) - - prevEntry, nextEntry, err := c.getEntryPrevNext(user, builder, entry.ID) - if err != nil { - response.HTML().ServerError(err) - return - } - - nextEntryRoute := "" - if nextEntry != nil { - nextEntryRoute = ctx.Route("categoryEntry", "categoryID", categoryID, "entryID", nextEntry.ID) - } - - prevEntryRoute := "" - if prevEntry != nil { - prevEntryRoute = ctx.Route("categoryEntry", "categoryID", categoryID, "entryID", prevEntry.ID) - } - - response.HTML().Render("entry", args.Merge(tplParams{ - "entry": entry, - "prevEntry": prevEntry, - "nextEntry": nextEntry, - "nextEntryRoute": nextEntryRoute, - "prevEntryRoute": prevEntryRoute, - "menu": "categories", - })) -} - -// ShowUnreadEntry shows a single feed entry in "unread" mode. -func (c *Controller) ShowUnreadEntry(ctx *core.Context, request *core.Request, response *core.Response) { - user := ctx.LoggedUser() - - entryID, err := request.IntegerParam("entryID") - if err != nil { - response.HTML().BadRequest(err) - return - } - - builder := c.store.NewEntryQueryBuilder(user.ID) - builder.WithEntryID(entryID) - builder.WithoutStatus(model.EntryStatusRemoved) - - 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.NewEntryQueryBuilder(user.ID) - builder.WithStatus(model.EntryStatusUnread) - - prevEntry, nextEntry, err := c.getEntryPrevNext(user, builder, entry.ID) - if err != nil { - response.HTML().ServerError(err) - return - } - - nextEntryRoute := "" - if nextEntry != nil { - nextEntryRoute = ctx.Route("unreadEntry", "entryID", nextEntry.ID) - } - - prevEntryRoute := "" - if prevEntry != nil { - prevEntryRoute = ctx.Route("unreadEntry", "entryID", prevEntry.ID) - } - - // We change the status here, otherwise we cannot get the pagination for unread items. - if entry.Status == model.EntryStatusUnread { - err = c.store.SetEntriesStatus(user.ID, []int64{entry.ID}, model.EntryStatusRead) - if err != nil { - logger.Error("[Controller:ShowUnreadEntry] %v", 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", - })) -} - -// ShowReadEntry shows a single feed entry in "history" mode. -func (c *Controller) ShowReadEntry(ctx *core.Context, request *core.Request, response *core.Response) { - user := ctx.LoggedUser() - - entryID, err := request.IntegerParam("entryID") - if err != nil { - response.HTML().BadRequest(err) - return - } - - builder := c.store.NewEntryQueryBuilder(user.ID) - builder.WithEntryID(entryID) - builder.WithoutStatus(model.EntryStatusRemoved) - - 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.NewEntryQueryBuilder(user.ID) - builder.WithStatus(model.EntryStatusRead) - - prevEntry, nextEntry, err := c.getEntryPrevNext(user, builder, entry.ID) - if err != nil { - response.HTML().ServerError(err) - return - } - - nextEntryRoute := "" - if nextEntry != nil { - nextEntryRoute = ctx.Route("readEntry", "entryID", nextEntry.ID) - } - - prevEntryRoute := "" - if prevEntry != nil { - prevEntryRoute = ctx.Route("readEntry", "entryID", prevEntry.ID) - } - - response.HTML().Render("entry", args.Merge(tplParams{ - "entry": entry, - "prevEntry": prevEntry, - "nextEntry": nextEntry, - "nextEntryRoute": nextEntryRoute, - "prevEntryRoute": prevEntryRoute, - "menu": "history", - })) -} - -// ShowStarredEntry shows a single feed entry in "starred" mode. -func (c *Controller) ShowStarredEntry(ctx *core.Context, request *core.Request, response *core.Response) { - user := ctx.LoggedUser() - - entryID, err := request.IntegerParam("entryID") - if err != nil { - response.HTML().BadRequest(err) - return - } - - builder := c.store.NewEntryQueryBuilder(user.ID) - builder.WithEntryID(entryID) - builder.WithoutStatus(model.EntryStatusRemoved) - - entry, err := builder.GetEntry() - if err != nil { - response.HTML().ServerError(err) - return - } - - if entry == nil { - response.HTML().NotFound() - return - } - - if entry.Status == model.EntryStatusUnread { - err = c.store.SetEntriesStatus(user.ID, []int64{entry.ID}, model.EntryStatusRead) - if err != nil { - logger.Error("[Controller:ShowReadEntry] %v", err) - response.HTML().ServerError(nil) - return - } - } - - args, err := c.getCommonTemplateArgs(ctx) - if err != nil { - response.HTML().ServerError(err) - return - } - - builder = c.store.NewEntryQueryBuilder(user.ID) - builder.WithStarred() - - prevEntry, nextEntry, err := c.getEntryPrevNext(user, builder, entry.ID) - if err != nil { - response.HTML().ServerError(err) - return - } - - nextEntryRoute := "" - if nextEntry != nil { - nextEntryRoute = ctx.Route("starredEntry", "entryID", nextEntry.ID) - } - - prevEntryRoute := "" - if prevEntry != nil { - prevEntryRoute = ctx.Route("starredEntry", "entryID", prevEntry.ID) - } - - response.HTML().Render("entry", args.Merge(tplParams{ - "entry": entry, - "prevEntry": prevEntry, - "nextEntry": nextEntry, - "nextEntryRoute": nextEntryRoute, - "prevEntryRoute": prevEntryRoute, - "menu": "starred", - })) -} - -// UpdateEntriesStatus handles Ajax request to update the status for a list of entries. -func (c *Controller) UpdateEntriesStatus(ctx *core.Context, request *core.Request, response *core.Response) { - user := ctx.LoggedUser() - - entryIDs, status, err := payload.DecodeEntryStatusPayload(request.Body()) - if err != nil { - logger.Error("[Controller:UpdateEntryStatus] %v", err) - response.JSON().BadRequest(nil) - return - } - - if len(entryIDs) == 0 { - response.JSON().BadRequest(errors.New("The list of entryID is empty")) - return - } - - err = c.store.SetEntriesStatus(user.ID, entryIDs, status) - if err != nil { - logger.Error("[Controller:UpdateEntryStatus] %v", err) - response.JSON().ServerError(nil) - return - } - - response.JSON().Standard("OK") -} - -func (c *Controller) getEntryPrevNext(user *model.User, builder *storage.EntryQueryBuilder, entryID int64) (prev *model.Entry, next *model.Entry, err error) { - builder.WithoutStatus(model.EntryStatusRemoved) - builder.WithOrder(model.DefaultSortingOrder) - builder.WithDirection(user.EntryDirection) - - entries, err := builder.GetEntries() - if err != nil { - return nil, nil, err - } - - n := len(entries) - for i := 0; i < n; i++ { - if entries[i].ID == entryID { - if i-1 >= 0 { - prev = entries[i-1] - } - - if i+1 < n { - next = entries[i+1] - } - } - } - - return prev, next, nil -} diff --git a/server/ui/controller/feed.go b/server/ui/controller/feed.go deleted file mode 100644 index 7dfc56e5..00000000 --- a/server/ui/controller/feed.go +++ /dev/null @@ -1,236 +0,0 @@ -// Copyright 2017 Frédéric Guillot. All rights reserved. -// Use of this source code is governed by the Apache 2.0 -// license that can be found in the LICENSE file. - -package controller - -import ( - "errors" - - "github.com/miniflux/miniflux/logger" - "github.com/miniflux/miniflux/model" - "github.com/miniflux/miniflux/server/core" - "github.com/miniflux/miniflux/server/ui/form" -) - -// RefreshAllFeeds refresh all feeds in the background for the current user. -func (c *Controller) RefreshAllFeeds(ctx *core.Context, request *core.Request, response *core.Response) { - user := ctx.LoggedUser() - jobs, err := c.store.NewUserBatch(user.ID, c.store.CountFeeds(user.ID)) - if err != nil { - response.HTML().ServerError(err) - return - } - - go func() { - c.pool.Push(jobs) - }() - - response.Redirect(ctx.Route("feeds")) -} - -// ShowFeedsPage shows the page with all subscriptions. -func (c *Controller) ShowFeedsPage(ctx *core.Context, request *core.Request, response *core.Response) { - user := ctx.LoggedUser() - - args, err := c.getCommonTemplateArgs(ctx) - if err != nil { - response.HTML().ServerError(err) - return - } - - feeds, err := c.store.Feeds(user.ID) - if err != nil { - response.HTML().ServerError(err) - return - } - - response.HTML().Render("feeds", args.Merge(tplParams{ - "feeds": feeds, - "total": len(feeds), - "menu": "feeds", - })) -} - -// ShowFeedEntries shows all entries for the given feed. -func (c *Controller) ShowFeedEntries(ctx *core.Context, request *core.Request, response *core.Response) { - user := ctx.LoggedUser() - offset := request.QueryIntegerParam("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.NewEntryQueryBuilder(user.ID) - builder.WithFeedID(feed.ID) - builder.WithoutStatus(model.EntryStatusRemoved) - builder.WithOrder(model.DefaultSortingOrder) - builder.WithDirection(user.EntryDirection) - 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.Route("feedEntries", "feedID", feed.ID), count, offset), - "menu": "feeds", - })) -} - -// EditFeed shows the form to modify a subscription. -func (c *Controller) EditFeed(ctx *core.Context, request *core.Request, response *core.Response) { - user := ctx.LoggedUser() - - 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) -} - -// UpdateFeed update a subscription and redirect to the feed entries page. -func (c *Controller) UpdateFeed(ctx *core.Context, request *core.Request, response *core.Response) { - user := ctx.LoggedUser() - - feed, err := c.getFeedFromURL(request, response, user) - if err != nil { - return - } - - feedForm := form.NewFeedForm(request.Request()) - 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 { - logger.Error("[Controller:EditFeed] %v", err) - response.HTML().Render("edit_feed", args.Merge(tplParams{ - "errorMessage": "Unable to update this feed.", - })) - return - } - - response.Redirect(ctx.Route("feedEntries", "feedID", feed.ID)) -} - -// RemoveFeed delete a subscription from the database and redirect to the list of feeds page. -func (c *Controller) RemoveFeed(ctx *core.Context, request *core.Request, response *core.Response) { - feedID, err := request.IntegerParam("feedID") - if err != nil { - response.HTML().ServerError(err) - return - } - - user := ctx.LoggedUser() - if err := c.store.RemoveFeed(user.ID, feedID); err != nil { - response.HTML().ServerError(err) - return - } - - response.Redirect(ctx.Route("feeds")) -} - -// RefreshFeed refresh a subscription and redirect to the feed entries page. -func (c *Controller) RefreshFeed(ctx *core.Context, request *core.Request, response *core.Response) { - feedID, err := request.IntegerParam("feedID") - if err != nil { - response.HTML().BadRequest(err) - return - } - - user := ctx.LoggedUser() - if err := c.feedHandler.RefreshFeed(user.ID, feedID); err != nil { - logger.Error("[Controller:RefreshFeed] %v", err) - } - - response.Redirect(ctx.Route("feedEntries", "feedID", feedID)) -} - -func (c *Controller) getFeedFromURL(request *core.Request, response *core.Response, user *model.User) (*model.Feed, error) { - feedID, err := request.IntegerParam("feedID") - if err != nil { - response.HTML().BadRequest(err) - return nil, err - } - - feed, err := c.store.FeedByID(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.Categories(user.ID) - if err != nil { - return nil, err - } - - if feedForm == nil { - args["form"] = form.FeedForm{ - SiteURL: feed.SiteURL, - FeedURL: feed.FeedURL, - Title: feed.Title, - ScraperRules: feed.ScraperRules, - RewriteRules: feed.RewriteRules, - Crawler: feed.Crawler, - CategoryID: feed.Category.ID, - } - } else { - args["form"] = feedForm - } - - args["categories"] = categories - args["feed"] = feed - args["menu"] = "feeds" - return args, nil -} diff --git a/server/ui/controller/history.go b/server/ui/controller/history.go deleted file mode 100644 index 7347bacc..00000000 --- a/server/ui/controller/history.go +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2017 Frédéric Guillot. All rights reserved. -// Use of this source code is governed by the Apache 2.0 -// license that can be found in the LICENSE file. - -package controller - -import ( - "github.com/miniflux/miniflux/model" - "github.com/miniflux/miniflux/server/core" -) - -// ShowHistoryPage renders the page with all read entries. -func (c *Controller) ShowHistoryPage(ctx *core.Context, request *core.Request, response *core.Response) { - user := ctx.LoggedUser() - offset := request.QueryIntegerParam("offset", 0) - - args, err := c.getCommonTemplateArgs(ctx) - if err != nil { - response.HTML().ServerError(err) - return - } - - builder := c.store.NewEntryQueryBuilder(user.ID) - builder.WithStatus(model.EntryStatusRead) - builder.WithOrder(model.DefaultSortingOrder) - builder.WithDirection(user.EntryDirection) - 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.Route("history"), count, offset), - "menu": "history", - })) -} - -// FlushHistory changes all "read" items to "removed". -func (c *Controller) FlushHistory(ctx *core.Context, request *core.Request, response *core.Response) { - user := ctx.LoggedUser() - - err := c.store.FlushHistory(user.ID) - if err != nil { - response.HTML().ServerError(err) - return - } - - response.Redirect(ctx.Route("history")) -} diff --git a/server/ui/controller/icon.go b/server/ui/controller/icon.go deleted file mode 100644 index f5ff1db3..00000000 --- a/server/ui/controller/icon.go +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2017 Frédéric Guillot. All rights reserved. -// Use of this source code is governed by the Apache 2.0 -// license that can be found in the LICENSE file. - -package controller - -import ( - "time" - - "github.com/miniflux/miniflux/server/core" -) - -// ShowIcon shows the feed icon. -func (c *Controller) ShowIcon(ctx *core.Context, request *core.Request, response *core.Response) { - iconID, err := request.IntegerParam("iconID") - if err != nil { - response.HTML().BadRequest(err) - return - } - - icon, err := c.store.IconByID(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) -} diff --git a/server/ui/controller/integrations.go b/server/ui/controller/integrations.go deleted file mode 100644 index 9ff4baa4..00000000 --- a/server/ui/controller/integrations.go +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright 2017 Frédéric Guillot. All rights reserved. -// Use of this source code is governed by the Apache 2.0 -// license that can be found in the LICENSE file. - -package controller - -import ( - "crypto/md5" - "fmt" - - "github.com/miniflux/miniflux/server/core" - "github.com/miniflux/miniflux/server/ui/form" -) - -// ShowIntegrations renders the page with all external integrations. -func (c *Controller) ShowIntegrations(ctx *core.Context, request *core.Request, response *core.Response) { - user := ctx.LoggedUser() - integration, err := c.store.Integration(user.ID) - if err != nil { - response.HTML().ServerError(err) - return - } - - args, err := c.getCommonTemplateArgs(ctx) - if err != nil { - response.HTML().ServerError(err) - return - } - - response.HTML().Render("integrations", args.Merge(tplParams{ - "menu": "settings", - "form": form.IntegrationForm{ - PinboardEnabled: integration.PinboardEnabled, - PinboardToken: integration.PinboardToken, - PinboardTags: integration.PinboardTags, - PinboardMarkAsUnread: integration.PinboardMarkAsUnread, - InstapaperEnabled: integration.InstapaperEnabled, - InstapaperUsername: integration.InstapaperUsername, - InstapaperPassword: integration.InstapaperPassword, - FeverEnabled: integration.FeverEnabled, - FeverUsername: integration.FeverUsername, - FeverPassword: integration.FeverPassword, - WallabagEnabled: integration.WallabagEnabled, - WallabagURL: integration.WallabagURL, - WallabagClientID: integration.WallabagClientID, - WallabagClientSecret: integration.WallabagClientSecret, - WallabagUsername: integration.WallabagUsername, - WallabagPassword: integration.WallabagPassword, - }, - })) -} - -// UpdateIntegration updates integration settings. -func (c *Controller) UpdateIntegration(ctx *core.Context, request *core.Request, response *core.Response) { - user := ctx.LoggedUser() - integration, err := c.store.Integration(user.ID) - if err != nil { - response.HTML().ServerError(err) - return - } - - integrationForm := form.NewIntegrationForm(request.Request()) - integrationForm.Merge(integration) - - if integration.FeverUsername != "" && c.store.HasDuplicateFeverUsername(user.ID, integration.FeverUsername) { - ctx.SetFlashErrorMessage(ctx.Translate("There is already someone else with the same Fever username!")) - response.Redirect(ctx.Route("integrations")) - return - } - - if integration.FeverEnabled { - integration.FeverToken = fmt.Sprintf("%x", md5.Sum([]byte(integration.FeverUsername+":"+integration.FeverPassword))) - } else { - integration.FeverToken = "" - } - - err = c.store.UpdateIntegration(integration) - if err != nil { - response.HTML().ServerError(err) - return - } - - response.Redirect(ctx.Route("integrations")) -} diff --git a/server/ui/controller/login.go b/server/ui/controller/login.go deleted file mode 100644 index ef99c824..00000000 --- a/server/ui/controller/login.go +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2017 Frédéric Guillot. All rights reserved. -// Use of this source code is governed by the Apache 2.0 -// license that can be found in the LICENSE file. - -package controller - -import ( - "github.com/miniflux/miniflux/logger" - "github.com/miniflux/miniflux/server/cookie" - "github.com/miniflux/miniflux/server/core" - "github.com/miniflux/miniflux/server/ui/form" - - "github.com/tomasen/realip" -) - -// ShowLoginPage shows the login form. -func (c *Controller) ShowLoginPage(ctx *core.Context, request *core.Request, response *core.Response) { - if ctx.IsAuthenticated() { - response.Redirect(ctx.Route("unread")) - return - } - - response.HTML().Render("login", tplParams{ - "csrf": ctx.CSRF(), - }) -} - -// CheckLogin validates the username/password and redirects the user to the unread page. -func (c *Controller) CheckLogin(ctx *core.Context, request *core.Request, response *core.Response) { - authForm := form.NewAuthForm(request.Request()) - tplParams := tplParams{ - "errorMessage": "Invalid username or password.", - "csrf": ctx.CSRF(), - "form": authForm, - } - - if err := authForm.Validate(); err != nil { - logger.Error("[Controller:CheckLogin] %v", err) - response.HTML().Render("login", tplParams) - return - } - - if err := c.store.CheckPassword(authForm.Username, authForm.Password); err != nil { - logger.Error("[Controller:CheckLogin] %v", err) - response.HTML().Render("login", tplParams) - return - } - - sessionToken, err := c.store.CreateUserSession( - authForm.Username, - request.Request().UserAgent(), - realip.RealIP(request.Request()), - ) - - if err != nil { - response.HTML().ServerError(err) - return - } - - logger.Info("[Controller:CheckLogin] username=%s just logged in", authForm.Username) - - response.SetCookie(cookie.New(cookie.CookieUserSessionID, sessionToken, c.cfg.IsHTTPS)) - response.Redirect(ctx.Route("unread")) -} - -// Logout destroy the session and redirects the user to the login page. -func (c *Controller) Logout(ctx *core.Context, request *core.Request, response *core.Response) { - user := ctx.LoggedUser() - - if err := c.store.RemoveUserSessionByToken(user.ID, ctx.UserSessionToken()); err != nil { - logger.Error("[Controller:Logout] %v", err) - } - - response.SetCookie(cookie.Expired(cookie.CookieUserSessionID, c.cfg.IsHTTPS)) - response.Redirect(ctx.Route("login")) -} diff --git a/server/ui/controller/oauth2.go b/server/ui/controller/oauth2.go deleted file mode 100644 index 2aaa5d7d..00000000 --- a/server/ui/controller/oauth2.go +++ /dev/null @@ -1,170 +0,0 @@ -// Copyright 2017 Frédéric Guillot. All rights reserved. -// Use of this source code is governed by the Apache 2.0 -// license that can be found in the LICENSE file. - -package controller - -import ( - "github.com/miniflux/miniflux/config" - "github.com/miniflux/miniflux/logger" - "github.com/miniflux/miniflux/model" - "github.com/miniflux/miniflux/server/cookie" - "github.com/miniflux/miniflux/server/core" - "github.com/miniflux/miniflux/server/oauth2" - "github.com/tomasen/realip" -) - -// OAuth2Redirect redirects the user to the consent page to ask for permission. -func (c *Controller) OAuth2Redirect(ctx *core.Context, request *core.Request, response *core.Response) { - provider := request.StringParam("provider", "") - if provider == "" { - logger.Error("[OAuth2] Invalid or missing provider: %s", provider) - response.Redirect(ctx.Route("login")) - return - } - - authProvider, err := getOAuth2Manager(c.cfg).Provider(provider) - if err != nil { - logger.Error("[OAuth2] %v", err) - response.Redirect(ctx.Route("login")) - return - } - - response.Redirect(authProvider.GetRedirectURL(ctx.GenerateOAuth2State())) -} - -// OAuth2Callback receives the authorization code and create a new session. -func (c *Controller) OAuth2Callback(ctx *core.Context, request *core.Request, response *core.Response) { - provider := request.StringParam("provider", "") - if provider == "" { - logger.Error("[OAuth2] Invalid or missing provider") - response.Redirect(ctx.Route("login")) - return - } - - code := request.QueryStringParam("code", "") - if code == "" { - logger.Error("[OAuth2] No code received on callback") - response.Redirect(ctx.Route("login")) - return - } - - state := request.QueryStringParam("state", "") - if state == "" || state != ctx.OAuth2State() { - logger.Error(`[OAuth2] Invalid state value: got "%s" instead of "%s"`, state, ctx.OAuth2State()) - response.Redirect(ctx.Route("login")) - return - } - - authProvider, err := getOAuth2Manager(c.cfg).Provider(provider) - if err != nil { - logger.Error("[OAuth2] %v", err) - response.Redirect(ctx.Route("login")) - return - } - - profile, err := authProvider.GetProfile(code) - if err != nil { - logger.Error("[OAuth2] %v", err) - response.Redirect(ctx.Route("login")) - return - } - - if ctx.IsAuthenticated() { - user, err := c.store.UserByExtraField(profile.Key, profile.ID) - if err != nil { - response.HTML().ServerError(err) - return - } - - if user != nil { - logger.Error("[OAuth2] User #%d cannot be associated because %s is already associated", ctx.UserID(), user.Username) - ctx.SetFlashErrorMessage(ctx.Translate("There is already someone associated with this provider!")) - response.Redirect(ctx.Route("settings")) - return - } - - user = ctx.LoggedUser() - if err := c.store.UpdateExtraField(user.ID, profile.Key, profile.ID); err != nil { - response.HTML().ServerError(err) - return - } - - ctx.SetFlashMessage(ctx.Translate("Your external account is now linked !")) - response.Redirect(ctx.Route("settings")) - return - } - - user, err := c.store.UserByExtraField(profile.Key, profile.ID) - if err != nil { - response.HTML().ServerError(err) - return - } - - if user == nil { - if c.cfg.GetInt("OAUTH2_USER_CREATION", 0) == 0 { - response.HTML().Forbidden() - return - } - - user = model.NewUser() - user.Username = profile.Username - user.IsAdmin = false - user.Extra[profile.Key] = profile.ID - - if err := c.store.CreateUser(user); err != nil { - response.HTML().ServerError(err) - return - } - } - - sessionToken, err := c.store.CreateUserSession( - user.Username, - request.Request().UserAgent(), - realip.RealIP(request.Request()), - ) - - if err != nil { - response.HTML().ServerError(err) - return - } - - logger.Info("[Controller:OAuth2Callback] username=%s just logged in", user.Username) - - response.SetCookie(cookie.New(cookie.CookieUserSessionID, sessionToken, c.cfg.IsHTTPS)) - response.Redirect(ctx.Route("unread")) -} - -// OAuth2Unlink unlink an account from the external provider. -func (c *Controller) OAuth2Unlink(ctx *core.Context, request *core.Request, response *core.Response) { - provider := request.StringParam("provider", "") - if provider == "" { - logger.Info("[OAuth2] Invalid or missing provider") - response.Redirect(ctx.Route("login")) - return - } - - authProvider, err := getOAuth2Manager(c.cfg).Provider(provider) - if err != nil { - logger.Error("[OAuth2] %v", err) - response.Redirect(ctx.Route("settings")) - return - } - - user := ctx.LoggedUser() - if err := c.store.RemoveExtraField(user.ID, authProvider.GetUserExtraKey()); err != nil { - response.HTML().ServerError(err) - return - } - - response.Redirect(ctx.Route("settings")) - return -} - -func getOAuth2Manager(cfg *config.Config) *oauth2.Manager { - return oauth2.NewManager( - cfg.Get("OAUTH2_CLIENT_ID", ""), - cfg.Get("OAUTH2_CLIENT_SECRET", ""), - cfg.Get("OAUTH2_REDIRECT_URL", ""), - ) -} diff --git a/server/ui/controller/opml.go b/server/ui/controller/opml.go deleted file mode 100644 index d8016779..00000000 --- a/server/ui/controller/opml.go +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2017 Frédéric Guillot. All rights reserved. -// Use of this source code is governed by the Apache 2.0 -// license that can be found in the LICENSE file. - -package controller - -import ( - "github.com/miniflux/miniflux/logger" - "github.com/miniflux/miniflux/server/core" -) - -// Export generates the OPML file. -func (c *Controller) Export(ctx *core.Context, request *core.Request, response *core.Response) { - user := ctx.LoggedUser() - opml, err := c.opmlHandler.Export(user.ID) - if err != nil { - response.HTML().ServerError(err) - return - } - - response.XML().Download("feeds.opml", opml) -} - -// Import shows the import form. -func (c *Controller) Import(ctx *core.Context, request *core.Request, response *core.Response) { - args, err := c.getCommonTemplateArgs(ctx) - if err != nil { - response.HTML().ServerError(err) - return - } - - response.HTML().Render("import", args.Merge(tplParams{ - "menu": "feeds", - })) -} - -// UploadOPML handles OPML file importation. -func (c *Controller) UploadOPML(ctx *core.Context, request *core.Request, response *core.Response) { - file, fileHeader, err := request.File("file") - if err != nil { - logger.Error("[Controller:UploadOPML] %v", err) - response.Redirect(ctx.Route("import")) - return - } - defer file.Close() - - user := ctx.LoggedUser() - logger.Info( - "[Controller:UploadOPML] User #%d uploaded this file: %s (%d bytes)", - 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, - "menu": "feeds", - })) - - return - } - - response.Redirect(ctx.Route("feeds")) -} diff --git a/server/ui/controller/pagination.go b/server/ui/controller/pagination.go deleted file mode 100644 index 1d61f74f..00000000 --- a/server/ui/controller/pagination.go +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2017 Frédéric Guillot. All rights reserved. -// Use of this source code is governed by the Apache 2.0 -// license that can be found in the LICENSE file. - -package 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, - } -} diff --git a/server/ui/controller/proxy.go b/server/ui/controller/proxy.go deleted file mode 100644 index 6ee52b88..00000000 --- a/server/ui/controller/proxy.go +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2017 Frédéric Guillot. All rights reserved. -// Use of this source code is governed by the Apache 2.0 -// license that can be found in the LICENSE file. - -package controller - -import ( - "encoding/base64" - "errors" - "io/ioutil" - "time" - - "github.com/miniflux/miniflux/crypto" - "github.com/miniflux/miniflux/http" - "github.com/miniflux/miniflux/logger" - "github.com/miniflux/miniflux/server/core" -) - -// ImageProxy fetch an image from a remote server and sent it back to the browser. -func (c *Controller) ImageProxy(ctx *core.Context, request *core.Request, response *core.Response) { - // If we receive a "If-None-Match" header we assume the image in stored in browser cache - if request.Request().Header.Get("If-None-Match") != "" { - response.NotModified() - return - } - - encodedURL := request.StringParam("encodedURL", "") - if encodedURL == "" { - response.HTML().BadRequest(errors.New("No URL provided")) - return - } - - decodedURL, err := base64.URLEncoding.DecodeString(encodedURL) - if err != nil { - response.HTML().BadRequest(errors.New("Unable to decode this URL")) - return - } - - client := http.NewClient(string(decodedURL)) - resp, err := client.Get() - if err != nil { - logger.Error("[Controller:ImageProxy] %v", err) - response.HTML().NotFound() - return - } - - if resp.HasServerFailure() { - response.HTML().NotFound() - return - } - - body, _ := ioutil.ReadAll(resp.Body) - etag := crypto.HashFromBytes(body) - - response.Cache(resp.ContentType, etag, body, 72*time.Hour) -} diff --git a/server/ui/controller/session.go b/server/ui/controller/session.go deleted file mode 100644 index 05cb29ed..00000000 --- a/server/ui/controller/session.go +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2017 Frédéric Guillot. All rights reserved. -// Use of this source code is governed by the Apache 2.0 -// license that can be found in the LICENSE file. - -package controller - -import ( - "github.com/miniflux/miniflux/logger" - "github.com/miniflux/miniflux/server/core" -) - -// ShowSessions shows the list of active user sessions. -func (c *Controller) ShowSessions(ctx *core.Context, request *core.Request, response *core.Response) { - user := ctx.LoggedUser() - args, err := c.getCommonTemplateArgs(ctx) - if err != nil { - response.HTML().ServerError(err) - return - } - - sessions, err := c.store.UserSessions(user.ID) - if err != nil { - response.HTML().ServerError(err) - return - } - - response.HTML().Render("sessions", args.Merge(tplParams{ - "sessions": sessions, - "currentSessionToken": ctx.UserSessionToken(), - "menu": "settings", - })) -} - -// RemoveSession remove a user session. -func (c *Controller) RemoveSession(ctx *core.Context, request *core.Request, response *core.Response) { - user := ctx.LoggedUser() - - sessionID, err := request.IntegerParam("sessionID") - if err != nil { - response.HTML().BadRequest(err) - return - } - - err = c.store.RemoveUserSessionByID(user.ID, sessionID) - if err != nil { - logger.Error("[Controller:RemoveSession] %v", err) - } - - response.Redirect(ctx.Route("sessions")) -} diff --git a/server/ui/controller/settings.go b/server/ui/controller/settings.go deleted file mode 100644 index feba8936..00000000 --- a/server/ui/controller/settings.go +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright 2017 Frédéric Guillot. All rights reserved. -// Use of this source code is governed by the Apache 2.0 -// license that can be found in the LICENSE file. - -package controller - -import ( - "github.com/miniflux/miniflux/locale" - "github.com/miniflux/miniflux/logger" - "github.com/miniflux/miniflux/model" - "github.com/miniflux/miniflux/server/core" - "github.com/miniflux/miniflux/server/ui/form" -) - -// ShowSettings shows the settings page. -func (c *Controller) ShowSettings(ctx *core.Context, request *core.Request, response *core.Response) { - user := ctx.LoggedUser() - - args, err := c.getSettingsFormTemplateArgs(ctx, user, nil) - if err != nil { - response.HTML().ServerError(err) - return - } - - response.HTML().Render("settings", args) -} - -// UpdateSettings update the settings. -func (c *Controller) UpdateSettings(ctx *core.Context, request *core.Request, response *core.Response) { - user := ctx.LoggedUser() - - settingsForm := form.NewSettingsForm(request.Request()) - 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 { - logger.Error("[Controller:UpdateSettings] %v", err) - response.HTML().Render("settings", args.Merge(tplParams{ - "form": settingsForm, - "errorMessage": "Unable to update this user.", - })) - return - } - - ctx.SetFlashMessage(ctx.Translate("Preferences saved!")) - response.Redirect(ctx.Route("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, - EntryDirection: user.EntryDirection, - } - } else { - args["form"] = settingsForm - } - - args["menu"] = "settings" - args["themes"] = model.Themes() - args["languages"] = locale.AvailableLanguages() - args["timezones"], err = c.store.Timezones() - if err != nil { - return args, err - } - - return args, nil -} diff --git a/server/ui/controller/starred.go b/server/ui/controller/starred.go deleted file mode 100644 index e9da241a..00000000 --- a/server/ui/controller/starred.go +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright 2017 Frédéric Guillot. All rights reserved. -// Use of this source code is governed by the Apache 2.0 -// license that can be found in the LICENSE file. - -package controller - -import ( - "github.com/miniflux/miniflux/logger" - "github.com/miniflux/miniflux/model" - "github.com/miniflux/miniflux/server/core" -) - -// ShowStarredPage renders the page with all starred entries. -func (c *Controller) ShowStarredPage(ctx *core.Context, request *core.Request, response *core.Response) { - user := ctx.LoggedUser() - offset := request.QueryIntegerParam("offset", 0) - - args, err := c.getCommonTemplateArgs(ctx) - if err != nil { - response.HTML().ServerError(err) - return - } - - builder := c.store.NewEntryQueryBuilder(user.ID) - builder.WithoutStatus(model.EntryStatusRemoved) - builder.WithStarred() - builder.WithOrder(model.DefaultSortingOrder) - builder.WithDirection(user.EntryDirection) - 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("starred", args.Merge(tplParams{ - "entries": entries, - "total": count, - "pagination": c.getPagination(ctx.Route("starred"), count, offset), - "menu": "starred", - })) -} - -// ToggleBookmark handles Ajax request to toggle bookmark value. -func (c *Controller) ToggleBookmark(ctx *core.Context, request *core.Request, response *core.Response) { - user := ctx.LoggedUser() - entryID, err := request.IntegerParam("entryID") - if err != nil { - response.HTML().BadRequest(err) - return - } - - if err := c.store.ToggleBookmark(user.ID, entryID); err != nil { - logger.Error("[Controller:UpdateEntryStatus] %v", err) - response.JSON().ServerError(nil) - return - } - - response.JSON().Standard("OK") -} diff --git a/server/ui/controller/static.go b/server/ui/controller/static.go deleted file mode 100644 index 7cf7a35d..00000000 --- a/server/ui/controller/static.go +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright 2017 Frédéric Guillot. All rights reserved. -// Use of this source code is governed by the Apache 2.0 -// license that can be found in the LICENSE file. - -package controller - -import ( - "encoding/base64" - "time" - - "github.com/miniflux/miniflux/logger" - "github.com/miniflux/miniflux/server/core" - "github.com/miniflux/miniflux/server/static" -) - -// Stylesheet renders the CSS. -func (c *Controller) Stylesheet(ctx *core.Context, request *core.Request, response *core.Response) { - stylesheet := request.StringParam("name", "white") - body := static.Stylesheets["common"] - etag := static.StylesheetsChecksums["common"] - - if theme, found := static.Stylesheets[stylesheet]; found { - body += theme - etag += static.StylesheetsChecksums[stylesheet] - } - - response.Cache("text/css; charset=utf-8", etag, []byte(body), 48*time.Hour) -} - -// Javascript renders application client side code. -func (c *Controller) Javascript(ctx *core.Context, request *core.Request, response *core.Response) { - response.Cache("text/javascript; charset=utf-8", static.JavascriptChecksums["app"], []byte(static.Javascript["app"]), 48*time.Hour) -} - -// Favicon renders the application favicon. -func (c *Controller) Favicon(ctx *core.Context, request *core.Request, response *core.Response) { - blob, err := base64.StdEncoding.DecodeString(static.Binaries["favicon.ico"]) - if err != nil { - logger.Error("[Controller:Favicon] %v", err) - response.HTML().NotFound() - return - } - - response.Cache("image/x-icon", static.BinariesChecksums["favicon.ico"], blob, 48*time.Hour) -} - -// AppIcon returns application icons. -func (c *Controller) AppIcon(ctx *core.Context, request *core.Request, response *core.Response) { - filename := request.StringParam("filename", "favicon.png") - encodedBlob, found := static.Binaries[filename] - if !found { - logger.Info("[Controller:AppIcon] This icon doesn't exists: %s", filename) - response.HTML().NotFound() - return - } - - blob, err := base64.StdEncoding.DecodeString(encodedBlob) - if err != nil { - logger.Error("[Controller:AppIcon] %v", err) - response.HTML().NotFound() - return - } - - response.Cache("image/png", static.BinariesChecksums[filename], blob, 48*time.Hour) -} - -// WebManifest renders web manifest file. -func (c *Controller) WebManifest(ctx *core.Context, request *core.Request, response *core.Response) { - type webManifestIcon struct { - Source string `json:"src"` - Sizes string `json:"sizes"` - Type string `json:"type"` - } - - type webManifest struct { - Name string `json:"name"` - Description string `json:"description"` - ShortName string `json:"short_name"` - StartURL string `json:"start_url"` - Icons []webManifestIcon `json:"icons"` - Display string `json:"display"` - } - - manifest := &webManifest{ - Name: "Miniflux", - ShortName: "Miniflux", - Description: "Minimalist Feed Reader", - Display: "minimal-ui", - StartURL: ctx.Route("unread"), - Icons: []webManifestIcon{ - webManifestIcon{Source: ctx.Route("appIcon", "filename", "touch-icon-ipad-retina.png"), Sizes: "144x144", Type: "image/png"}, - webManifestIcon{Source: ctx.Route("appIcon", "filename", "touch-icon-iphone-retina.png"), Sizes: "114x114", Type: "image/png"}, - }, - } - - response.JSON().Standard(manifest) -} diff --git a/server/ui/controller/subscription.go b/server/ui/controller/subscription.go deleted file mode 100644 index d243f9a9..00000000 --- a/server/ui/controller/subscription.go +++ /dev/null @@ -1,145 +0,0 @@ -// Copyright 2017 Frédéric Guillot. All rights reserved. -// Use of this source code is governed by the Apache 2.0 -// license that can be found in the LICENSE file. - -package controller - -import ( - "github.com/miniflux/miniflux/logger" - "github.com/miniflux/miniflux/model" - "github.com/miniflux/miniflux/reader/subscription" - "github.com/miniflux/miniflux/server/core" - "github.com/miniflux/miniflux/server/ui/form" -) - -// Bookmarklet prefill the form to add a subscription from the URL provided by the bookmarklet. -func (c *Controller) Bookmarklet(ctx *core.Context, request *core.Request, response *core.Response) { - user := ctx.LoggedUser() - args, err := c.getSubscriptionFormTemplateArgs(ctx, user) - if err != nil { - response.HTML().ServerError(err) - return - } - - bookmarkletURL := request.QueryStringParam("uri", "") - response.HTML().Render("add_subscription", args.Merge(tplParams{ - "form": &form.SubscriptionForm{URL: bookmarkletURL}, - })) -} - -// AddSubscription shows the form to add a new feed. -func (c *Controller) AddSubscription(ctx *core.Context, request *core.Request, response *core.Response) { - user := ctx.LoggedUser() - - args, err := c.getSubscriptionFormTemplateArgs(ctx, user) - if err != nil { - response.HTML().ServerError(err) - return - } - - response.HTML().Render("add_subscription", args) -} - -// SubmitSubscription try to find a feed from the URL provided by the user. -func (c *Controller) SubmitSubscription(ctx *core.Context, request *core.Request, response *core.Response) { - user := ctx.LoggedUser() - - args, err := c.getSubscriptionFormTemplateArgs(ctx, user) - if err != nil { - response.HTML().ServerError(err) - return - } - - subscriptionForm := form.NewSubscriptionForm(request.Request()) - 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 { - logger.Error("[Controller:SubmitSubscription] %v", err) - response.HTML().Render("add_subscription", args.Merge(tplParams{ - "form": subscriptionForm, - "errorMessage": err, - })) - return - } - - logger.Info("[UI:SubmitSubscription] %s", 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, subscriptionForm.Crawler) - if err != nil { - response.HTML().Render("add_subscription", args.Merge(tplParams{ - "form": subscriptionForm, - "errorMessage": err, - })) - return - } - - response.Redirect(ctx.Route("feedEntries", "feedID", feed.ID)) - case n > 1: - response.HTML().Render("choose_subscription", args.Merge(tplParams{ - "categoryID": subscriptionForm.CategoryID, - "subscriptions": subscriptions, - })) - } -} - -// ChooseSubscription shows a page to choose a subscription. -func (c *Controller) ChooseSubscription(ctx *core.Context, request *core.Request, response *core.Response) { - user := ctx.LoggedUser() - - args, err := c.getSubscriptionFormTemplateArgs(ctx, user) - if err != nil { - response.HTML().ServerError(err) - return - } - - subscriptionForm := form.NewSubscriptionForm(request.Request()) - 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, subscriptionForm.Crawler) - if err != nil { - response.HTML().Render("add_subscription", args.Merge(tplParams{ - "form": subscriptionForm, - "errorMessage": err, - })) - return - } - - response.Redirect(ctx.Route("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.Categories(user.ID) - if err != nil { - return nil, err - } - - args["categories"] = categories - args["menu"] = "feeds" - return args, nil -} diff --git a/server/ui/controller/unread.go b/server/ui/controller/unread.go deleted file mode 100644 index 1dd7b072..00000000 --- a/server/ui/controller/unread.go +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2017 Frédéric Guillot. All rights reserved. -// Use of this source code is governed by the Apache 2.0 -// license that can be found in the LICENSE file. - -package controller - -import ( - "github.com/miniflux/miniflux/model" - "github.com/miniflux/miniflux/server/core" -) - -// ShowUnreadPage render the page with all unread entries. -func (c *Controller) ShowUnreadPage(ctx *core.Context, request *core.Request, response *core.Response) { - user := ctx.LoggedUser() - offset := request.QueryIntegerParam("offset", 0) - - builder := c.store.NewEntryQueryBuilder(user.ID) - builder.WithStatus(model.EntryStatusUnread) - countUnread, err := builder.CountEntries() - if err != nil { - response.HTML().ServerError(err) - return - } - - if offset >= countUnread { - offset = 0 - } - - builder = c.store.NewEntryQueryBuilder(user.ID) - builder.WithStatus(model.EntryStatusUnread) - builder.WithOrder(model.DefaultSortingOrder) - builder.WithDirection(user.EntryDirection) - builder.WithOffset(offset) - builder.WithLimit(nbItemsPerPage) - entries, err := builder.GetEntries() - if err != nil { - response.HTML().ServerError(err) - return - } - - response.HTML().Render("unread", tplParams{ - "user": user, - "countUnread": countUnread, - "entries": entries, - "pagination": c.getPagination(ctx.Route("unread"), countUnread, offset), - "menu": "unread", - "csrf": ctx.CSRF(), - }) -} diff --git a/server/ui/controller/user.go b/server/ui/controller/user.go deleted file mode 100644 index c5d4dba0..00000000 --- a/server/ui/controller/user.go +++ /dev/null @@ -1,238 +0,0 @@ -// Copyright 2017 Frédéric Guillot. All rights reserved. -// Use of this source code is governed by the Apache 2.0 -// license that can be found in the LICENSE file. - -package controller - -import ( - "errors" - - "github.com/miniflux/miniflux/logger" - "github.com/miniflux/miniflux/model" - "github.com/miniflux/miniflux/server/core" - "github.com/miniflux/miniflux/server/ui/form" -) - -// ShowUsers shows the list of users. -func (c *Controller) ShowUsers(ctx *core.Context, request *core.Request, response *core.Response) { - user := ctx.LoggedUser() - - if !user.IsAdmin { - response.HTML().Forbidden() - return - } - - args, err := c.getCommonTemplateArgs(ctx) - if err != nil { - response.HTML().ServerError(err) - return - } - - users, err := c.store.Users() - if err != nil { - response.HTML().ServerError(err) - return - } - - response.HTML().Render("users", args.Merge(tplParams{ - "users": users, - "menu": "settings", - })) -} - -// CreateUser shows the user creation form. -func (c *Controller) CreateUser(ctx *core.Context, request *core.Request, response *core.Response) { - user := ctx.LoggedUser() - - 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{}, - })) -} - -// SaveUser validate and save the new user into the database. -func (c *Controller) SaveUser(ctx *core.Context, request *core.Request, response *core.Response) { - user := ctx.LoggedUser() - - if !user.IsAdmin { - response.HTML().Forbidden() - return - } - - args, err := c.getCommonTemplateArgs(ctx) - if err != nil { - response.HTML().ServerError(err) - return - } - - userForm := form.NewUserForm(request.Request()) - 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 { - logger.Error("[Controller:SaveUser] %v", err) - response.HTML().Render("edit_user", args.Merge(tplParams{ - "menu": "settings", - "form": userForm, - "errorMessage": "Unable to create this user.", - })) - return - } - - response.Redirect(ctx.Route("users")) -} - -// EditUser shows the form to edit a user. -func (c *Controller) EditUser(ctx *core.Context, request *core.Request, response *core.Response) { - user := ctx.LoggedUser() - - 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, - }, - })) -} - -// UpdateUser validate and update a user. -func (c *Controller) UpdateUser(ctx *core.Context, request *core.Request, response *core.Response) { - user := ctx.LoggedUser() - - 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.Request()) - 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 { - logger.Error("[Controller:UpdateUser] %v", 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.Route("users")) -} - -// RemoveUser deletes a user from the database. -func (c *Controller) RemoveUser(ctx *core.Context, request *core.Request, response *core.Response) { - user := ctx.LoggedUser() - if !user.IsAdmin { - 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.Route("users")) -} - -func (c *Controller) getUserFromURL(ctx *core.Context, request *core.Request, response *core.Response) (*model.User, error) { - userID, err := request.IntegerParam("userID") - if err != nil { - response.HTML().BadRequest(err) - return nil, err - } - - user, err := c.store.UserByID(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 -} diff --git a/server/ui/filter/image_proxy_filter.go b/server/ui/filter/image_proxy_filter.go deleted file mode 100644 index 12c9da63..00000000 --- a/server/ui/filter/image_proxy_filter.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2017 Frédéric Guillot. All rights reserved. -// Use of this source code is governed by the Apache 2.0 -// license that can be found in the LICENSE file. - -package filter - -import ( - "encoding/base64" - "strings" - - "github.com/miniflux/miniflux/server/route" - "github.com/miniflux/miniflux/url" - - "github.com/PuerkitoBio/goquery" - "github.com/gorilla/mux" -) - -// ImageProxyFilter rewrites image tag URLs without HTTPS to local proxy URL -func ImageProxyFilter(router *mux.Router, data string) string { - doc, err := goquery.NewDocumentFromReader(strings.NewReader(data)) - if err != nil { - return data - } - - doc.Find("img").Each(func(i int, img *goquery.Selection) { - if srcAttr, ok := img.Attr("src"); ok { - if !url.IsHTTPS(srcAttr) { - img.SetAttr("src", Proxify(router, srcAttr)) - } - } - }) - - output, _ := doc.Find("body").First().Html() - return output -} - -// Proxify returns a proxified link. -func Proxify(router *mux.Router, link string) string { - // We use base64 url encoding to avoid slash in the URL. - return route.Path(router, "proxy", "encodedURL", base64.URLEncoding.EncodeToString([]byte(link))) -} diff --git a/server/ui/filter/image_proxy_filter_test.go b/server/ui/filter/image_proxy_filter_test.go deleted file mode 100644 index 992516eb..00000000 --- a/server/ui/filter/image_proxy_filter_test.go +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2017 Frédéric Guillot. All rights reserved. -// Use of this source code is governed by the Apache 2.0 -// license that can be found in the LICENSE file. - -package filter - -import ( - "net/http" - "testing" - - "github.com/gorilla/mux" -) - -func TestProxyFilterWithHttp(t *testing.T) { - r := mux.NewRouter() - r.HandleFunc("/proxy/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy") - - input := `<p><img src="http://website/folder/image.png" alt="Test"/></p>` - output := ImageProxyFilter(r, input) - expected := `<p><img src="/proxy/aHR0cDovL3dlYnNpdGUvZm9sZGVyL2ltYWdlLnBuZw==" alt="Test"/></p>` - - if expected != output { - t.Errorf(`Not expected output: got "%s" instead of "%s"`, output, expected) - } -} - -func TestProxyFilterWithHttps(t *testing.T) { - r := mux.NewRouter() - r.HandleFunc("/proxy/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy") - - input := `<p><img src="https://website/folder/image.png" alt="Test"/></p>` - output := ImageProxyFilter(r, input) - expected := `<p><img src="https://website/folder/image.png" alt="Test"/></p>` - - if expected != output { - t.Errorf(`Not expected output: got "%s" instead of "%s"`, output, expected) - } -} diff --git a/server/ui/form/auth.go b/server/ui/form/auth.go deleted file mode 100644 index c18a0bef..00000000 --- a/server/ui/form/auth.go +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2017 Frédéric Guillot. All rights reserved. -// Use of this source code is governed by the Apache 2.0 -// license that can be found in the LICENSE file. - -package form - -import ( - "net/http" - - "github.com/miniflux/miniflux/errors" -) - -// AuthForm represents the authentication form. -type AuthForm struct { - Username string - Password string -} - -// Validate makes sure the form values are valid. -func (a AuthForm) Validate() error { - if a.Username == "" || a.Password == "" { - return errors.NewLocalizedError("All fields are mandatory.") - } - - return nil -} - -// NewAuthForm returns a new AuthForm. -func NewAuthForm(r *http.Request) *AuthForm { - return &AuthForm{ - Username: r.FormValue("username"), - Password: r.FormValue("password"), - } -} diff --git a/server/ui/form/category.go b/server/ui/form/category.go deleted file mode 100644 index 31b71968..00000000 --- a/server/ui/form/category.go +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2017 Frédéric Guillot. All rights reserved. -// Use of this source code is governed by the Apache 2.0 -// license that can be found in the LICENSE file. - -package form - -import ( - "net/http" - - "github.com/miniflux/miniflux/errors" - "github.com/miniflux/miniflux/model" -) - -// CategoryForm represents a feed form in the UI -type CategoryForm struct { - Title string -} - -// Validate makes sure the form values are valid. -func (c CategoryForm) Validate() error { - if c.Title == "" { - return errors.NewLocalizedError("The title is mandatory.") - } - return nil -} - -// Merge update the given category fields. -func (c CategoryForm) Merge(category *model.Category) *model.Category { - category.Title = c.Title - return category -} - -// NewCategoryForm returns a new CategoryForm. -func NewCategoryForm(r *http.Request) *CategoryForm { - return &CategoryForm{ - Title: r.FormValue("title"), - } -} diff --git a/server/ui/form/feed.go b/server/ui/form/feed.go deleted file mode 100644 index 896a6d70..00000000 --- a/server/ui/form/feed.go +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright 2017 Frédéric Guillot. All rights reserved. -// Use of this source code is governed by the Apache 2.0 -// license that can be found in the LICENSE file. - -package form - -import ( - "net/http" - "strconv" - - "github.com/miniflux/miniflux/errors" - "github.com/miniflux/miniflux/model" -) - -// FeedForm represents a feed form in the UI -type FeedForm struct { - FeedURL string - SiteURL string - Title string - ScraperRules string - RewriteRules string - Crawler bool - CategoryID int64 -} - -// ValidateModification validates FeedForm fields -func (f FeedForm) ValidateModification() error { - if f.FeedURL == "" || f.SiteURL == "" || f.Title == "" || f.CategoryID == 0 { - return errors.NewLocalizedError("All fields are mandatory.") - } - return nil -} - -// Merge updates the fields of the given feed. -func (f FeedForm) Merge(feed *model.Feed) *model.Feed { - feed.Category.ID = f.CategoryID - feed.Title = f.Title - feed.SiteURL = f.SiteURL - feed.FeedURL = f.FeedURL - feed.ScraperRules = f.ScraperRules - feed.RewriteRules = f.RewriteRules - feed.Crawler = f.Crawler - feed.ParsingErrorCount = 0 - feed.ParsingErrorMsg = "" - return feed -} - -// NewFeedForm parses the HTTP request and returns a FeedForm -func NewFeedForm(r *http.Request) *FeedForm { - categoryID, err := strconv.Atoi(r.FormValue("category_id")) - if err != nil { - categoryID = 0 - } - - return &FeedForm{ - FeedURL: r.FormValue("feed_url"), - SiteURL: r.FormValue("site_url"), - Title: r.FormValue("title"), - ScraperRules: r.FormValue("scraper_rules"), - RewriteRules: r.FormValue("rewrite_rules"), - Crawler: r.FormValue("crawler") == "1", - CategoryID: int64(categoryID), - } -} diff --git a/server/ui/form/integration.go b/server/ui/form/integration.go deleted file mode 100644 index 8cc6d356..00000000 --- a/server/ui/form/integration.go +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2017 Frédéric Guillot. All rights reserved. -// Use of this source code is governed by the Apache 2.0 -// license that can be found in the LICENSE file. - -package form - -import ( - "net/http" - - "github.com/miniflux/miniflux/model" -) - -// IntegrationForm represents user integration settings form. -type IntegrationForm struct { - PinboardEnabled bool - PinboardToken string - PinboardTags string - PinboardMarkAsUnread bool - InstapaperEnabled bool - InstapaperUsername string - InstapaperPassword string - FeverEnabled bool - FeverUsername string - FeverPassword string - WallabagEnabled bool - WallabagURL string - WallabagClientID string - WallabagClientSecret string - WallabagUsername string - WallabagPassword string -} - -// Merge copy form values to the model. -func (i IntegrationForm) Merge(integration *model.Integration) { - integration.PinboardEnabled = i.PinboardEnabled - integration.PinboardToken = i.PinboardToken - integration.PinboardTags = i.PinboardTags - integration.PinboardMarkAsUnread = i.PinboardMarkAsUnread - integration.InstapaperEnabled = i.InstapaperEnabled - integration.InstapaperUsername = i.InstapaperUsername - integration.InstapaperPassword = i.InstapaperPassword - integration.FeverEnabled = i.FeverEnabled - integration.FeverUsername = i.FeverUsername - integration.FeverPassword = i.FeverPassword - integration.WallabagEnabled = i.WallabagEnabled - integration.WallabagURL = i.WallabagURL - integration.WallabagClientID = i.WallabagClientID - integration.WallabagClientSecret = i.WallabagClientSecret - integration.WallabagUsername = i.WallabagUsername - integration.WallabagPassword = i.WallabagPassword -} - -// NewIntegrationForm returns a new AuthForm. -func NewIntegrationForm(r *http.Request) *IntegrationForm { - return &IntegrationForm{ - PinboardEnabled: r.FormValue("pinboard_enabled") == "1", - PinboardToken: r.FormValue("pinboard_token"), - PinboardTags: r.FormValue("pinboard_tags"), - PinboardMarkAsUnread: r.FormValue("pinboard_mark_as_unread") == "1", - InstapaperEnabled: r.FormValue("instapaper_enabled") == "1", - InstapaperUsername: r.FormValue("instapaper_username"), - InstapaperPassword: r.FormValue("instapaper_password"), - FeverEnabled: r.FormValue("fever_enabled") == "1", - FeverUsername: r.FormValue("fever_username"), - FeverPassword: r.FormValue("fever_password"), - WallabagEnabled: r.FormValue("wallabag_enabled") == "1", - WallabagURL: r.FormValue("wallabag_url"), - WallabagClientID: r.FormValue("wallabag_client_id"), - WallabagClientSecret: r.FormValue("wallabag_client_secret"), - WallabagUsername: r.FormValue("wallabag_username"), - WallabagPassword: r.FormValue("wallabag_password"), - } -} diff --git a/server/ui/form/settings.go b/server/ui/form/settings.go deleted file mode 100644 index e5f6939b..00000000 --- a/server/ui/form/settings.go +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2017 Frédéric Guillot. All rights reserved. -// Use of this source code is governed by the Apache 2.0 -// license that can be found in the LICENSE file. - -package form - -import ( - "net/http" - - "github.com/miniflux/miniflux/errors" - "github.com/miniflux/miniflux/model" -) - -// SettingsForm represents the settings form. -type SettingsForm struct { - Username string - Password string - Confirmation string - Theme string - Language string - Timezone string - EntryDirection string -} - -// Merge updates the fields of the given user. -func (s *SettingsForm) Merge(user *model.User) *model.User { - user.Username = s.Username - user.Theme = s.Theme - user.Language = s.Language - user.Timezone = s.Timezone - user.EntryDirection = s.EntryDirection - - if s.Password != "" { - user.Password = s.Password - } - - return user -} - -// Validate makes sure the form values are valid. -func (s *SettingsForm) Validate() error { - if s.Username == "" || s.Theme == "" || s.Language == "" || s.Timezone == "" || s.EntryDirection == "" { - return errors.NewLocalizedError("The username, theme, language and timezone fields are mandatory.") - } - - if s.Password != "" { - if s.Password != s.Confirmation { - return errors.NewLocalizedError("Passwords are not the same.") - } - - if len(s.Password) < 6 { - return errors.NewLocalizedError("You must use at least 6 characters") - } - } - - return nil -} - -// NewSettingsForm returns a new SettingsForm. -func NewSettingsForm(r *http.Request) *SettingsForm { - return &SettingsForm{ - Username: r.FormValue("username"), - Password: r.FormValue("password"), - Confirmation: r.FormValue("confirmation"), - Theme: r.FormValue("theme"), - Language: r.FormValue("language"), - Timezone: r.FormValue("timezone"), - EntryDirection: r.FormValue("entry_direction"), - } -} diff --git a/server/ui/form/subscription.go b/server/ui/form/subscription.go deleted file mode 100644 index 7d2caaff..00000000 --- a/server/ui/form/subscription.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2017 Frédéric Guillot. All rights reserved. -// Use of this source code is governed by the Apache 2.0 -// license that can be found in the LICENSE file. - -package form - -import ( - "net/http" - "strconv" - - "github.com/miniflux/miniflux/errors" -) - -// SubscriptionForm represents the subscription form. -type SubscriptionForm struct { - URL string - CategoryID int64 - Crawler bool -} - -// Validate makes sure the form values are valid. -func (s *SubscriptionForm) Validate() error { - if s.URL == "" || s.CategoryID == 0 { - return errors.NewLocalizedError("The URL and the category are mandatory.") - } - - return nil -} - -// NewSubscriptionForm returns a new SubscriptionForm. -func NewSubscriptionForm(r *http.Request) *SubscriptionForm { - categoryID, err := strconv.Atoi(r.FormValue("category_id")) - if err != nil { - categoryID = 0 - } - - return &SubscriptionForm{ - URL: r.FormValue("url"), - Crawler: r.FormValue("crawler") == "1", - CategoryID: int64(categoryID), - } -} diff --git a/server/ui/form/user.go b/server/ui/form/user.go deleted file mode 100644 index 8b8346ea..00000000 --- a/server/ui/form/user.go +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright 2017 Frédéric Guillot. All rights reserved. -// Use of this source code is governed by the Apache 2.0 -// license that can be found in the LICENSE file. - -package form - -import ( - "net/http" - - "github.com/miniflux/miniflux/errors" - "github.com/miniflux/miniflux/model" -) - -// UserForm represents the user form. -type UserForm struct { - Username string - Password string - Confirmation string - IsAdmin bool -} - -// ValidateCreation validates user creation. -func (u UserForm) ValidateCreation() error { - if u.Username == "" || u.Password == "" || u.Confirmation == "" { - return errors.NewLocalizedError("All fields are mandatory.") - } - - if u.Password != u.Confirmation { - return errors.NewLocalizedError("Passwords are not the same.") - } - - if len(u.Password) < 6 { - return errors.NewLocalizedError("You must use at least 6 characters.") - } - - return nil -} - -// ValidateModification validates user modification. -func (u UserForm) ValidateModification() error { - if u.Username == "" { - return errors.NewLocalizedError("The username is mandatory.") - } - - if u.Password != "" { - if u.Password != u.Confirmation { - return errors.NewLocalizedError("Passwords are not the same.") - } - - if len(u.Password) < 6 { - return errors.NewLocalizedError("You must use at least 6 characters.") - } - } - - return nil -} - -// ToUser returns a User from the form values. -func (u UserForm) ToUser() *model.User { - return &model.User{ - Username: u.Username, - Password: u.Password, - IsAdmin: u.IsAdmin, - } -} - -// Merge updates the fields of the given user. -func (u UserForm) Merge(user *model.User) *model.User { - user.Username = u.Username - user.IsAdmin = u.IsAdmin - - if u.Password != "" { - user.Password = u.Password - } - - return user -} - -// NewUserForm returns a new UserForm. -func NewUserForm(r *http.Request) *UserForm { - return &UserForm{ - Username: r.FormValue("username"), - Password: r.FormValue("password"), - Confirmation: r.FormValue("confirmation"), - IsAdmin: r.FormValue("is_admin") == "1", - } -} diff --git a/server/ui/payload/payload.go b/server/ui/payload/payload.go deleted file mode 100644 index d91e34a8..00000000 --- a/server/ui/payload/payload.go +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2017 Frédéric Guillot. All rights reserved. -// Use of this source code is governed by the Apache 2.0 -// license that can be found in the LICENSE file. - -package payload - -import ( - "encoding/json" - "fmt" - "io" - - "github.com/miniflux/miniflux/model" -) - -// DecodeEntryStatusPayload unserialize JSON request to update entry statuses. -func DecodeEntryStatusPayload(data io.Reader) (entryIDs []int64, status string, err error) { - type payload struct { - EntryIDs []int64 `json:"entry_ids"` - Status string `json:"status"` - } - - var p payload - decoder := json.NewDecoder(data) - if err = decoder.Decode(&p); err != nil { - return nil, "", fmt.Errorf("invalid JSON payload: %v", err) - } - - if err := model.ValidateEntryStatus(p.Status); err != nil { - return nil, "", err - } - - return p.EntryIDs, p.Status, nil -} |