From 653e80be8e934867fc3efdf5eb4616eda95f45a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Guillot?= Date: Sun, 4 May 2025 12:52:59 -0700 Subject: [PATCH] refactor(googlereader): move constants to separate files --- internal/googlereader/handler.go | 258 +--------------------- internal/googlereader/parameters.go | 39 ++++ internal/googlereader/prefix_suffix.go | 31 +++ internal/googlereader/request_modifier.go | 91 ++++++++ internal/googlereader/stream.go | 119 ++++++++++ 5 files changed, 282 insertions(+), 256 deletions(-) create mode 100644 internal/googlereader/parameters.go create mode 100644 internal/googlereader/prefix_suffix.go create mode 100644 internal/googlereader/request_modifier.go create mode 100644 internal/googlereader/stream.go diff --git a/internal/googlereader/handler.go b/internal/googlereader/handler.go index 7f0753f4..09f66ae7 100644 --- a/internal/googlereader/handler.go +++ b/internal/googlereader/handler.go @@ -9,7 +9,6 @@ import ( "log/slog" "net/http" "strconv" - "strings" "time" "miniflux.app/v2/internal/config" @@ -35,184 +34,12 @@ type handler struct { router *mux.Router } -const ( - // StreamPrefix is the prefix for astreams (read/starred/reading list and so on) - StreamPrefix = "user/-/state/com.google/" - // UserStreamPrefix is the user specific prefix for streams (read/starred/reading list and so on) - UserStreamPrefix = "user/%d/state/com.google/" - // LabelPrefix is the prefix for a label stream - LabelPrefix = "user/-/label/" - // UserLabelPrefix is the user specific prefix prefix for a label stream - UserLabelPrefix = "user/%d/label/" - // FeedPrefix is the prefix for a feed stream - FeedPrefix = "feed/" - // Read is the suffix for read stream - Read = "read" - // Starred is the suffix for starred stream - Starred = "starred" - // ReadingList is the suffix for reading list stream - ReadingList = "reading-list" - // KeptUnread is the suffix for kept unread stream - KeptUnread = "kept-unread" - // Broadcast is the suffix for broadcast stream - Broadcast = "broadcast" - // BroadcastFriends is the suffix for broadcast friends stream - BroadcastFriends = "broadcast-friends" - // Like is the suffix for like stream - Like = "like" -) - -const ( - // ParamItemIDs - name of the parameter with the item ids - ParamItemIDs = "i" - // ParamStreamID - name of the parameter containing the stream to be included - ParamStreamID = "s" - // ParamStreamExcludes - name of the parameter containing streams to be excluded - ParamStreamExcludes = "xt" - // ParamStreamFilters - name of the parameter containing streams to be included - ParamStreamFilters = "it" - // ParamStreamMaxItems - name of the parameter containing number of items per page/max items returned - ParamStreamMaxItems = "n" - // ParamStreamOrder - name of the parameter containing the sort criteria - ParamStreamOrder = "r" - // ParamStreamStartTime - name of the parameter containing epoch timestamp, filtering items older than - ParamStreamStartTime = "ot" - // ParamStreamStopTime - name of the parameter containing epoch timestamp, filtering items newer than - ParamStreamStopTime = "nt" - // ParamTagsRemove - name of the parameter containing tags (streams) to be removed - ParamTagsRemove = "r" - // ParamTagsAdd - name of the parameter containing tags (streams) to be added - ParamTagsAdd = "a" - // ParamSubscribeAction - name of the parameter indicating the action to take for subscription/edit - ParamSubscribeAction = "ac" - // ParamTitle - name of the parameter for the title of the subscription - ParamTitle = "t" - // ParamQuickAdd - name of the parameter for a URL being quick subscribed to - ParamQuickAdd = "quickadd" - // 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" - // ParamStreamType - name of the parameter for unix timestamp - ParamTimestamp = "ts" -) - var ( errEmptyFeedTitle = errors.New("googlereader: empty feed title") errFeedNotFound = errors.New("googlereader: feed not found") errCategoryNotFound = errors.New("googlereader: category not found") ) -// StreamType represents the possible stream types -type StreamType int - -const ( - // NoStream - no stream type - NoStream StreamType = iota - // ReadStream - read stream type - ReadStream - // StarredStream - starred stream type - StarredStream - // ReadingListStream - reading list stream type - ReadingListStream - // KeptUnreadStream - kept unread stream type - KeptUnreadStream - // BroadcastStream - broadcast stream type - BroadcastStream - // BroadcastFriendsStream - broadcast friends stream type - BroadcastFriendsStream - // LabelStream - label stream type - LabelStream - // FeedStream - feed stream type - FeedStream - // LikeStream - like stream type - LikeStream -) - -// Stream defines a stream type and its ID. -type Stream struct { - Type StreamType - ID string -} - -func (s Stream) String() string { - return fmt.Sprintf("%v - '%s'", s.Type, s.ID) -} - -func (st StreamType) String() string { - switch st { - case NoStream: - return "NoStream" - case ReadStream: - return "ReadStream" - case StarredStream: - return "StarredStream" - case ReadingListStream: - return "ReadingListStream" - case KeptUnreadStream: - return "KeptUnreadStream" - case BroadcastStream: - return "BroadcastStream" - case BroadcastFriendsStream: - return "BroadcastFriendsStream" - case LabelStream: - return "LabelStream" - case FeedStream: - return "FeedStream" - case LikeStream: - return "LikeStream" - default: - return st.String() - } -} - -// RequestModifiers are the parsed request parameters. -type RequestModifiers struct { - ExcludeTargets []Stream - FilterTargets []Stream - Streams []Stream - Count int - Offset int - SortDirection string - StartTime int64 - StopTime int64 - ContinuationToken string - UserID int64 -} - -func (r RequestModifiers) String() string { - var results []string - - results = append(results, fmt.Sprintf("UserID: %d", r.UserID)) - - var streamStr []string - for _, s := range r.Streams { - streamStr = append(streamStr, s.String()) - } - results = append(results, fmt.Sprintf("Streams: [%s]", strings.Join(streamStr, ", "))) - - var exclusions []string - for _, s := range r.ExcludeTargets { - exclusions = append(exclusions, s.String()) - } - results = append(results, fmt.Sprintf("Exclusions: [%s]", strings.Join(exclusions, ", "))) - - var filters []string - for _, s := range r.FilterTargets { - filters = append(filters, s.String()) - } - results = append(results, fmt.Sprintf("Filters: [%s]", strings.Join(filters, ", "))) - - results = append(results, fmt.Sprintf("Count: %d", r.Count)) - results = append(results, fmt.Sprintf("Offset: %d", r.Offset)) - results = append(results, fmt.Sprintf("Sort Direction: %s", r.SortDirection)) - results = append(results, fmt.Sprintf("Continuation Token: %s", r.ContinuationToken)) - results = append(results, fmt.Sprintf("Start Time: %d", r.StartTime)) - results = append(results, fmt.Sprintf("Stop Time: %d", r.StopTime)) - - return strings.Join(results, "; ") -} - // Serve handles Google Reader API calls. func Serve(router *mux.Router, store *storage.Storage) { handler := &handler{store, router} @@ -238,87 +65,6 @@ func Serve(router *mux.Router, store *storage.Storage) { sr.PathPrefix("/").HandlerFunc(handler.serveHandler).Methods(http.MethodPost, http.MethodGet).Name("GoogleReaderApiEndpoint") } -func getStreamFilterModifiers(r *http.Request) (RequestModifiers, error) { - userID := request.UserID(r) - - result := RequestModifiers{ - SortDirection: "desc", - UserID: userID, - } - streamOrder := request.QueryStringParam(r, ParamStreamOrder, "d") - if streamOrder == "o" { - result.SortDirection = "asc" - } - var err error - result.Streams, err = getStreams(request.QueryStringParamList(r, ParamStreamID), userID) - if err != nil { - return RequestModifiers{}, err - } - result.ExcludeTargets, err = getStreams(request.QueryStringParamList(r, ParamStreamExcludes), userID) - if err != nil { - return RequestModifiers{}, err - } - - result.FilterTargets, err = getStreams(request.QueryStringParamList(r, ParamStreamFilters), userID) - if err != nil { - return RequestModifiers{}, err - } - - result.Count = request.QueryIntParam(r, ParamStreamMaxItems, 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 -} - -func getStream(streamID string, userID int64) (Stream, error) { - switch { - case strings.HasPrefix(streamID, FeedPrefix): - return Stream{Type: FeedStream, ID: strings.TrimPrefix(streamID, FeedPrefix)}, nil - case strings.HasPrefix(streamID, fmt.Sprintf(UserStreamPrefix, userID)) || strings.HasPrefix(streamID, StreamPrefix): - id := strings.TrimPrefix(streamID, fmt.Sprintf(UserStreamPrefix, userID)) - id = strings.TrimPrefix(id, StreamPrefix) - switch id { - case Read: - return Stream{ReadStream, ""}, nil - case Starred: - return Stream{StarredStream, ""}, nil - case ReadingList: - return Stream{ReadingListStream, ""}, nil - case KeptUnread: - return Stream{KeptUnreadStream, ""}, nil - case Broadcast: - return Stream{BroadcastStream, ""}, nil - case BroadcastFriends: - return Stream{BroadcastFriendsStream, ""}, nil - case Like: - return Stream{LikeStream, ""}, nil - default: - return Stream{NoStream, ""}, fmt.Errorf("googlereader: unknown stream with id: %s", id) - } - case strings.HasPrefix(streamID, fmt.Sprintf(UserLabelPrefix, userID)) || strings.HasPrefix(streamID, LabelPrefix): - id := strings.TrimPrefix(streamID, fmt.Sprintf(UserLabelPrefix, userID)) - id = strings.TrimPrefix(id, LabelPrefix) - return Stream{LabelStream, id}, nil - case streamID == "": - return Stream{NoStream, ""}, nil - default: - return Stream{NoStream, ""}, fmt.Errorf("googlereader: unknown stream type: %s", streamID) - } -} - -func getStreams(streamIDs []string, userID int64) ([]Stream, error) { - streams := make([]Stream, 0) - for _, streamID := range streamIDs { - stream, err := getStream(streamID, userID) - if err != nil { - return []Stream{}, err - } - streams = append(streams, stream) - } - return streams, nil -} - func checkAndSimplifyTags(addTags []Stream, removeTags []Stream) (map[StreamType]bool, error) { tags := make(map[StreamType]bool) for _, s := range addTags { @@ -951,7 +697,7 @@ func (h *handler) streamItemContentsHandler(w http.ResponseWriter, r *http.Reque return } - requestModifiers, err := getStreamFilterModifiers(r) + requestModifiers, err := parseStreamFilterFromRequest(r) if err != nil { json.ServerError(w, r, err) return @@ -1303,7 +1049,7 @@ func (h *handler) streamItemIDsHandler(w http.ResponseWriter, r *http.Request) { return } - rm, err := getStreamFilterModifiers(r) + rm, err := parseStreamFilterFromRequest(r) if err != nil { json.ServerError(w, r, err) return diff --git a/internal/googlereader/parameters.go b/internal/googlereader/parameters.go new file mode 100644 index 00000000..c51414e4 --- /dev/null +++ b/internal/googlereader/parameters.go @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package googlereader // import "miniflux.app/v2/internal/googlereader" + +const ( + // ParamItemIDs - name of the parameter with the item ids + ParamItemIDs = "i" + // ParamStreamID - name of the parameter containing the stream to be included + ParamStreamID = "s" + // ParamStreamExcludes - name of the parameter containing streams to be excluded + ParamStreamExcludes = "xt" + // ParamStreamFilters - name of the parameter containing streams to be included + ParamStreamFilters = "it" + // ParamStreamMaxItems - name of the parameter containing number of items per page/max items returned + ParamStreamMaxItems = "n" + // ParamStreamOrder - name of the parameter containing the sort criteria + ParamStreamOrder = "r" + // ParamStreamStartTime - name of the parameter containing epoch timestamp, filtering items older than + ParamStreamStartTime = "ot" + // ParamStreamStopTime - name of the parameter containing epoch timestamp, filtering items newer than + ParamStreamStopTime = "nt" + // ParamTagsRemove - name of the parameter containing tags (streams) to be removed + ParamTagsRemove = "r" + // ParamTagsAdd - name of the parameter containing tags (streams) to be added + ParamTagsAdd = "a" + // ParamSubscribeAction - name of the parameter indicating the action to take for subscription/edit + ParamSubscribeAction = "ac" + // ParamTitle - name of the parameter for the title of the subscription + ParamTitle = "t" + // ParamQuickAdd - name of the parameter for a URL being quick subscribed to + ParamQuickAdd = "quickadd" + // 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" + // ParamStreamType - name of the parameter for unix timestamp + ParamTimestamp = "ts" +) diff --git a/internal/googlereader/prefix_suffix.go b/internal/googlereader/prefix_suffix.go new file mode 100644 index 00000000..fb0932d3 --- /dev/null +++ b/internal/googlereader/prefix_suffix.go @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package googlereader // import "miniflux.app/v2/internal/googlereader" + +const ( + // StreamPrefix is the prefix for astreams (read/starred/reading list and so on) + StreamPrefix = "user/-/state/com.google/" + // UserStreamPrefix is the user specific prefix for streams (read/starred/reading list and so on) + UserStreamPrefix = "user/%d/state/com.google/" + // LabelPrefix is the prefix for a label stream + LabelPrefix = "user/-/label/" + // UserLabelPrefix is the user specific prefix prefix for a label stream + UserLabelPrefix = "user/%d/label/" + // FeedPrefix is the prefix for a feed stream + FeedPrefix = "feed/" + // Read is the suffix for read stream + Read = "read" + // Starred is the suffix for starred stream + Starred = "starred" + // ReadingList is the suffix for reading list stream + ReadingList = "reading-list" + // KeptUnread is the suffix for kept unread stream + KeptUnread = "kept-unread" + // Broadcast is the suffix for broadcast stream + Broadcast = "broadcast" + // BroadcastFriends is the suffix for broadcast friends stream + BroadcastFriends = "broadcast-friends" + // Like is the suffix for like stream + Like = "like" +) diff --git a/internal/googlereader/request_modifier.go b/internal/googlereader/request_modifier.go new file mode 100644 index 00000000..052b5939 --- /dev/null +++ b/internal/googlereader/request_modifier.go @@ -0,0 +1,91 @@ +// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package googlereader // import "miniflux.app/v2/internal/googlereader" + +import ( + "fmt" + "net/http" + "strings" + + "miniflux.app/v2/internal/http/request" +) + +type RequestModifiers struct { + ExcludeTargets []Stream + FilterTargets []Stream + Streams []Stream + Count int + Offset int + SortDirection string + StartTime int64 + StopTime int64 + ContinuationToken string + UserID int64 +} + +func (r RequestModifiers) String() string { + var results []string + + results = append(results, fmt.Sprintf("UserID: %d", r.UserID)) + + var streamStr []string + for _, s := range r.Streams { + streamStr = append(streamStr, s.String()) + } + results = append(results, fmt.Sprintf("Streams: [%s]", strings.Join(streamStr, ", "))) + + var exclusions []string + for _, s := range r.ExcludeTargets { + exclusions = append(exclusions, s.String()) + } + results = append(results, fmt.Sprintf("Exclusions: [%s]", strings.Join(exclusions, ", "))) + + var filters []string + for _, s := range r.FilterTargets { + filters = append(filters, s.String()) + } + results = append(results, fmt.Sprintf("Filters: [%s]", strings.Join(filters, ", "))) + + results = append(results, fmt.Sprintf("Count: %d", r.Count)) + results = append(results, fmt.Sprintf("Offset: %d", r.Offset)) + results = append(results, fmt.Sprintf("Sort Direction: %s", r.SortDirection)) + results = append(results, fmt.Sprintf("Continuation Token: %s", r.ContinuationToken)) + results = append(results, fmt.Sprintf("Start Time: %d", r.StartTime)) + results = append(results, fmt.Sprintf("Stop Time: %d", r.StopTime)) + + return strings.Join(results, "; ") +} + +func parseStreamFilterFromRequest(r *http.Request) (RequestModifiers, error) { + userID := request.UserID(r) + result := RequestModifiers{ + SortDirection: "desc", + UserID: userID, + } + + streamOrder := request.QueryStringParam(r, ParamStreamOrder, "d") + if streamOrder == "o" { + result.SortDirection = "asc" + } + var err error + result.Streams, err = getStreams(request.QueryStringParamList(r, ParamStreamID), userID) + if err != nil { + return RequestModifiers{}, err + } + result.ExcludeTargets, err = getStreams(request.QueryStringParamList(r, ParamStreamExcludes), userID) + if err != nil { + return RequestModifiers{}, err + } + + result.FilterTargets, err = getStreams(request.QueryStringParamList(r, ParamStreamFilters), userID) + if err != nil { + return RequestModifiers{}, err + } + + result.Count = request.QueryIntParam(r, ParamStreamMaxItems, 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 +} diff --git a/internal/googlereader/stream.go b/internal/googlereader/stream.go new file mode 100644 index 00000000..262e6e78 --- /dev/null +++ b/internal/googlereader/stream.go @@ -0,0 +1,119 @@ +// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package googlereader // import "miniflux.app/v2/internal/googlereader" + +import ( + "fmt" + "strings" +) + +type StreamType int + +const ( + // NoStream - no stream type + NoStream StreamType = iota + // ReadStream - read stream type + ReadStream + // StarredStream - starred stream type + StarredStream + // ReadingListStream - reading list stream type + ReadingListStream + // KeptUnreadStream - kept unread stream type + KeptUnreadStream + // BroadcastStream - broadcast stream type + BroadcastStream + // BroadcastFriendsStream - broadcast friends stream type + BroadcastFriendsStream + // LabelStream - label stream type + LabelStream + // FeedStream - feed stream type + FeedStream + // LikeStream - like stream type + LikeStream +) + +// Stream defines a stream type and its ID. +type Stream struct { + Type StreamType + ID string +} + +func (s Stream) String() string { + return fmt.Sprintf("%v - '%s'", s.Type, s.ID) +} + +func (st StreamType) String() string { + switch st { + case NoStream: + return "NoStream" + case ReadStream: + return "ReadStream" + case StarredStream: + return "StarredStream" + case ReadingListStream: + return "ReadingListStream" + case KeptUnreadStream: + return "KeptUnreadStream" + case BroadcastStream: + return "BroadcastStream" + case BroadcastFriendsStream: + return "BroadcastFriendsStream" + case LabelStream: + return "LabelStream" + case FeedStream: + return "FeedStream" + case LikeStream: + return "LikeStream" + default: + return st.String() + } +} + +func getStream(streamID string, userID int64) (Stream, error) { + switch { + case strings.HasPrefix(streamID, FeedPrefix): + return Stream{Type: FeedStream, ID: strings.TrimPrefix(streamID, FeedPrefix)}, nil + case strings.HasPrefix(streamID, fmt.Sprintf(UserStreamPrefix, userID)) || strings.HasPrefix(streamID, StreamPrefix): + id := strings.TrimPrefix(streamID, fmt.Sprintf(UserStreamPrefix, userID)) + id = strings.TrimPrefix(id, StreamPrefix) + switch id { + case Read: + return Stream{ReadStream, ""}, nil + case Starred: + return Stream{StarredStream, ""}, nil + case ReadingList: + return Stream{ReadingListStream, ""}, nil + case KeptUnread: + return Stream{KeptUnreadStream, ""}, nil + case Broadcast: + return Stream{BroadcastStream, ""}, nil + case BroadcastFriends: + return Stream{BroadcastFriendsStream, ""}, nil + case Like: + return Stream{LikeStream, ""}, nil + default: + return Stream{NoStream, ""}, fmt.Errorf("googlereader: unknown stream with id: %s", id) + } + case strings.HasPrefix(streamID, fmt.Sprintf(UserLabelPrefix, userID)) || strings.HasPrefix(streamID, LabelPrefix): + id := strings.TrimPrefix(streamID, fmt.Sprintf(UserLabelPrefix, userID)) + id = strings.TrimPrefix(id, LabelPrefix) + return Stream{LabelStream, id}, nil + case streamID == "": + return Stream{NoStream, ""}, nil + default: + return Stream{NoStream, ""}, fmt.Errorf("googlereader: unknown stream type: %s", streamID) + } +} + +func getStreams(streamIDs []string, userID int64) ([]Stream, error) { + streams := make([]Stream, 0) + for _, streamID := range streamIDs { + stream, err := getStream(streamID, userID) + if err != nil { + return []Stream{}, err + } + streams = append(streams, stream) + } + return streams, nil +}