From 74b69a4c7ccdc6ed2add007f5bc01e0619766067 Mon Sep 17 00:00:00 2001 From: Dave Marquard Date: Sun, 23 Oct 2022 16:49:45 -0700 Subject: [PATCH] Add support for the `continuation` parameter and result for Google Reader API ID calls Support the ``continuation parameter and result for Google Reader API ID calls. Allows clients to support paging of results / fetching additional results when the number of entries exceed the limit parameter passed in by the client. --- googlereader/handler.go | 78 +++++++++++++++++++++++++++++++++++----- googlereader/response.go | 3 +- 2 files changed, 72 insertions(+), 9 deletions(-) diff --git a/googlereader/handler.go b/googlereader/handler.go index dc3ce1dd..c55925e6 100644 --- a/googlereader/handler.go +++ b/googlereader/handler.go @@ -88,8 +88,10 @@ const ( ParamTitle = "t" // ParamQuickAdd - name of the parameter for a URL being quick subscribed to ParamQuickAdd = "quickadd" - // ParamDestination - name fo the parameter for the new name of a tag + // ParamDestination - name of the parameter for the new name of a tag ParamDestination = "dest" + // ParamContinuation - name of the parameter for callers to pass to receive the next page of results + ParamContinuation = "c" ) // StreamType represents the possible stream types @@ -130,6 +132,7 @@ type RequestModifiers struct { FilterTargets []Stream Streams []Stream Count int + Offset int SortDirection string StartTime int64 StopTime int64 @@ -185,6 +188,7 @@ func (r RequestModifiers) String() string { result += fmt.Sprintf(" %v\n", s) } result += fmt.Sprintf("Count: %d\n", r.Count) + result += fmt.Sprintf("Offset: %d\n", r.Offset) result += fmt.Sprintf("Sort Direction: %s\n", r.SortDirection) result += fmt.Sprintf("Continuation Token: %s\n", r.ContinuationToken) result += fmt.Sprintf("Start Time: %d\n", r.StartTime) @@ -243,8 +247,9 @@ func getStreamFilterModifiers(r *http.Request) (RequestModifiers, error) { } result.Count = request.QueryIntParam(r, ParamStreamMaxItems, 0) - result.StartTime = int64(request.QueryIntParam(r, ParamStreamStartTime, 0)) - result.StopTime = int64(request.QueryIntParam(r, ParamStreamStopTime, 0)) + result.Offset = request.QueryIntParam(r, ParamContinuation, 0) + result.StartTime = request.QueryInt64Param(r, ParamStreamStartTime, int64(0)) + result.StopTime = request.QueryInt64Param(r, ParamStreamStopTime, int64(0)) return result, nil } @@ -1120,7 +1125,9 @@ func (h *handler) handleReadingListStream(w http.ResponseWriter, r *http.Request logger.Info("[GoogleReader][ReadingListStreamIDs][ClientIP=%s] xt filter type: %#v", clientIP, s) } } + builder.WithoutStatus(model.EntryStatusRemoved) builder.WithLimit(rm.Count) + builder.WithOffset(rm.Offset) builder.WithOrder(model.DefaultSortingOrder) builder.WithDirection(rm.SortDirection) if rm.StartTime > 0 { @@ -1141,15 +1148,29 @@ func (h *handler) handleReadingListStream(w http.ResponseWriter, r *http.Request formattedID := strconv.FormatInt(entryID, 10) itemRefs = append(itemRefs, itemRef{ID: formattedID}) } - json.OK(w, r, streamIDResponse{itemRefs}) + + totalEntries, err := builder.CountEntries() + if err != nil { + logger.Error("[GoogleReader][/stream/items/ids#reading-list] [ClientIP=%s] %v", clientIP, err) + json.ServerError(w, r, err) + return + } + continuation := 0 + if len(itemRefs)+rm.Offset < totalEntries { + continuation = len(itemRefs) + rm.Offset + } + + json.OK(w, r, streamIDResponse{itemRefs, continuation}) } func (h *handler) handleStarredStream(w http.ResponseWriter, r *http.Request, rm RequestModifiers) { clientIP := request.ClientIP(r) builder := h.store.NewEntryQueryBuilder(rm.UserID) + builder.WithoutStatus(model.EntryStatusRemoved) builder.WithStarred(true) builder.WithLimit(rm.Count) + builder.WithOffset(rm.Offset) builder.WithOrder(model.DefaultSortingOrder) builder.WithDirection(rm.SortDirection) if rm.StartTime > 0 { @@ -1170,14 +1191,29 @@ func (h *handler) handleStarredStream(w http.ResponseWriter, r *http.Request, rm formattedID := strconv.FormatInt(entryID, 10) itemRefs = append(itemRefs, itemRef{ID: formattedID}) } - json.OK(w, r, streamIDResponse{itemRefs}) + + totalEntries, err := builder.CountEntries() + if err != nil { + logger.Error("[GoogleReader][/stream/items/ids#starred] [ClientIP=%s] %v", clientIP, err) + json.ServerError(w, r, err) + return + } + continuation := 0 + if len(itemRefs)+rm.Offset < totalEntries { + continuation = len(itemRefs) + rm.Offset + } + + json.OK(w, r, streamIDResponse{itemRefs, continuation}) } func (h *handler) handleReadStream(w http.ResponseWriter, r *http.Request, rm RequestModifiers) { clientIP := request.ClientIP(r) builder := h.store.NewEntryQueryBuilder(rm.UserID) + builder.WithoutStatus(model.EntryStatusRemoved) builder.WithStatus(model.EntryStatusRead) + builder.WithLimit(rm.Count) + builder.WithOffset(rm.Offset) builder.WithOrder(model.DefaultSortingOrder) builder.WithDirection(rm.SortDirection) if rm.StartTime > 0 { @@ -1198,7 +1234,19 @@ func (h *handler) handleReadStream(w http.ResponseWriter, r *http.Request, rm Re formattedID := strconv.FormatInt(entryID, 10) itemRefs = append(itemRefs, itemRef{ID: formattedID}) } - json.OK(w, r, streamIDResponse{itemRefs}) + + totalEntries, err := builder.CountEntries() + if err != nil { + logger.Error("[GoogleReader][/stream/items/ids#read] [ClientIP=%s] %v", clientIP, err) + json.ServerError(w, r, err) + return + } + continuation := 0 + if len(itemRefs)+rm.Offset < totalEntries { + continuation = len(itemRefs) + rm.Offset + } + + json.OK(w, r, streamIDResponse{itemRefs, continuation}) } func (h *handler) handleFeedStream(w http.ResponseWriter, r *http.Request, rm RequestModifiers) { @@ -1211,8 +1259,10 @@ func (h *handler) handleFeedStream(w http.ResponseWriter, r *http.Request, rm Re } builder := h.store.NewEntryQueryBuilder(rm.UserID) + builder.WithoutStatus(model.EntryStatusRemoved) builder.WithFeedID(feedID) builder.WithLimit(rm.Count) + builder.WithOffset(rm.Offset) builder.WithOrder(model.DefaultSortingOrder) builder.WithDirection(rm.SortDirection) if rm.StartTime > 0 { @@ -1224,7 +1274,7 @@ func (h *handler) handleFeedStream(w http.ResponseWriter, r *http.Request, rm Re rawEntryIDs, err := builder.GetEntryIDs() if err != nil { - logger.Error("[GoogleReader][/stream/items/ids#starred] [ClientIP=%s] %v", clientIP, err) + logger.Error("[GoogleReader][/stream/items/ids#feed] [ClientIP=%s] %v", clientIP, err) json.ServerError(w, r, err) return } @@ -1233,5 +1283,17 @@ func (h *handler) handleFeedStream(w http.ResponseWriter, r *http.Request, rm Re formattedID := strconv.FormatInt(entryID, 10) itemRefs = append(itemRefs, itemRef{ID: formattedID}) } - json.OK(w, r, streamIDResponse{itemRefs}) + + totalEntries, err := builder.CountEntries() + if err != nil { + logger.Error("[GoogleReader][/stream/items/ids#feed] [ClientIP=%s] %v", clientIP, err) + json.ServerError(w, r, err) + return + } + continuation := 0 + if len(itemRefs)+rm.Offset < totalEntries { + continuation = len(itemRefs) + rm.Offset + } + + json.OK(w, r, streamIDResponse{itemRefs, continuation}) } diff --git a/googlereader/response.go b/googlereader/response.go index a528358c..58501256 100644 --- a/googlereader/response.go +++ b/googlereader/response.go @@ -61,7 +61,8 @@ type itemRef struct { } type streamIDResponse struct { - ItemRefs []itemRef `json:"itemRefs"` + ItemRefs []itemRef `json:"itemRefs"` + Continuation int `json:"continuation,omitempty,string"` } type tagsResponse struct {