mirror of
https://github.com/miniflux/v2.git
synced 2025-06-27 16:36:00 +00:00
feat(googlereader): add mark-all-as-read
endpoint
This commit is contained in:
parent
e8c3435bb9
commit
50395f13ca
2 changed files with 112 additions and 4 deletions
|
@ -95,6 +95,8 @@ const (
|
||||||
ParamDestination = "dest"
|
ParamDestination = "dest"
|
||||||
// ParamContinuation - name of the parameter for callers to pass to receive the next page of results
|
// ParamContinuation - name of the parameter for callers to pass to receive the next page of results
|
||||||
ParamContinuation = "c"
|
ParamContinuation = "c"
|
||||||
|
// ParamStreamType - name of the parameter for unix timestamp
|
||||||
|
ParamTimestamp = "ts"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -234,6 +236,7 @@ func Serve(router *mux.Router, store *storage.Storage) {
|
||||||
sr.HandleFunc("/subscription/quickadd", handler.quickAddHandler).Methods(http.MethodPost).Name("QuickAdd")
|
sr.HandleFunc("/subscription/quickadd", handler.quickAddHandler).Methods(http.MethodPost).Name("QuickAdd")
|
||||||
sr.HandleFunc("/stream/items/ids", handler.streamItemIDsHandler).Methods(http.MethodGet).Name("StreamItemIDs")
|
sr.HandleFunc("/stream/items/ids", handler.streamItemIDsHandler).Methods(http.MethodGet).Name("StreamItemIDs")
|
||||||
sr.HandleFunc("/stream/items/contents", handler.streamItemContentsHandler).Methods(http.MethodPost).Name("StreamItemsContents")
|
sr.HandleFunc("/stream/items/contents", handler.streamItemContentsHandler).Methods(http.MethodPost).Name("StreamItemsContents")
|
||||||
|
sr.HandleFunc("/mark-all-as-read", handler.markAllAsReadHandler).Methods(http.MethodPost).Name("MarkAllAsRead")
|
||||||
sr.PathPrefix("/").HandlerFunc(handler.serveHandler).Methods(http.MethodPost, http.MethodGet).Name("GoogleReaderApiEndpoint")
|
sr.PathPrefix("/").HandlerFunc(handler.serveHandler).Methods(http.MethodPost, http.MethodGet).Name("GoogleReaderApiEndpoint")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -324,12 +327,12 @@ func checkAndSimplifyTags(addTags []Stream, removeTags []Stream) (map[StreamType
|
||||||
switch s.Type {
|
switch s.Type {
|
||||||
case ReadStream:
|
case ReadStream:
|
||||||
if _, ok := tags[KeptUnreadStream]; ok {
|
if _, ok := tags[KeptUnreadStream]; ok {
|
||||||
return nil, fmt.Errorf("googlereader: %s ad %s should not be supplied simultaneously", KeptUnread, Read)
|
return nil, fmt.Errorf("googlereader: %s and %s should not be supplied simultaneously", KeptUnread, Read)
|
||||||
}
|
}
|
||||||
tags[ReadStream] = true
|
tags[ReadStream] = true
|
||||||
case KeptUnreadStream:
|
case KeptUnreadStream:
|
||||||
if _, ok := tags[ReadStream]; ok {
|
if _, ok := tags[ReadStream]; ok {
|
||||||
return nil, fmt.Errorf("googlereader: %s ad %s should not be supplied simultaneously", KeptUnread, Read)
|
return nil, fmt.Errorf("googlereader: %s and %s should not be supplied simultaneously", KeptUnread, Read)
|
||||||
}
|
}
|
||||||
tags[ReadStream] = false
|
tags[ReadStream] = false
|
||||||
case StarredStream:
|
case StarredStream:
|
||||||
|
@ -344,12 +347,12 @@ func checkAndSimplifyTags(addTags []Stream, removeTags []Stream) (map[StreamType
|
||||||
switch s.Type {
|
switch s.Type {
|
||||||
case ReadStream:
|
case ReadStream:
|
||||||
if _, ok := tags[ReadStream]; ok {
|
if _, ok := tags[ReadStream]; ok {
|
||||||
return nil, fmt.Errorf("googlereader: %s ad %s should not be supplied simultaneously", KeptUnread, Read)
|
return nil, fmt.Errorf("googlereader: %s and %s should not be supplied simultaneously", KeptUnread, Read)
|
||||||
}
|
}
|
||||||
tags[ReadStream] = false
|
tags[ReadStream] = false
|
||||||
case KeptUnreadStream:
|
case KeptUnreadStream:
|
||||||
if _, ok := tags[ReadStream]; ok {
|
if _, ok := tags[ReadStream]; ok {
|
||||||
return nil, fmt.Errorf("googlereader: %s ad %s should not be supplied simultaneously", KeptUnread, Read)
|
return nil, fmt.Errorf("googlereader: %s and %s should not be supplied simultaneously", KeptUnread, Read)
|
||||||
}
|
}
|
||||||
tags[ReadStream] = true
|
tags[ReadStream] = true
|
||||||
case StarredStream:
|
case StarredStream:
|
||||||
|
@ -1551,3 +1554,82 @@ func (h *handler) handleFeedStreamHandler(w http.ResponseWriter, r *http.Request
|
||||||
|
|
||||||
json.OK(w, r, streamIDResponse{itemRefs, continuation})
|
json.OK(w, r, streamIDResponse{itemRefs, continuation})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *handler) markAllAsReadHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
userID := request.UserID(r)
|
||||||
|
clientIP := request.ClientIP(r)
|
||||||
|
|
||||||
|
slog.Debug("[GoogleReader] Handle /mark-all-as-read",
|
||||||
|
slog.String("handler", "markAllAsReadHandler"),
|
||||||
|
slog.String("client_ip", clientIP),
|
||||||
|
slog.String("user_agent", r.UserAgent()),
|
||||||
|
)
|
||||||
|
|
||||||
|
if err := r.ParseForm(); err != nil {
|
||||||
|
json.BadRequest(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
stream, err := getStream(r.Form.Get(ParamStreamID), userID)
|
||||||
|
if err != nil {
|
||||||
|
json.BadRequest(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var before time.Time
|
||||||
|
if timestampParamValue := r.Form.Get(ParamTimestamp); timestampParamValue != "" {
|
||||||
|
timestampParsedValue, err := strconv.ParseInt(timestampParamValue, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
json.BadRequest(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if timestampParsedValue > 0 {
|
||||||
|
// It's unclear if the timestamp is in seconds or microseconds, so we try both using a naive approach.
|
||||||
|
if len(timestampParamValue) >= 16 {
|
||||||
|
before = time.UnixMicro(timestampParsedValue)
|
||||||
|
} else {
|
||||||
|
before = time.Unix(timestampParsedValue, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if before.IsZero() {
|
||||||
|
before = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
switch stream.Type {
|
||||||
|
case FeedStream:
|
||||||
|
feedID, err := strconv.ParseInt(stream.ID, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
json.BadRequest(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = h.store.MarkFeedAsRead(userID, feedID, before)
|
||||||
|
if err != nil {
|
||||||
|
json.ServerError(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case LabelStream:
|
||||||
|
category, err := h.store.CategoryByTitle(userID, stream.ID)
|
||||||
|
if err != nil {
|
||||||
|
json.ServerError(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if category == nil {
|
||||||
|
json.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := h.store.MarkCategoryAsRead(userID, category.ID, before); err != nil {
|
||||||
|
json.ServerError(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case ReadingListStream:
|
||||||
|
if err = h.store.MarkAllAsReadBeforeDate(userID, before); err != nil {
|
||||||
|
json.ServerError(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OK(w, r)
|
||||||
|
}
|
||||||
|
|
|
@ -476,6 +476,30 @@ func (s *Storage) MarkAllAsRead(userID int64) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MarkAllAsReadBeforeDate updates all user entries to the read status before the given date.
|
||||||
|
func (s *Storage) MarkAllAsReadBeforeDate(userID int64, before time.Time) error {
|
||||||
|
query := `
|
||||||
|
UPDATE
|
||||||
|
entries
|
||||||
|
SET
|
||||||
|
status=$1,
|
||||||
|
changed_at=now()
|
||||||
|
WHERE
|
||||||
|
user_id=$2 AND status=$3 AND published_at < $4
|
||||||
|
`
|
||||||
|
result, err := s.db.Exec(query, model.EntryStatusRead, userID, model.EntryStatusUnread, before)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf(`store: unable to mark all entries as read before %s: %v`, before.Format(time.RFC3339), err)
|
||||||
|
}
|
||||||
|
count, _ := result.RowsAffected()
|
||||||
|
slog.Debug("Marked all entries as read before date",
|
||||||
|
slog.Int64("user_id", userID),
|
||||||
|
slog.Int64("nb_entries", count),
|
||||||
|
slog.String("before", before.Format(time.RFC3339)),
|
||||||
|
)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// MarkGloballyVisibleFeedsAsRead updates all user entries to the read status.
|
// MarkGloballyVisibleFeedsAsRead updates all user entries to the read status.
|
||||||
func (s *Storage) MarkGloballyVisibleFeedsAsRead(userID int64) error {
|
func (s *Storage) MarkGloballyVisibleFeedsAsRead(userID int64) error {
|
||||||
query := `
|
query := `
|
||||||
|
@ -527,6 +551,7 @@ func (s *Storage) MarkFeedAsRead(userID, feedID int64, before time.Time) error {
|
||||||
slog.Int64("user_id", userID),
|
slog.Int64("user_id", userID),
|
||||||
slog.Int64("feed_id", feedID),
|
slog.Int64("feed_id", feedID),
|
||||||
slog.Int64("nb_entries", count),
|
slog.Int64("nb_entries", count),
|
||||||
|
slog.String("before", before.Format(time.RFC3339)),
|
||||||
)
|
)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -563,6 +588,7 @@ func (s *Storage) MarkCategoryAsRead(userID, categoryID int64, before time.Time)
|
||||||
slog.Int64("user_id", userID),
|
slog.Int64("user_id", userID),
|
||||||
slog.Int64("category_id", categoryID),
|
slog.Int64("category_id", categoryID),
|
||||||
slog.Int64("nb_entries", count),
|
slog.Int64("nb_entries", count),
|
||||||
|
slog.String("before", before.Format(time.RFC3339)),
|
||||||
)
|
)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue