瀏覽代碼

Add flush history feature

Frédéric Guillot 8 年之前
父節點
當前提交
549a4277b0

+ 2 - 1
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

+ 1 - 1
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
 

+ 7 - 0
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"

+ 4 - 1
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() {

+ 1 - 0
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")
 

+ 1 - 1
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
 

+ 1 - 1
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
 

+ 1 - 1
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
 

+ 1 - 1
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
 

+ 5 - 0
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 }}

+ 7 - 2
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",

+ 9 - 3
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
 	}
 

+ 14 - 0
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"))
+}

+ 1 - 1
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
 

+ 24 - 4
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