aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar Frédéric Guillot <fred@miniflux.net>2017-11-21 15:46:59 -0800
committerGravatar Frédéric Guillot <fred@miniflux.net>2017-11-21 15:46:59 -0800
commit549a4277b0c000d7cf0b2867c895599d283d70a1 (patch)
tree1b4c982e8a2c6205cb646b11c4d1e6d11ecfbab0
parent238b9e4c8594b8846b3cd9a24702a2299581a0a1 (diff)
Add flush history feature
-rw-r--r--README.md3
-rw-r--r--locale/translations.go2
-rw-r--r--model/entry.go7
-rw-r--r--server/core/json_response.go5
-rw-r--r--server/routes.go1
-rw-r--r--server/static/bin.go2
-rw-r--r--server/static/css.go2
-rw-r--r--server/static/js.go2
-rw-r--r--server/template/common.go2
-rw-r--r--server/template/html/history.html5
-rw-r--r--server/template/views.go9
-rw-r--r--server/ui/controller/entry.go12
-rw-r--r--server/ui/controller/history.go14
-rw-r--r--sql/sql.go2
-rw-r--r--storage/entry.go28
15 files changed, 79 insertions, 17 deletions
diff --git a/README.md b/README.md
index 25e58bd..564fdb7 100644
--- a/README.md
+++ b/README.md
@@ -9,6 +9,7 @@ Miniflux is a minimalist and opinionated feed reader:
- Works only with Postgresql
- Doesn't use any ORM
- Doesn't use any complicated framework
+- Use only modern vanilla Javascript (ES6 and fetch)
- The number of features is volountary limited
It's simple, fast, lightweight and super easy to install.
@@ -29,7 +30,7 @@ TODO
- [ ] External integrations (Pinboard, Wallabag...)
- [ ] Gzip compression
- [ ] Integration tests
-- [ ] Flush history
+- [X] Flush history
- [ ] OAuth2
Credits
diff --git a/locale/translations.go b/locale/translations.go
index bbe62d3..f4532a2 100644
--- a/locale/translations.go
+++ b/locale/translations.go
@@ -1,5 +1,5 @@
// Code generated by go generate; DO NOT EDIT.
-// 2017-11-21 14:55:14.456403496 -0800 PST m=+0.037949400
+// 2017-11-21 15:41:59.495654213 -0800 PST m=+0.041889871
package locale
diff --git a/model/entry.go b/model/entry.go
index 6858935..1053130 100644
--- a/model/entry.go
+++ b/model/entry.go
@@ -9,6 +9,7 @@ import (
"time"
)
+// Entry statuses
const (
EntryStatusUnread = "unread"
EntryStatusRead = "read"
@@ -17,6 +18,7 @@ const (
DefaultSortingDirection = "desc"
)
+// Entry represents a feed item in the system.
type Entry struct {
ID int64 `json:"id"`
UserID int64 `json:"user_id"`
@@ -33,8 +35,10 @@ type Entry struct {
Category *Category `json:"category,omitempty"`
}
+// Entries represents a list of entries.
type Entries []*Entry
+// ValidateEntryStatus makes sure the entry status is valid.
func ValidateEntryStatus(status string) error {
switch status {
case EntryStatusRead, EntryStatusUnread, EntryStatusRemoved:
@@ -44,6 +48,7 @@ func ValidateEntryStatus(status string) error {
return fmt.Errorf(`Invalid entry status, valid status values are: "%s", "%s" and "%s"`, EntryStatusRead, EntryStatusUnread, EntryStatusRemoved)
}
+// ValidateEntryOrder makes sure the sorting order is valid.
func ValidateEntryOrder(order string) error {
switch order {
case "id", "status", "published_at", "category_title", "category_id":
@@ -53,6 +58,7 @@ func ValidateEntryOrder(order string) error {
return fmt.Errorf(`Invalid entry order, valid order values are: "id", "status", "published_at", "category_title", "category_id"`)
}
+// ValidateDirection makes sure the sorting direction is valid.
func ValidateDirection(direction string) error {
switch direction {
case "asc", "desc":
@@ -62,6 +68,7 @@ func ValidateDirection(direction string) error {
return fmt.Errorf(`Invalid direction, valid direction values are: "asc" or "desc"`)
}
+// GetOppositeDirection returns the opposite sorting direction.
func GetOppositeDirection(direction string) string {
if direction == "asc" {
return "desc"
diff --git a/server/core/json_response.go b/server/core/json_response.go
index 51a9ede..c72fd73 100644
--- a/server/core/json_response.go
+++ b/server/core/json_response.go
@@ -54,7 +54,10 @@ func (j *JsonResponse) ServerError(err error) {
log.Println("[API:ServerError]", err)
j.writer.WriteHeader(http.StatusInternalServerError)
j.commonHeaders()
- j.writer.Write(j.encodeError(err))
+
+ if err != nil {
+ j.writer.Write(j.encodeError(err))
+ }
}
func (j *JsonResponse) Forbidden() {
diff --git a/server/routes.go b/server/routes.go
index 239f5fa..36471b0 100644
--- a/server/routes.go
+++ b/server/routes.go
@@ -81,6 +81,7 @@ func getRoutes(store *storage.Storage, feedHandler *feed.Handler) *mux.Router {
router.Handle("/unread/entry/{entryID}", uiHandler.Use(uiController.ShowUnreadEntry)).Name("unreadEntry").Methods("GET")
router.Handle("/history/entry/{entryID}", uiHandler.Use(uiController.ShowReadEntry)).Name("readEntry").Methods("GET")
+ router.Handle("/history/flush", uiHandler.Use(uiController.FlushHistory)).Name("flushHistory").Methods("GET")
router.Handle("/feed/{feedID}/entry/{entryID}", uiHandler.Use(uiController.ShowFeedEntry)).Name("feedEntry").Methods("GET")
router.Handle("/category/{categoryID}/entry/{entryID}", uiHandler.Use(uiController.ShowCategoryEntry)).Name("categoryEntry").Methods("GET")
diff --git a/server/static/bin.go b/server/static/bin.go
index dc411fa..ea9db5a 100644
--- a/server/static/bin.go
+++ b/server/static/bin.go
@@ -1,5 +1,5 @@
// Code generated by go generate; DO NOT EDIT.
-// 2017-11-21 14:55:14.42928305 -0800 PST m=+0.010828954
+// 2017-11-21 15:41:59.461181295 -0800 PST m=+0.007416953
package static
diff --git a/server/static/css.go b/server/static/css.go
index e2d14e3..5243f89 100644
--- a/server/static/css.go
+++ b/server/static/css.go
@@ -1,5 +1,5 @@
// Code generated by go generate; DO NOT EDIT.
-// 2017-11-21 14:55:14.43289693 -0800 PST m=+0.014442834
+// 2017-11-21 15:41:59.464123652 -0800 PST m=+0.010359310
package static
diff --git a/server/static/js.go b/server/static/js.go
index f6e8970..23df072 100644
--- a/server/static/js.go
+++ b/server/static/js.go
@@ -1,5 +1,5 @@
// Code generated by go generate; DO NOT EDIT.
-// 2017-11-21 14:55:14.43700259 -0800 PST m=+0.018548494
+// 2017-11-21 15:41:59.4687788 -0800 PST m=+0.015014458
package static
diff --git a/server/template/common.go b/server/template/common.go
index 8c6db15..b108f0e 100644
--- a/server/template/common.go
+++ b/server/template/common.go
@@ -1,5 +1,5 @@
// Code generated by go generate; DO NOT EDIT.
-// 2017-11-21 14:55:14.455330256 -0800 PST m=+0.036876160
+// 2017-11-21 15:41:59.491806442 -0800 PST m=+0.038042100
package template
diff --git a/server/template/html/history.html b/server/template/html/history.html
index a344da1..8de640f 100644
--- a/server/template/html/history.html
+++ b/server/template/html/history.html
@@ -3,6 +3,11 @@
{{ define "content"}}
<section class="page-header">
<h1>{{ t "History" }} ({{ .total }})</h1>
+ <ul>
+ <li>
+ <a href="{{ route "flushHistory" }}">{{ t "Flush history" }}</a>
+ </li>
+ </ul>
</section>
{{ if not .entries }}
diff --git a/server/template/views.go b/server/template/views.go
index 0611d6c..7f192d0 100644
--- a/server/template/views.go
+++ b/server/template/views.go
@@ -1,5 +1,5 @@
// Code generated by go generate; DO NOT EDIT.
-// 2017-11-21 14:55:14.438565193 -0800 PST m=+0.020111097
+// 2017-11-21 15:41:59.472545112 -0800 PST m=+0.018780770
package template
@@ -649,6 +649,11 @@ var templateViewsMap = map[string]string{
{{ define "content"}}
<section class="page-header">
<h1>{{ t "History" }} ({{ .total }})</h1>
+ <ul>
+ <li>
+ <a href="{{ route "flushHistory" }}">{{ t "Flush history" }}</a>
+ </li>
+ </ul>
</section>
{{ if not .entries }}
@@ -980,7 +985,7 @@ var templateViewsMapChecksums = map[string]string{
"entry": "32e605edd6d43773ac31329d247ebd81d38d974cd43689d91de79fffec7fe04b",
"feed_entries": "9aff923b6c7452dec1514feada7e0d2bbc1ec21c6f5e9f48b2de41d1b731ffe4",
"feeds": "94e43404a4044490c065c888a49bebd3ff51b588b9fb47d03c2598003aa40dca",
- "history": "439000d0be8fd716f3b89860af4d721e05baef0c2ccd2325ba020c940d6aa847",
+ "history": "947603cbde888516e62925f5d08fb0b13d930623d3ee4c690dbc22612fdda75e",
"import": "73b5112e20bfd232bf73334544186ea419505936bc237d481517a8622901878f",
"login": "568f2f69f248048f3e55e9bbc719077a74ae23fe18f237aa40e3de37e97b7a41",
"sessions": "5ac3793f0ee74d0807bab6a173a1aa6508e98add5c022fa54c8fdf5c6b4a0e75",
diff --git a/server/ui/controller/entry.go b/server/ui/controller/entry.go
index 5a3a979..c4ca587 100644
--- a/server/ui/controller/entry.go
+++ b/server/ui/controller/entry.go
@@ -6,12 +6,14 @@ package controller
import (
"errors"
+ "log"
+
"github.com/miniflux/miniflux2/model"
"github.com/miniflux/miniflux2/server/core"
"github.com/miniflux/miniflux2/server/ui/payload"
- "log"
)
+// ShowFeedEntry shows a single feed entry in "feed" mode.
func (c *Controller) ShowFeedEntry(ctx *core.Context, request *core.Request, response *core.Response) {
user := ctx.GetLoggedUser()
sortingDirection := model.DefaultSortingDirection
@@ -102,6 +104,7 @@ func (c *Controller) ShowFeedEntry(ctx *core.Context, request *core.Request, res
}))
}
+// ShowCategoryEntry shows a single feed entry in "category" mode.
func (c *Controller) ShowCategoryEntry(ctx *core.Context, request *core.Request, response *core.Response) {
user := ctx.GetLoggedUser()
sortingDirection := model.DefaultSortingDirection
@@ -192,6 +195,7 @@ func (c *Controller) ShowCategoryEntry(ctx *core.Context, request *core.Request,
}))
}
+// ShowUnreadEntry shows a single feed entry in "unread" mode.
func (c *Controller) ShowUnreadEntry(ctx *core.Context, request *core.Request, response *core.Response) {
user := ctx.GetLoggedUser()
sortingDirection := model.DefaultSortingDirection
@@ -275,6 +279,7 @@ func (c *Controller) ShowUnreadEntry(ctx *core.Context, request *core.Request, r
}))
}
+// ShowReadEntry shows a single feed entry in "history" mode.
func (c *Controller) ShowReadEntry(ctx *core.Context, request *core.Request, response *core.Response) {
user := ctx.GetLoggedUser()
sortingDirection := model.DefaultSortingDirection
@@ -349,6 +354,7 @@ func (c *Controller) ShowReadEntry(ctx *core.Context, request *core.Request, res
}))
}
+// UpdateEntriesStatus handles Ajax request to update a list of entries.
func (c *Controller) UpdateEntriesStatus(ctx *core.Context, request *core.Request, response *core.Response) {
user := ctx.GetLoggedUser()
@@ -360,14 +366,14 @@ func (c *Controller) UpdateEntriesStatus(ctx *core.Context, request *core.Reques
}
if len(entryIDs) == 0 {
- response.Html().BadRequest(errors.New("The list of entryID is empty"))
+ response.Json().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)
+ response.Json().ServerError(nil)
return
}
diff --git a/server/ui/controller/history.go b/server/ui/controller/history.go
index 2c06737..e4612e1 100644
--- a/server/ui/controller/history.go
+++ b/server/ui/controller/history.go
@@ -9,6 +9,7 @@ import (
"github.com/miniflux/miniflux2/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.GetLoggedUser()
offset := request.GetQueryIntegerParam("offset", 0)
@@ -45,3 +46,16 @@ func (c *Controller) ShowHistoryPage(ctx *core.Context, request *core.Request, r
"menu": "history",
}))
}
+
+// FlushHistory changes all "read" items to "removed".
+func (c *Controller) FlushHistory(ctx *core.Context, request *core.Request, response *core.Response) {
+ user := ctx.GetLoggedUser()
+
+ err := c.store.FlushHistory(user.ID)
+ if err != nil {
+ response.Html().ServerError(err)
+ return
+ }
+
+ response.Redirect(ctx.GetRoute("history"))
+}
diff --git a/sql/sql.go b/sql/sql.go
index 198779b..882a6d6 100644
--- a/sql/sql.go
+++ b/sql/sql.go
@@ -1,5 +1,5 @@
// Code generated by go generate; DO NOT EDIT.
-// 2017-11-21 14:55:14.420877594 -0800 PST m=+0.002423498
+// 2017-11-21 15:41:59.457985225 -0800 PST m=+0.004220883
package sql
diff --git a/storage/entry.go b/storage/entry.go
index 84cfb0f..fe8c6f2 100644
--- a/storage/entry.go
+++ b/storage/entry.go
@@ -7,17 +7,20 @@ package storage
import (
"errors"
"fmt"
+ "time"
+
"github.com/miniflux/miniflux2/helper"
"github.com/miniflux/miniflux2/model"
- "time"
"github.com/lib/pq"
)
+// GetEntryQueryBuilder returns a new EntryQueryBuilder
func (s *Storage) GetEntryQueryBuilder(userID int64, timezone string) *EntryQueryBuilder {
return NewEntryQueryBuilder(s, userID, timezone)
}
+// CreateEntry add a new entry.
func (s *Storage) CreateEntry(entry *model.Entry) error {
query := `
INSERT INTO entries
@@ -55,6 +58,7 @@ func (s *Storage) CreateEntry(entry *model.Entry) error {
return nil
}
+// UpdateEntry update an entry when a feed is refreshed.
func (s *Storage) UpdateEntry(entry *model.Entry) error {
query := `
UPDATE entries SET
@@ -76,6 +80,7 @@ func (s *Storage) UpdateEntry(entry *model.Entry) error {
return err
}
+// EntryExists checks if an entry already exists based on its hash when refreshing a feed.
func (s *Storage) EntryExists(entry *model.Entry) bool {
var result int
query := `SELECT count(*) as c FROM entries WHERE user_id=$1 AND feed_id=$2 AND hash=$3`
@@ -83,6 +88,7 @@ func (s *Storage) EntryExists(entry *model.Entry) bool {
return result >= 1
}
+// UpdateEntries update a list of entries while refreshing a feed.
func (s *Storage) UpdateEntries(userID, feedID int64, entries model.Entries) (err error) {
for _, entry := range entries {
entry.UserID = userID
@@ -102,22 +108,36 @@ func (s *Storage) UpdateEntries(userID, feedID int64, entries model.Entries) (er
return nil
}
+// SetEntriesStatus update the status of the given list of entries.
func (s *Storage) SetEntriesStatus(userID int64, entryIDs []int64, status string) error {
defer helper.ExecutionTime(time.Now(), fmt.Sprintf("[Storage:SetEntriesStatus] userID=%d, entryIDs=%v, status=%s", userID, entryIDs, status))
query := `UPDATE entries SET status=$1 WHERE user_id=$2 AND id=ANY($3)`
result, err := s.db.Exec(query, status, userID, pq.Array(entryIDs))
if err != nil {
- return fmt.Errorf("Unable to update entry status: %v", err)
+ return fmt.Errorf("unable to update entries status: %v", err)
}
count, err := result.RowsAffected()
if err != nil {
- return fmt.Errorf("Unable to update this entry: %v", err)
+ return fmt.Errorf("unable to update these entries: %v", err)
}
if count == 0 {
- return errors.New("Nothing has been updated")
+ return errors.New("nothing has been updated")
+ }
+
+ return nil
+}
+
+// FlushHistory set all entries with the status "read" to "removed".
+func (s *Storage) FlushHistory(userID int64) error {
+ defer helper.ExecutionTime(time.Now(), fmt.Sprintf("[Storage:FlushHistory] userID=%d", userID))
+
+ query := `UPDATE entries SET status=$1 WHERE user_id=$2 AND status=$3`
+ _, err := s.db.Exec(query, model.EntryStatusRemoved, userID, model.EntryStatusRead)
+ if err != nil {
+ return fmt.Errorf("unable to flush history: %v", err)
}
return nil