Преглед изворни кода

feat(googlereader): add `mark-all-as-read` endpoint

Frédéric Guillot пре 11 месеци
родитељ
комит
50395f13ca
2 измењених фајлова са 112 додато и 4 уклоњено
  1. 86 4
      internal/googlereader/handler.go
  2. 26 0
      internal/storage/entry.go

+ 86 - 4
internal/googlereader/handler.go

@@ -95,6 +95,8 @@ const (
 	ParamDestination = "dest"
 	// ParamContinuation -  name of the parameter for callers to pass to receive the next page of results
 	ParamContinuation = "c"
+	// ParamStreamType - name of the parameter for unix timestamp
+	ParamTimestamp = "ts"
 )
 
 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("/stream/items/ids", handler.streamItemIDsHandler).Methods(http.MethodGet).Name("StreamItemIDs")
 	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")
 }
 
@@ -324,12 +327,12 @@ func checkAndSimplifyTags(addTags []Stream, removeTags []Stream) (map[StreamType
 		switch s.Type {
 		case ReadStream:
 			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
 		case KeptUnreadStream:
 			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
 		case StarredStream:
@@ -344,12 +347,12 @@ func checkAndSimplifyTags(addTags []Stream, removeTags []Stream) (map[StreamType
 		switch s.Type {
 		case ReadStream:
 			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
 		case KeptUnreadStream:
 			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
 		case StarredStream:
@@ -1551,3 +1554,82 @@ func (h *handler) handleFeedStreamHandler(w http.ResponseWriter, r *http.Request
 
 	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)
+}

+ 26 - 0
internal/storage/entry.go

@@ -476,6 +476,30 @@ func (s *Storage) MarkAllAsRead(userID int64) error {
 	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.
 func (s *Storage) MarkGloballyVisibleFeedsAsRead(userID int64) error {
 	query := `
@@ -527,6 +551,7 @@ func (s *Storage) MarkFeedAsRead(userID, feedID int64, before time.Time) error {
 		slog.Int64("user_id", userID),
 		slog.Int64("feed_id", feedID),
 		slog.Int64("nb_entries", count),
+		slog.String("before", before.Format(time.RFC3339)),
 	)
 
 	return nil
@@ -563,6 +588,7 @@ func (s *Storage) MarkCategoryAsRead(userID, categoryID int64, before time.Time)
 		slog.Int64("user_id", userID),
 		slog.Int64("category_id", categoryID),
 		slog.Int64("nb_entries", count),
+		slog.String("before", before.Format(time.RFC3339)),
 	)
 
 	return nil