diff --git a/.github/workflows/initiate_release.yml b/.github/workflows/initiate_release.yml index f45e15d..29c8229 100644 --- a/.github/workflows/initiate_release.yml +++ b/.github/workflows/initiate_release.yml @@ -28,7 +28,7 @@ jobs: git push -q -u origin "release-$VERSION" - name: Get changelog diff - uses: actions/github-script@v5 + uses: actions/github-script@v6 with: script: | const get_change_log_diff = require('./scripts/get_changelog_diff.js') diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 65bb61f..fd0a06e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,11 +12,11 @@ jobs: if: github.event.pull_request.merged && startsWith(github.head_ref, 'release-') runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: fetch-depth: 0 - - uses: actions/github-script@v5 + - uses: actions/github-script@v6 with: script: | const get_change_log_diff = require('./scripts/get_changelog_diff.js') diff --git a/.github/workflows/reviewdog.yml b/.github/workflows/reviewdog.yml index 2d15bf0..291d4f8 100644 --- a/.github/workflows/reviewdog.yml +++ b/.github/workflows/reviewdog.yml @@ -24,7 +24,7 @@ jobs: - name: Install golangci-lint run: - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.45.2 + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.46.0 - name: Reviewdog env: diff --git a/README.md b/README.md index 051bdde..b5fb872 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,10 @@ Request Feature

+--- +> ## 🚨 Breaking change in v7.0 < +> From version 7.0.0, all methods' first argument is `context.Context`. The reason is that we internally changed `http.NewRequest()` to `http.NewRequestWithContext()` so that it's easier to handle cancellations, timeouts and deadlines for our users. + ## About Stream stream-go2 is a Go client for [Stream](https://getstream.io) Feeds API. @@ -170,7 +174,7 @@ for _, activity := range resp.Results { You can retrieve flat feeds with [custom ranking](https://getstream.io/docs/#custom_ranking), using the dedicated method: ```go -resp, err := flat.GetActivitiesWithRanking("popularity") +resp, err := flat.GetActivitiesWithRanking(ctx, "popularity") if err != nil { // ... } @@ -179,7 +183,7 @@ if err != nil { #### Aggregated feeds ```go -resp, err := aggregated.GetActivities() +resp, err := aggregated.GetActivities(ctx) if err != nil { // ... } @@ -200,7 +204,7 @@ for _, group := range resp.Results { #### Notification feeds ```go -resp, err := notification.GetActivities() +resp, err := notification.GetActivities(ctx) if err != nil { // ... } @@ -227,6 +231,7 @@ You can pass supported options and filters when retrieving activities: ```go resp, err := flat.GetActivities( + ctx, stream.WithActivitiesIDGTE("f505b3fb-a212-11e7-..."), stream.WithActivitiesLimit(5), ..., @@ -238,7 +243,7 @@ resp, err := flat.GetActivities( Add a single activity: ```go -resp, err := feed.AddActivity(stream.Activity{Actor: "bob", ...}) +resp, err := feed.AddActivity(ctx, stream.Activity{Actor: "bob", ...}) if err != nil { // ... } @@ -255,7 +260,7 @@ a1 := stream.Activity{Actor: "bob", ...} a2 := stream.Activity{Actor: "john", ...} a3 := stream.Activity{Actor: "alice", ...} -resp, err := feed.AddActivities(a1, a2, a3) +resp, err := feed.AddActivities(ctx, a1, a2, a3) if err != nil { // ... } @@ -271,7 +276,7 @@ for _, activity := range resp.Activities { ### Updating activities ```go -_, err := feed.UpdateActivities(a1, a2, ...) +_, err := feed.UpdateActivities(ctx, a1, a2, ...) if err != nil { // ... } @@ -284,7 +289,7 @@ You can partial update activities identified either by ID: ``` go changesetA := stream.NewUpdateActivityRequestByID("f505b3fb-a212-11e7-...", map[string]interface{}{"key": "new-value"}, []string{"removed", "keys"}) changesetB := stream.NewUpdateActivityRequestByID("f707b3fb-a212-11e7-...", map[string]interface{}{"key": "new-value"}, []string{"removed", "keys"}) -resp, err := client.PartialUpdateActivities(changesetA, changesetB) +resp, err := client.PartialUpdateActivities(ctx, changesetA, changesetB) if err != nil { // ... } @@ -294,7 +299,7 @@ or by a ForeignID and timestamp pair: ``` go changesetA := stream.NewUpdateActivityRequestByForeignID("dothings:1", stream.Time{...}, map[string]interface{}{"key": "new-value"}, []string{"removed", "keys"}) changesetB := stream.NewUpdateActivityRequestByForeignID("dothings:2", stream.Time{...}, map[string]interface{}{"key": "new-value"}, []string{"removed", "keys"}) -resp, err := client.PartialUpdateActivities(changesetA, changesetB) +resp, err := client.PartialUpdateActivities(ctx, changesetA, changesetB) if err != nil { // ... } @@ -305,12 +310,12 @@ if err != nil { You can either remove activities by ID or ForeignID: ```go -_, err := feed.RemoveActivityByID("f505b3fb-a212-11e7-...") +_, err := feed.RemoveActivityByID(ctx, "f505b3fb-a212-11e7-...") if err != nil { // ... } -_, err := feed.RemoveActivityByForeignID("bob:123") +_, err := feed.RemoveActivityByForeignID(ctx, "bob:123") if err != nil { // ... } @@ -319,7 +324,7 @@ if err != nil { ### Following another feed ```go -_, err := feed.Follow(anotherFeed) +_, err := feed.Follow(ctx, anotherFeed) if err != nil { // ... } @@ -332,7 +337,8 @@ Beware that it's possible to follow only flat feeds. You can pass options to the `Follow` method. For example: ```go -_, err := feed.Follow(anotherFeed, +_, err := feed.Follow(ctx, + anotherFeed, stream.WithFollowFeedActivityCopyLimit(15), ..., ) @@ -345,7 +351,7 @@ _, err := feed.Follow(anotherFeed, Get the feeds that a feed is following: ```go -resp, err := feed.GetFollowing() +resp, err := feed.GetFollowing(ctx) if err != nil { // ... } @@ -360,6 +366,7 @@ You can pass options to `GetFollowing`: ```go resp, err := feed.GetFollowing( + ctx, stream.WithFollowingLimit(5), ..., ) @@ -368,7 +375,7 @@ resp, err := feed.GetFollowing( #### Followers ```go -resp, err := flat.GetFollowers() +resp, err := flat.GetFollowers(ctx) if err != nil { // ... } @@ -386,6 +393,7 @@ You can pass options to `GetFollowers`: ```go resp, err := feed.GetFollowing( + ctx, stream.WithFollowersLimit(5), ..., ) @@ -394,7 +402,7 @@ resp, err := feed.GetFollowing( ### Unfollowing a feed ```go -_, err := flat.Unfollow(anotherFeed) +_, err := flat.Unfollow(ctx, anotherFeed) if err != nil { // ... } @@ -403,7 +411,8 @@ if err != nil { You can pass options to `Unfollow`: ```go -_, err := flat.Unfollow(anotherFeed, +_, err := flat.Unfollow(ctx, + anotherFeed, stream.WithUnfollowKeepHistory(true), ..., ) @@ -416,7 +425,7 @@ Remove all old targets and set new ones (replace): ```go newTargets := []stream.Feed{f1, f2} -_, err := feed.UpdateToTargets(activity, stream.WithToTargetsNew(newTargets...)) +_, err := feed.UpdateToTargets(ctx, activity, stream.WithToTargetsNew(newTargets...)) if err != nil { // ... } @@ -429,6 +438,7 @@ add := []stream.Feed{target1, target2} remove := []stream.Feed{oldTarget1, oldTarget2} _, err := feed.UpdateToTargets( + ctx, activity, stream.WithToTargetsAdd(add), stream.WithToTargetsRemove(remove), @@ -445,8 +455,8 @@ Note: you can't mix `stream.WithToTargetsNew` with `stream.WithToTargetsAdd` or You can add the same activities to multiple feeds at once with the `(*Client).AddToMany` method ([docs](https://getstream.io/docs_rest/#add_to_many)): ```go -_, err := client.AddToMany(activity, - feed1, feed2, ..., +_, err := client.AddToMany(ctx, + activity, feed1, feed2, ..., ) if err != nil { // ... @@ -463,7 +473,7 @@ relationships := []stream.FollowRelationship{ ..., } -_, err := client.FollowMany(relationships) +_, err := client.FollowMany(ctx, relationships) if err != nil { // ... } @@ -515,7 +525,7 @@ event := stream.EngagementEvent{}. WithLocation("homepage") // Track the event(s) -_, err := analytics.TrackEngagement(event) +_, err := analytics.TrackEngagement(ctx, event) if err != nil { // ... } @@ -533,7 +543,7 @@ imp := stream.ImpressionEventData{}. WithLocation("storepage") // Track the events -_, err := analytics.TrackImpression(imp) +_, err := analytics.TrackImpression(ctx, imp) if err != nil { // ... } @@ -590,7 +600,7 @@ data := map[string]interface{}{ "source_feed_slug": "timeline", "target_feed_slug": "user", } -resp, err = personalization.Get("follow_recommendations", data) +resp, err = personalization.Get(ctx, "follow_recommendations", data) if err != nil { // ... } @@ -618,25 +628,25 @@ object := stream.CollectionObject{ "location": "North America", }, } -_, err = collections.Upsert("picture", object) +_, err = collections.Upsert(ctx, "picture", object) if err != nil { // ... } // Get the data from the "picture" collection for ID "123" and "456" -objects, err := collections.Select("picture", "123", "456") +objects, err := collections.Select(ctx, "picture", "123", "456") if err != nil { // ... } // Delete the data from the "picture" collection for picture with ID "123" -_, err = collections.Delete("picture", "123") +_, err = collections.Delete(ctx, "picture", "123") if err != nil { // ... } // Get a single collection object from the "pictures" collection with ID "123" -_, err = collections.Get("pictures", "123") +_, err = collections.Get(ctx, "pictures", "123") if err != nil { // ... } @@ -662,7 +672,7 @@ user := stream.User{ }, } -insertedUser, err := users.Add(user, false) +insertedUser, err := users.Add(ctx, user, false) if err != nil { // ... } @@ -672,12 +682,12 @@ newUserData :=map[string]interface{}{ "age": 7, } -updatedUser, err := users.Update("123", newUserData) +updatedUser, err := users.Update(ctx, "123", newUserData) if err != nil { // ... } -_, err = users.Delete("123") +_, err = users.Delete(ctx, "123") if err != nil { // ... } @@ -706,7 +716,7 @@ r := stream.AddReactionRequestObject{ TargetFeeds: []string{"user:bob", "timeline:alice"}, } -comment, err := reactions.Add(r) +comment, err := reactions.Add(ctx, r) if err != nil { // ... } @@ -716,13 +726,13 @@ like := stream.AddReactionRequestObject{ UserID: "456", } -childReaction, err := reactions.AddChild(comment.ID, like) +childReaction, err := reactions.AddChild(ctx, comment.ID, like) if err != nil { // ... } // If we fetch the "comment" reaction now, it will have the child reaction(s) present. -parent, err := reactions.Get(comment.ID) +parent, err := reactions.Get(ctx, comment.ID) if err != nil { // ... } @@ -732,14 +742,15 @@ for kind, children := range parent.ChildrenReactions { } // update the target feeds for the `comment` reaction -updatedReaction, err := reactions.Update(comment.ID, nil, []string{"timeline:jeff"}) +updatedReaction, err := reactions.Update(ctx, comment.ID, nil, []string{"timeline:jeff"}) if err != nil { // ... } // get all reactions for the activity "87a9eec0-fd5f-11e8-8080-80013fed2f5b", // paginated 5 at a time, including the activity data -response, err := reactions.Filter(stream.ByActivityID("87a9eec0-fd5f-11e8-8080-80013fed2f5b"), +response, err := reactions.Filter(ctx, + stream.ByActivityID("87a9eec0-fd5f-11e8-8080-80013fed2f5b"), stream.WithLimit(5), stream.WithActivityData()) if err != nil { @@ -754,13 +765,13 @@ for _, reaction := range response.Results{ } //get the next page of reactions -response, err = reactions.GetNextPageFilteredReactions(response) +response, err = reactions.GetNextPageFilteredReactions(ctx, response) if err != nil { // ... } // get all likes by user "123" -response, err = reactions.Filter(stream.ByUserID("123").ByKind("like")) +response, err = reactions.Filter(ctx, stream.ByUserID("123").ByKind("like")) if err != nil { // ... } @@ -783,7 +794,7 @@ u := stream.User{ } // We add a user -user, err := client.Users().Add(u, true) +user, err := client.Users().Add(ctx, u, true) if err != nil { // ... } @@ -797,7 +808,7 @@ c := stream.CollectionObject{ } // We add a collection object -collectionObject, err := client.Collections().Add("picture", c) +collectionObject, err := client.Collections().Add(ctx, "picture", c) if err != nil { // ... } @@ -813,19 +824,19 @@ act := stream.Activity{ // We add the activity to the user's feed feed, _ := client.FlatFeed("user", "123") -_, err = feed.AddActivity(act) +_, err = feed.AddActivity(ctx, act) if err != nil { // ... } -result, err := feed.GetActivities() +result, err := feed.GetActivities(ctx) if err != nil { // ... } fmt.Println(result.Results[0].Actor) // Will output the user reference fmt.Println(result.Results[0].Object) // Will output the collection reference -enrichedResult, err := feed.GetEnrichedActivities() +enrichedResult, err := feed.GetEnrichedActivities(ctx) if err != nil { // ... } diff --git a/aggregated_feed.go b/aggregated_feed.go index 402ff48..0e2b5f2 100644 --- a/aggregated_feed.go +++ b/aggregated_feed.go @@ -1,6 +1,7 @@ package stream import ( + "context" "encoding/json" ) @@ -12,8 +13,8 @@ type AggregatedFeed struct { // GetActivities requests and retrieves the activities and groups for the // aggregated feed. -func (f *AggregatedFeed) GetActivities(opts ...GetActivitiesOption) (*AggregatedFeedResponse, error) { - body, err := f.client.getActivities(f, opts...) +func (f *AggregatedFeed) GetActivities(ctx context.Context, opts ...GetActivitiesOption) (*AggregatedFeedResponse, error) { + body, err := f.client.getActivities(ctx, f, opts...) if err != nil { return nil, err } @@ -26,24 +27,24 @@ func (f *AggregatedFeed) GetActivities(opts ...GetActivitiesOption) (*Aggregated // GetActivitiesWithRanking returns the activities and groups for the given AggregatedFeed, // using the provided ranking method. -func (f *AggregatedFeed) GetActivitiesWithRanking(ranking string, opts ...GetActivitiesOption) (*AggregatedFeedResponse, error) { - return f.GetActivities(append(opts, withActivitiesRanking(ranking))...) +func (f *AggregatedFeed) GetActivitiesWithRanking(ctx context.Context, ranking string, opts ...GetActivitiesOption) (*AggregatedFeedResponse, error) { + return f.GetActivities(ctx, append(opts, withActivitiesRanking(ranking))...) } // GetNextPageActivities returns the activities for the given AggregatedFeed at the "next" page // of a previous *AggregatedFeedResponse response, if any. -func (f *AggregatedFeed) GetNextPageActivities(resp *AggregatedFeedResponse) (*AggregatedFeedResponse, error) { +func (f *AggregatedFeed) GetNextPageActivities(ctx context.Context, resp *AggregatedFeedResponse) (*AggregatedFeedResponse, error) { opts, err := resp.parseNext() if err != nil { return nil, err } - return f.GetActivities(opts...) + return f.GetActivities(ctx, opts...) } // GetEnrichedActivities requests and retrieves the enriched activities and groups for the // aggregated feed. -func (f *AggregatedFeed) GetEnrichedActivities(opts ...GetActivitiesOption) (*EnrichedAggregatedFeedResponse, error) { - body, err := f.client.getEnrichedActivities(f, opts...) +func (f *AggregatedFeed) GetEnrichedActivities(ctx context.Context, opts ...GetActivitiesOption) (*EnrichedAggregatedFeedResponse, error) { + body, err := f.client.getEnrichedActivities(ctx, f, opts...) if err != nil { return nil, err } @@ -56,16 +57,16 @@ func (f *AggregatedFeed) GetEnrichedActivities(opts ...GetActivitiesOption) (*En // GetNextPageEnrichedActivities returns the enriched activities for the given AggregatedFeed at the "next" page // of a previous *EnrichedAggregatedFeedResponse response, if any. -func (f *AggregatedFeed) GetNextPageEnrichedActivities(resp *EnrichedAggregatedFeedResponse) (*EnrichedAggregatedFeedResponse, error) { +func (f *AggregatedFeed) GetNextPageEnrichedActivities(ctx context.Context, resp *EnrichedAggregatedFeedResponse) (*EnrichedAggregatedFeedResponse, error) { opts, err := resp.parseNext() if err != nil { return nil, err } - return f.GetEnrichedActivities(opts...) + return f.GetEnrichedActivities(ctx, opts...) } // GetEnrichedActivitiesWithRanking returns the enriched activities and groups for the given AggregatedFeed, // using the provided ranking method. -func (f *AggregatedFeed) GetEnrichedActivitiesWithRanking(ranking string, opts ...GetActivitiesOption) (*EnrichedAggregatedFeedResponse, error) { - return f.GetEnrichedActivities(append(opts, withActivitiesRanking(ranking))...) +func (f *AggregatedFeed) GetEnrichedActivitiesWithRanking(ctx context.Context, ranking string, opts ...GetActivitiesOption) (*EnrichedAggregatedFeedResponse, error) { + return f.GetEnrichedActivities(ctx, append(opts, withActivitiesRanking(ranking))...) } diff --git a/aggregated_feed_test.go b/aggregated_feed_test.go index 31b293b..9fd2cc4 100644 --- a/aggregated_feed_test.go +++ b/aggregated_feed_test.go @@ -1,6 +1,7 @@ package stream_test import ( + "context" "fmt" "net/http" "testing" @@ -12,6 +13,7 @@ import ( ) func TestAggregatedFeedGetActivities(t *testing.T) { + ctx := context.Background() client, requester := newClient(t) aggregated, _ := newAggregatedFeedWithUserID(client, "123") testCases := []struct { @@ -36,55 +38,56 @@ func TestAggregatedFeedGetActivities(t *testing.T) { } for _, tc := range testCases { - _, err := aggregated.GetActivities(tc.opts...) + _, err := aggregated.GetActivities(ctx, tc.opts...) assert.NoError(t, err) testRequest(t, requester.req, http.MethodGet, tc.url, "") - _, err = aggregated.GetActivitiesWithRanking("popularity", tc.opts...) + _, err = aggregated.GetActivitiesWithRanking(ctx, "popularity", tc.opts...) testRequest(t, requester.req, http.MethodGet, fmt.Sprintf("%s&ranking=popularity", tc.url), "") assert.NoError(t, err) - _, err = aggregated.GetEnrichedActivities(tc.opts...) + _, err = aggregated.GetEnrichedActivities(ctx, tc.opts...) assert.NoError(t, err) testRequest(t, requester.req, http.MethodGet, tc.enrichedURL, "") - _, err = aggregated.GetEnrichedActivitiesWithRanking("popularity", tc.opts...) + _, err = aggregated.GetEnrichedActivitiesWithRanking(ctx, "popularity", tc.opts...) testRequest(t, requester.req, http.MethodGet, fmt.Sprintf("%s&ranking=popularity", tc.enrichedURL), "") assert.NoError(t, err) } } func TestAggregatedFeedGetNextPageActivities(t *testing.T) { + ctx := context.Background() client, requester := newClient(t) aggregated, _ := newAggregatedFeedWithUserID(client, "123") requester.resp = `{"next":"/api/v1.0/feed/aggregated/123/?id_lt=78c1a709-aff2-11e7-b3a7-a45e60be7d3b&limit=25"}` - resp, err := aggregated.GetActivities() + resp, err := aggregated.GetActivities(ctx) require.NoError(t, err) - _, err = aggregated.GetNextPageActivities(resp) + _, err = aggregated.GetNextPageActivities(ctx, resp) testRequest(t, requester.req, http.MethodGet, "https://api.stream-io-api.com/api/v1.0/feed/aggregated/123/?api_key=key&id_lt=78c1a709-aff2-11e7-b3a7-a45e60be7d3b&limit=25", "") require.NoError(t, err) requester.resp = `{"next":"/api/v1.0/enrich/feed/aggregated/123/?id_lt=78c1a709-aff2-11e7-b3a7-a45e60be7d3b&limit=25"}` - enrichedResp, err := aggregated.GetEnrichedActivities() + enrichedResp, err := aggregated.GetEnrichedActivities(ctx) require.NoError(t, err) - _, err = aggregated.GetNextPageEnrichedActivities(enrichedResp) + _, err = aggregated.GetNextPageEnrichedActivities(ctx, enrichedResp) testRequest(t, requester.req, http.MethodGet, "https://api.stream-io-api.com/api/v1.0/enrich/feed/aggregated/123/?api_key=key&id_lt=78c1a709-aff2-11e7-b3a7-a45e60be7d3b&limit=25", "") require.NoError(t, err) requester.resp = `{"next":123}` - _, err = aggregated.GetActivities() + _, err = aggregated.GetActivities(ctx) require.Error(t, err) requester.resp = `{"next":"123"}` - resp, err = aggregated.GetActivities() + resp, err = aggregated.GetActivities(ctx) require.NoError(t, err) - _, err = aggregated.GetNextPageActivities(resp) + _, err = aggregated.GetNextPageActivities(ctx, resp) require.Error(t, err) requester.resp = `{"next":"?q=a%"}` - resp, err = aggregated.GetActivities() + resp, err = aggregated.GetActivities(ctx) require.NoError(t, err) - _, err = aggregated.GetNextPageActivities(resp) + _, err = aggregated.GetNextPageActivities(ctx, resp) require.Error(t, err) } diff --git a/analytics.go b/analytics.go index 38f525c..42269b4 100644 --- a/analytics.go +++ b/analytics.go @@ -1,6 +1,7 @@ package stream import ( + "context" "encoding/json" ) @@ -11,18 +12,18 @@ type AnalyticsClient struct { } // TrackEngagement is used to send and track analytics EngagementEvents. -func (c *AnalyticsClient) TrackEngagement(events ...EngagementEvent) (*BaseResponse, error) { +func (c *AnalyticsClient) TrackEngagement(ctx context.Context, events ...EngagementEvent) (*BaseResponse, error) { endpoint := c.client.makeEndpoint("engagement/") data := map[string]interface{}{ "content_list": events, } - return decode(c.client.post(endpoint, data, c.client.authenticator.analyticsAuth)) + return decode(c.client.post(ctx, endpoint, data, c.client.authenticator.analyticsAuth)) } // TrackImpression is used to send and track analytics ImpressionEvents. -func (c *AnalyticsClient) TrackImpression(eventsData ImpressionEventsData) (*BaseResponse, error) { +func (c *AnalyticsClient) TrackImpression(ctx context.Context, eventsData ImpressionEventsData) (*BaseResponse, error) { endpoint := c.client.makeEndpoint("impression/") - return decode(c.client.post(endpoint, eventsData, c.client.authenticator.analyticsAuth)) + return decode(c.client.post(ctx, endpoint, eventsData, c.client.authenticator.analyticsAuth)) } // RedirectAndTrack is used to send and track analytics ImpressionEvents. It tracks diff --git a/analytics_test.go b/analytics_test.go index ce34044..9b804f6 100644 --- a/analytics_test.go +++ b/analytics_test.go @@ -1,6 +1,7 @@ package stream_test import ( + "context" "net/http" "net/url" "testing" @@ -12,6 +13,7 @@ import ( ) func TestAnalyticsTrackEngagement(t *testing.T) { + ctx := context.Background() client, requester := newClient(t) analytics := client.Analytics() event1 := stream.EngagementEvent{}. @@ -33,7 +35,7 @@ func TestAnalyticsTrackEngagement(t *testing.T) { stream.NewEventFeature("size", "xxl"), ) - _, err := analytics.TrackEngagement(event1, event2) + _, err := analytics.TrackEngagement(ctx, event1, event2) require.NoError(t, err) expectedURL := "https://analytics.stream-io-api.com/analytics/v1.0/engagement/?api_key=key" expectedBody := `{"content_list":[{"boost":10,"content":"abcdef","feed_id":"timeline:123","label":"click","location":"hawaii","position":42,"user_data":{"alias":"John Doe","id":12345}},{"content":"aabbccdd","features":[{"group":"color","value":"red"},{"group":"size","value":"xxl"}],"feed_id":"timeline:123","label":"share","user_data":"bob"}]}` @@ -41,6 +43,7 @@ func TestAnalyticsTrackEngagement(t *testing.T) { } func TestAnalyticsTrackImpression(t *testing.T) { + ctx := context.Background() client, requester := newClient(t) analytics := client.Analytics() imp := stream.ImpressionEventsData{}. @@ -54,7 +57,7 @@ func TestAnalyticsTrackImpression(t *testing.T) { WithLocation("hawaii"). WithPosition(42) - _, err := analytics.TrackImpression(imp) + _, err := analytics.TrackImpression(ctx, imp) require.NoError(t, err) expectedURL := "https://analytics.stream-io-api.com/analytics/v1.0/impression/?api_key=key" expectedBody := `{"content_list":["a","b","c","d"],"features":[{"group":"color","value":"red"},{"group":"size","value":"xxl"}],"feed_id":"timeline:123","location":"hawaii","position":42,"user_data":123}` diff --git a/client.go b/client.go index 8990a2e..4e7ece8 100644 --- a/client.go +++ b/client.go @@ -2,6 +2,7 @@ package stream import ( "bytes" + "context" "encoding/json" "errors" "fmt" @@ -158,7 +159,7 @@ func (c *Client) GenericFeed(targetID string) (Feed, error) { } // AddToMany adds an activity to multiple feeds at once. -func (c *Client) AddToMany(activity Activity, feeds ...Feed) error { +func (c *Client) AddToMany(ctx context.Context, activity Activity, feeds ...Feed) error { endpoint := c.makeEndpoint("feed/add_to_many/") ids := make([]string, len(feeds)) for i := range feeds { @@ -168,24 +169,24 @@ func (c *Client) AddToMany(activity Activity, feeds ...Feed) error { Activity: activity, FeedIDs: ids, } - _, err := c.post(endpoint, req, c.authenticator.feedAuth(resFeed, nil)) + _, err := c.post(ctx, endpoint, req, c.authenticator.feedAuth(resFeed, nil)) return err } // FollowMany creates multiple follows at once. -func (c *Client) FollowMany(relationships []FollowRelationship, opts ...FollowManyOption) error { +func (c *Client) FollowMany(ctx context.Context, relationships []FollowRelationship, opts ...FollowManyOption) error { endpoint := c.makeEndpoint("follow_many/") for _, opt := range opts { endpoint.addQueryParam(opt) } - _, err := c.post(endpoint, relationships, c.authenticator.feedAuth(resFollower, nil)) + _, err := c.post(ctx, endpoint, relationships, c.authenticator.feedAuth(resFollower, nil)) return err } // UnfollowMany removes multiple follow relationships at once. -func (c *Client) UnfollowMany(relationships []UnfollowRelationship) error { +func (c *Client) UnfollowMany(ctx context.Context, relationships []UnfollowRelationship) error { endpoint := c.makeEndpoint("unfollow_many/") - _, err := c.post(endpoint, relationships, c.authenticator.feedAuth(resFollower, nil)) + _, err := c.post(ctx, endpoint, relationships, c.authenticator.feedAuth(resFollower, nil)) return err } @@ -229,30 +230,30 @@ func (c *Client) Personalization() *PersonalizationClient { } // GetActivitiesByID returns activities for the current app having the given IDs. -func (c *Client) GetActivitiesByID(ids ...string) (*GetActivitiesResponse, error) { - return c.getAppActivities(makeRequestOption("ids", strings.Join(ids, ","))) +func (c *Client) GetActivitiesByID(ctx context.Context, ids ...string) (*GetActivitiesResponse, error) { + return c.getAppActivities(ctx, makeRequestOption("ids", strings.Join(ids, ","))) } // GetActivitiesByForeignID returns activities for the current app having the given foreign IDs and timestamps. -func (c *Client) GetActivitiesByForeignID(values ...ForeignIDTimePair) (*GetActivitiesResponse, error) { +func (c *Client) GetActivitiesByForeignID(ctx context.Context, values ...ForeignIDTimePair) (*GetActivitiesResponse, error) { foreignIDs := make([]string, len(values)) timestamps := make([]string, len(values)) for i, v := range values { foreignIDs[i] = v.ForeignID timestamps[i] = v.Timestamp.Format(TimeLayout) } - return c.getAppActivities( + return c.getAppActivities(ctx, makeRequestOption("foreign_ids", strings.Join(foreignIDs, ",")), makeRequestOption("timestamps", strings.Join(timestamps, ",")), ) } -func (c *Client) getAppActivities(values ...valuer) (*GetActivitiesResponse, error) { +func (c *Client) getAppActivities(ctx context.Context, values ...valuer) (*GetActivitiesResponse, error) { endpoint := c.makeEndpoint("activities/") for _, v := range values { endpoint.addQueryParam(v) } - data, err := c.get(endpoint, nil, c.authenticator.feedAuth(resActivities, nil)) + data, err := c.get(ctx, endpoint, nil, c.authenticator.feedAuth(resActivities, nil)) if err != nil { return nil, err } @@ -265,13 +266,13 @@ func (c *Client) getAppActivities(values ...valuer) (*GetActivitiesResponse, err } // GetEnrichedActivitiesByID returns enriched activities for the current app having the given IDs. -func (c *Client) GetEnrichedActivitiesByID(ids []string, opts ...GetActivitiesOption) (*GetEnrichedActivitiesResponse, error) { +func (c *Client) GetEnrichedActivitiesByID(ctx context.Context, ids []string, opts ...GetActivitiesOption) (*GetEnrichedActivitiesResponse, error) { options := []GetActivitiesOption{{makeRequestOption("ids", strings.Join(ids, ","))}} - return c.getAppEnrichedActivities(append(options, opts...)...) + return c.getAppEnrichedActivities(ctx, append(options, opts...)...) } // GetEnrichedActivitiesByForeignID returns enriched activities for the current app having the given foreign IDs and timestamps. -func (c *Client) GetEnrichedActivitiesByForeignID(values []ForeignIDTimePair, opts ...GetActivitiesOption) (*GetEnrichedActivitiesResponse, error) { +func (c *Client) GetEnrichedActivitiesByForeignID(ctx context.Context, values []ForeignIDTimePair, opts ...GetActivitiesOption) (*GetEnrichedActivitiesResponse, error) { foreignIDs := make([]string, len(values)) timestamps := make([]string, len(values)) for i, v := range values { @@ -283,15 +284,15 @@ func (c *Client) GetEnrichedActivitiesByForeignID(values []ForeignIDTimePair, op {makeRequestOption("timestamps", strings.Join(timestamps, ","))}, } - return c.getAppEnrichedActivities(append(options, opts...)...) + return c.getAppEnrichedActivities(ctx, append(options, opts...)...) } -func (c *Client) getAppEnrichedActivities(options ...GetActivitiesOption) (*GetEnrichedActivitiesResponse, error) { +func (c *Client) getAppEnrichedActivities(ctx context.Context, options ...GetActivitiesOption) (*GetEnrichedActivitiesResponse, error) { endpoint := c.makeEndpoint("enrich/activities/") for _, v := range options { endpoint.addQueryParam(v.requestOption) } - data, err := c.get(endpoint, nil, c.authenticator.feedAuth(resActivities, nil)) + data, err := c.get(ctx, endpoint, nil, c.authenticator.feedAuth(resActivities, nil)) if err != nil { return nil, err } @@ -304,26 +305,26 @@ func (c *Client) getAppEnrichedActivities(options ...GetActivitiesOption) (*GetE } // UpdateActivities updates existing activities. -func (c *Client) UpdateActivities(activities ...Activity) (*BaseResponse, error) { +func (c *Client) UpdateActivities(ctx context.Context, activities ...Activity) (*BaseResponse, error) { req := struct { Activities []Activity `json:"activities,omitempty"` }{ Activities: activities, } endpoint := c.makeEndpoint("activities/") - return decode(c.post(endpoint, req, c.authenticator.feedAuth(resActivities, nil))) + return decode(c.post(ctx, endpoint, req, c.authenticator.feedAuth(resActivities, nil))) } // PartialUpdateActivities performs a partial update on multiple activities with the given set and unset operations // specified by each changeset. This returns the affected activities. -func (c *Client) PartialUpdateActivities(changesets ...UpdateActivityRequest) (*UpdateActivitiesResponse, error) { +func (c *Client) PartialUpdateActivities(ctx context.Context, changesets ...UpdateActivityRequest) (*UpdateActivitiesResponse, error) { req := struct { Activities []UpdateActivityRequest `json:"changes,omitempty"` }{ Activities: changesets, } endpoint := c.makeEndpoint("activity/") - data, err := c.post(endpoint, req, c.authenticator.feedAuth(resActivities, nil)) + data, err := c.post(ctx, endpoint, req, c.authenticator.feedAuth(resActivities, nil)) if err != nil { return nil, err } @@ -336,8 +337,8 @@ func (c *Client) PartialUpdateActivities(changesets ...UpdateActivityRequest) (* // UpdateActivityByID performs a partial activity update with the given set and unset operations, returning the // affected activity, on the activity with the given ID. -func (c *Client) UpdateActivityByID(id string, set map[string]interface{}, unset []string) (*UpdateActivityResponse, error) { - return c.updateActivity(UpdateActivityRequest{ +func (c *Client) UpdateActivityByID(ctx context.Context, id string, set map[string]interface{}, unset []string) (*UpdateActivityResponse, error) { + return c.updateActivity(ctx, UpdateActivityRequest{ ID: &id, Set: set, Unset: unset, @@ -346,8 +347,8 @@ func (c *Client) UpdateActivityByID(id string, set map[string]interface{}, unset // UpdateActivityByForeignID performs a partial activity update with the given set and unset operations, returning the // affected activity, on the activity with the given foreign ID and timestamp. -func (c *Client) UpdateActivityByForeignID(foreignID string, timestamp Time, set map[string]interface{}, unset []string) (*UpdateActivityResponse, error) { - return c.updateActivity(UpdateActivityRequest{ +func (c *Client) UpdateActivityByForeignID(ctx context.Context, foreignID string, timestamp Time, set map[string]interface{}, unset []string) (*UpdateActivityResponse, error) { + return c.updateActivity(ctx, UpdateActivityRequest{ ForeignID: &foreignID, Time: ×tamp, Set: set, @@ -355,9 +356,9 @@ func (c *Client) UpdateActivityByForeignID(foreignID string, timestamp Time, set }) } -func (c *Client) updateActivity(req UpdateActivityRequest) (*UpdateActivityResponse, error) { +func (c *Client) updateActivity(ctx context.Context, req UpdateActivityRequest) (*UpdateActivityResponse, error) { endpoint := c.makeEndpoint("activity/") - data, err := c.post(endpoint, req, c.authenticator.feedAuth(resActivities, nil)) + data, err := c.post(ctx, endpoint, req, c.authenticator.feedAuth(resActivities, nil)) if err != nil { return nil, err } @@ -417,20 +418,20 @@ func (c *Client) makeEndpoint(format string, a ...interface{}) endpoint { } } -func (c *Client) get(endpoint endpoint, data interface{}, authFn authFunc) ([]byte, error) { - return c.request(http.MethodGet, endpoint, data, authFn) +func (c *Client) get(ctx context.Context, endpoint endpoint, data interface{}, authFn authFunc) ([]byte, error) { + return c.request(ctx, http.MethodGet, endpoint, data, authFn) } -func (c *Client) post(endpoint endpoint, data interface{}, authFn authFunc) ([]byte, error) { - return c.request(http.MethodPost, endpoint, data, authFn) +func (c *Client) post(ctx context.Context, endpoint endpoint, data interface{}, authFn authFunc) ([]byte, error) { + return c.request(ctx, http.MethodPost, endpoint, data, authFn) } -func (c *Client) put(endpoint endpoint, data interface{}, authFn authFunc) ([]byte, error) { - return c.request(http.MethodPut, endpoint, data, authFn) +func (c *Client) put(ctx context.Context, endpoint endpoint, data interface{}, authFn authFunc) ([]byte, error) { + return c.request(ctx, http.MethodPut, endpoint, data, authFn) } -func (c *Client) delete(endpoint endpoint, data interface{}, authFn authFunc) ([]byte, error) { - return c.request(http.MethodDelete, endpoint, data, authFn) +func (c *Client) delete(ctx context.Context, endpoint endpoint, data interface{}, authFn authFunc) ([]byte, error) { + return c.request(ctx, http.MethodDelete, endpoint, data, authFn) } func (c *Client) setBaseHeaders(r *http.Request) { @@ -438,7 +439,7 @@ func (c *Client) setBaseHeaders(r *http.Request) { r.Header.Set("X-Stream-Client", fmt.Sprintf("stream-go2-client-%s", Version)) } -func (c *Client) request(method string, endpoint endpoint, data interface{}, authFn authFunc) ([]byte, error) { +func (c *Client) request(ctx context.Context, method string, endpoint endpoint, data interface{}, authFn authFunc) ([]byte, error) { var reader io.Reader if data != nil { payload, err := json.Marshal(data) @@ -448,7 +449,7 @@ func (c *Client) request(method string, endpoint endpoint, data interface{}, aut reader = bytes.NewReader(payload) } - req, err := http.NewRequest(method, endpoint.String(), reader) + req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), reader) if err != nil { return nil, fmt.Errorf("cannot create request: %w", err) } @@ -491,9 +492,9 @@ func (c *Client) request(method string, endpoint endpoint, data interface{}, aut return body, nil } -func (c *Client) addActivity(feed Feed, activity Activity) (*AddActivityResponse, error) { +func (c *Client) addActivity(ctx context.Context, feed Feed, activity Activity) (*AddActivityResponse, error) { endpoint := c.makeEndpoint("feed/%s/%s/", feed.Slug(), feed.UserID()) - resp, err := c.post(endpoint, activity, c.authenticator.feedAuth(resFeed, feed)) + resp, err := c.post(ctx, endpoint, activity, c.authenticator.feedAuth(resFeed, feed)) if err != nil { return nil, err } @@ -504,14 +505,14 @@ func (c *Client) addActivity(feed Feed, activity Activity) (*AddActivityResponse return &out, nil } -func (c *Client) addActivities(feed Feed, activities ...Activity) (*AddActivitiesResponse, error) { +func (c *Client) addActivities(ctx context.Context, feed Feed, activities ...Activity) (*AddActivitiesResponse, error) { reqBody := struct { Activities []Activity `json:"activities,omitempty"` }{ Activities: activities, } endpoint := c.makeEndpoint("feed/%s/%s/", feed.Slug(), feed.UserID()) - resp, err := c.post(endpoint, reqBody, c.authenticator.feedAuth(resFeed, feed)) + resp, err := c.post(ctx, endpoint, reqBody, c.authenticator.feedAuth(resFeed, feed)) if err != nil { return nil, err } @@ -522,9 +523,9 @@ func (c *Client) addActivities(feed Feed, activities ...Activity) (*AddActivitie return &out, nil } -func (c *Client) removeActivityByID(feed Feed, activityID string) (*RemoveActivityResponse, error) { +func (c *Client) removeActivityByID(ctx context.Context, feed Feed, activityID string) (*RemoveActivityResponse, error) { endpoint := c.makeEndpoint("feed/%s/%s/%s/", feed.Slug(), feed.UserID(), activityID) - resp, err := c.delete(endpoint, nil, c.authenticator.feedAuth(resFeed, feed)) + resp, err := c.delete(ctx, endpoint, nil, c.authenticator.feedAuth(resFeed, feed)) if err != nil { return nil, err } @@ -535,10 +536,10 @@ func (c *Client) removeActivityByID(feed Feed, activityID string) (*RemoveActivi return &out, nil } -func (c *Client) removeActivityByForeignID(feed Feed, foreignID string) (*RemoveActivityResponse, error) { +func (c *Client) removeActivityByForeignID(ctx context.Context, feed Feed, foreignID string) (*RemoveActivityResponse, error) { endpoint := c.makeEndpoint("feed/%s/%s/%s/", feed.Slug(), feed.UserID(), foreignID) endpoint.addQueryParam(makeRequestOption("foreign_id", 1)) - resp, err := c.delete(endpoint, nil, c.authenticator.feedAuth(resFeed, feed)) + resp, err := c.delete(ctx, endpoint, nil, c.authenticator.feedAuth(resFeed, feed)) if err != nil { return nil, err } @@ -549,35 +550,35 @@ func (c *Client) removeActivityByForeignID(feed Feed, foreignID string) (*Remove return &out, nil } -func (c *Client) getActivities(feed Feed, opts ...GetActivitiesOption) ([]byte, error) { +func (c *Client) getActivities(ctx context.Context, feed Feed, opts ...GetActivitiesOption) ([]byte, error) { endpoint := c.makeEndpoint("feed/%s/%s/", feed.Slug(), feed.UserID()) - return c.getActivitiesInternal(endpoint, feed, opts...) + return c.getActivitiesInternal(ctx, endpoint, feed, opts...) } -func (c *Client) getEnrichedActivities(feed Feed, opts ...GetActivitiesOption) ([]byte, error) { +func (c *Client) getEnrichedActivities(ctx context.Context, feed Feed, opts ...GetActivitiesOption) ([]byte, error) { endpoint := c.makeEndpoint("enrich/feed/%s/%s/", feed.Slug(), feed.UserID()) - return c.getActivitiesInternal(endpoint, feed, opts...) + return c.getActivitiesInternal(ctx, endpoint, feed, opts...) } -func (c *Client) getActivitiesInternal(endpoint endpoint, feed Feed, opts ...GetActivitiesOption) ([]byte, error) { +func (c *Client) getActivitiesInternal(ctx context.Context, endpoint endpoint, feed Feed, opts ...GetActivitiesOption) ([]byte, error) { for _, opt := range opts { endpoint.addQueryParam(opt) } - return c.get(endpoint, nil, c.authenticator.feedAuth(resFeed, feed)) + return c.get(ctx, endpoint, nil, c.authenticator.feedAuth(resFeed, feed)) } -func (c *Client) follow(feed Feed, opts *followFeedOptions) (*BaseResponse, error) { +func (c *Client) follow(ctx context.Context, feed Feed, opts *followFeedOptions) (*BaseResponse, error) { endpoint := c.makeEndpoint("feed/%s/%s/follows/", feed.Slug(), feed.UserID()) - return decode(c.post(endpoint, opts, c.authenticator.feedAuth(resFollower, feed))) + return decode(c.post(ctx, endpoint, opts, c.authenticator.feedAuth(resFollower, feed))) } -func (c *Client) getFollowers(feed Feed, opts ...FollowersOption) (*FollowersResponse, error) { +func (c *Client) getFollowers(ctx context.Context, feed Feed, opts ...FollowersOption) (*FollowersResponse, error) { endpoint := c.makeEndpoint("feed/%s/%s/followers/", feed.Slug(), feed.UserID()) for _, opt := range opts { endpoint.addQueryParam(opt) } - resp, err := c.get(endpoint, nil, c.authenticator.feedAuth(resFollower, feed)) + resp, err := c.get(ctx, endpoint, nil, c.authenticator.feedAuth(resFollower, feed)) if err != nil { return nil, err } @@ -588,13 +589,13 @@ func (c *Client) getFollowers(feed Feed, opts ...FollowersOption) (*FollowersRes return &out, nil } -func (c *Client) getFollowing(feed Feed, opts ...FollowingOption) (*FollowingResponse, error) { +func (c *Client) getFollowing(ctx context.Context, feed Feed, opts ...FollowingOption) (*FollowingResponse, error) { endpoint := c.makeEndpoint("feed/%s/%s/follows/", feed.Slug(), feed.UserID()) for _, opt := range opts { endpoint.addQueryParam(opt) } - resp, err := c.get(endpoint, nil, c.authenticator.feedAuth(resFollower, feed)) + resp, err := c.get(ctx, endpoint, nil, c.authenticator.feedAuth(resFollower, feed)) if err != nil { return nil, err } @@ -605,16 +606,16 @@ func (c *Client) getFollowing(feed Feed, opts ...FollowingOption) (*FollowingRes return &out, nil } -func (c *Client) unfollow(feed Feed, target string, opts ...UnfollowOption) (*BaseResponse, error) { +func (c *Client) unfollow(ctx context.Context, feed Feed, target string, opts ...UnfollowOption) (*BaseResponse, error) { endpoint := c.makeEndpoint("feed/%s/%s/follows/%s/", feed.Slug(), feed.UserID(), target) for _, opt := range opts { endpoint.addQueryParam(opt) } - return decode(c.delete(endpoint, nil, c.authenticator.feedAuth(resFollower, feed))) + return decode(c.delete(ctx, endpoint, nil, c.authenticator.feedAuth(resFollower, feed))) } -func (c *Client) followStats(feed Feed, opts ...FollowStatOption) (*FollowStatResponse, error) { +func (c *Client) followStats(ctx context.Context, feed Feed, opts ...FollowStatOption) (*FollowStatResponse, error) { endpoint := c.makeEndpoint("stats/follow/") endpoint.addQueryParam(makeRequestOption("followers", feed.ID())) endpoint.addQueryParam(makeRequestOption("following", feed.ID())) @@ -622,7 +623,7 @@ func (c *Client) followStats(feed Feed, opts ...FollowStatOption) (*FollowStatRe endpoint.addQueryParam(opt) } - resp, err := c.get(endpoint, nil, c.authenticator.feedAuth(resFollower, nil)) + resp, err := c.get(ctx, endpoint, nil, c.authenticator.feedAuth(resFollower, nil)) if err != nil { return nil, err } @@ -633,7 +634,7 @@ func (c *Client) followStats(feed Feed, opts ...FollowStatOption) (*FollowStatRe return &out, nil } -func (c *Client) updateToTargets(feed Feed, activity Activity, opts ...UpdateToTargetsOption) (*UpdateToTargetsResponse, error) { +func (c *Client) updateToTargets(ctx context.Context, feed Feed, activity Activity, opts ...UpdateToTargetsOption) (*UpdateToTargetsResponse, error) { endpoint := c.makeEndpoint("feed_targets/%s/%s/activity_to_targets/", feed.Slug(), feed.UserID()) req := &updateToTargetsRequest{ @@ -644,7 +645,7 @@ func (c *Client) updateToTargets(feed Feed, activity Activity, opts ...UpdateToT opt(req) } - resp, err := c.post(endpoint, req, c.authenticator.feedAuth(resFeedTargets, feed)) + resp, err := c.post(ctx, endpoint, req, c.authenticator.feedAuth(resFeedTargets, feed)) if err != nil { return nil, err } diff --git a/client_internal_test.go b/client_internal_test.go index 54f7dc2..5cccb86 100644 --- a/client_internal_test.go +++ b/client_internal_test.go @@ -1,6 +1,7 @@ package stream import ( + "context" "fmt" "io" "net/http" @@ -193,6 +194,7 @@ func (r requester) Do(*http.Request) (*http.Response, error) { } func Test_requestErrors(t *testing.T) { + ctx := context.Background() testCases := []struct { data interface{} method string @@ -236,7 +238,7 @@ func Test_requestErrors(t *testing.T) { for _, tc := range testCases { c := &Client{requester: tc.requester} - _, err := c.request(tc.method, endpoint{url: &url.URL{}, query: url.Values{}}, tc.data, tc.authFn) + _, err := c.request(ctx, tc.method, endpoint{url: &url.URL{}, query: url.Values{}}, tc.data, tc.authFn) require.Error(t, err) assert.Equal(t, tc.expected.Error(), err.Error()) } diff --git a/client_test.go b/client_test.go index 68c7da1..3e34028 100644 --- a/client_test.go +++ b/client_test.go @@ -1,6 +1,7 @@ package stream_test import ( + "context" "net/http" "strconv" "testing" @@ -14,11 +15,12 @@ import ( ) func TestHeaders(t *testing.T) { + ctx := context.Background() client, requester := newClient(t) feed, err := client.FlatFeed("user", "123") require.NoError(t, err) - _, err = feed.GetActivities() + _, err = feed.GetActivities(ctx) require.NoError(t, err) assert.Equal(t, "application/json", requester.req.Header.Get("content-type")) assert.Regexp(t, "^stream-go2-client-v[0-9\\.]+$", requester.req.Header.Get("x-stream-client")) @@ -27,12 +29,13 @@ func TestHeaders(t *testing.T) { func TestAddToMany(t *testing.T) { var ( client, requester = newClient(t) + ctx = context.Background() activity = stream.Activity{Actor: "bob", Verb: "like", Object: "cake"} flat, _ = newFlatFeedWithUserID(client, "123") aggregated, _ = newAggregatedFeedWithUserID(client, "123") ) - err := client.AddToMany(activity, flat, aggregated) + err := client.AddToMany(ctx, activity, flat, aggregated) require.NoError(t, err) body := `{"activity":{"actor":"bob","object":"cake","verb":"like"},"feeds":["flat:123","aggregated:123"]}` testRequest(t, requester.req, http.MethodPost, "https://api.stream-io-api.com/api/v1.0/feed/add_to_many/?api_key=key", body) @@ -41,6 +44,7 @@ func TestAddToMany(t *testing.T) { func TestFollowMany(t *testing.T) { var ( client, requester = newClient(t) + ctx = context.Background() relationships = make([]stream.FollowRelationship, 3) flat, _ = newFlatFeedWithUserID(client, "123") ) @@ -50,12 +54,12 @@ func TestFollowMany(t *testing.T) { relationships[i] = stream.NewFollowRelationship(other, flat) } - err := client.FollowMany(relationships) + err := client.FollowMany(ctx, relationships) require.NoError(t, err) body := `[{"source":"aggregated:0","target":"flat:123"},{"source":"aggregated:1","target":"flat:123"},{"source":"aggregated:2","target":"flat:123"}]` testRequest(t, requester.req, http.MethodPost, "https://api.stream-io-api.com/api/v1.0/follow_many/?api_key=key", body) - err = client.FollowMany(relationships, stream.WithFollowManyActivityCopyLimit(500)) + err = client.FollowMany(ctx, relationships, stream.WithFollowManyActivityCopyLimit(500)) require.NoError(t, err) body = `[{"source":"aggregated:0","target":"flat:123"},{"source":"aggregated:1","target":"flat:123"},{"source":"aggregated:2","target":"flat:123"}]` testRequest(t, requester.req, http.MethodPost, "https://api.stream-io-api.com/api/v1.0/follow_many/?activity_copy_limit=500&api_key=key", body) @@ -64,6 +68,7 @@ func TestFollowMany(t *testing.T) { func TestFollowManyActivityCopyLimit(t *testing.T) { var ( client, requester = newClient(t) + ctx = context.Background() relationships = make([]stream.FollowRelationship, 3) flat, _ = newFlatFeedWithUserID(client, "123") ) @@ -73,12 +78,12 @@ func TestFollowManyActivityCopyLimit(t *testing.T) { relationships[i] = stream.NewFollowRelationship(other, flat, stream.WithFollowRelationshipActivityCopyLimit(i)) } - err := client.FollowMany(relationships) + err := client.FollowMany(ctx, relationships) require.NoError(t, err) body := `[{"source":"aggregated:0","target":"flat:123","activity_copy_limit":0},{"source":"aggregated:1","target":"flat:123","activity_copy_limit":1},{"source":"aggregated:2","target":"flat:123","activity_copy_limit":2}]` testRequest(t, requester.req, http.MethodPost, "https://api.stream-io-api.com/api/v1.0/follow_many/?api_key=key", body) - err = client.FollowMany(relationships, stream.WithFollowManyActivityCopyLimit(123)) + err = client.FollowMany(ctx, relationships, stream.WithFollowManyActivityCopyLimit(123)) require.NoError(t, err) body = `[{"source":"aggregated:0","target":"flat:123","activity_copy_limit":0},{"source":"aggregated:1","target":"flat:123","activity_copy_limit":1},{"source":"aggregated:2","target":"flat:123","activity_copy_limit":2}]` testRequest(t, requester.req, http.MethodPost, "https://api.stream-io-api.com/api/v1.0/follow_many/?activity_copy_limit=123&api_key=key", body) @@ -87,6 +92,7 @@ func TestFollowManyActivityCopyLimit(t *testing.T) { func TestUnfollowMany(t *testing.T) { var ( client, requester = newClient(t) + ctx = context.Background() relationships = make([]stream.UnfollowRelationship, 3) ) for i := range relationships { @@ -102,18 +108,20 @@ func TestUnfollowMany(t *testing.T) { relationships[i] = stream.NewUnfollowRelationship(src, tgt, options...) } - err := client.UnfollowMany(relationships) + err := client.UnfollowMany(ctx, relationships) require.NoError(t, err) body := `[{"source":"src:0","target":"tgt:0","keep_history":true},{"source":"src:1","target":"tgt:1","keep_history":false},{"source":"src:2","target":"tgt:2","keep_history":true}]` testRequest(t, requester.req, http.MethodPost, "https://api.stream-io-api.com/api/v1.0/unfollow_many/?api_key=key", body) } func TestGetActivities(t *testing.T) { + ctx := context.Background() client, requester := newClient(t) - _, err := client.GetActivitiesByID("foo", "bar", "baz") + _, err := client.GetActivitiesByID(ctx, "foo", "bar", "baz") require.NoError(t, err) testRequest(t, requester.req, http.MethodGet, "https://api.stream-io-api.com/api/v1.0/activities/?api_key=key&ids=foo%2Cbar%2Cbaz", "") _, err = client.GetActivitiesByForeignID( + ctx, stream.NewForeignIDTimePair("foo", stream.Time{}), stream.NewForeignIDTimePair("bar", stream.Time{Time: time.Time{}.Add(time.Second)}), ) @@ -122,11 +130,13 @@ func TestGetActivities(t *testing.T) { } func TestGetEnrichedActivities(t *testing.T) { + ctx := context.Background() client, requester := newClient(t) - _, err := client.GetEnrichedActivitiesByID([]string{"foo", "bar", "baz"}, stream.WithEnrichReactionCounts()) + _, err := client.GetEnrichedActivitiesByID(ctx, []string{"foo", "bar", "baz"}, stream.WithEnrichReactionCounts()) require.NoError(t, err) testRequest(t, requester.req, http.MethodGet, "https://api.stream-io-api.com/api/v1.0/enrich/activities/?api_key=key&ids=foo%2Cbar%2Cbaz&withReactionCounts=true", "") _, err = client.GetEnrichedActivitiesByForeignID( + ctx, []stream.ForeignIDTimePair{ stream.NewForeignIDTimePair("foo", stream.Time{}), stream.NewForeignIDTimePair("bar", stream.Time{Time: time.Time{}.Add(time.Second)}), @@ -138,28 +148,31 @@ func TestGetEnrichedActivities(t *testing.T) { } func TestUpdateActivityByID(t *testing.T) { + ctx := context.Background() client, requester := newClient(t) - _, err := client.UpdateActivityByID("abcdef", map[string]interface{}{"foo.bar": "baz", "popularity": 42, "color": map[string]interface{}{"hex": "FF0000", "rgb": "255,0,0"}}, []string{"a", "b", "c"}) + _, err := client.UpdateActivityByID(ctx, "abcdef", map[string]interface{}{"foo.bar": "baz", "popularity": 42, "color": map[string]interface{}{"hex": "FF0000", "rgb": "255,0,0"}}, []string{"a", "b", "c"}) require.NoError(t, err) body := `{"id":"abcdef","set":{"color":{"hex":"FF0000","rgb":"255,0,0"},"foo.bar":"baz","popularity":42},"unset":["a","b","c"]}` testRequest(t, requester.req, http.MethodPost, "https://api.stream-io-api.com/api/v1.0/activity/?api_key=key", body) - _, err = client.UpdateActivityByID("abcdef", map[string]interface{}{"foo.bar": "baz", "popularity": 42, "color": map[string]interface{}{"hex": "FF0000", "rgb": "255,0,0"}}, nil) + _, err = client.UpdateActivityByID(ctx, "abcdef", map[string]interface{}{"foo.bar": "baz", "popularity": 42, "color": map[string]interface{}{"hex": "FF0000", "rgb": "255,0,0"}}, nil) require.NoError(t, err) body = `{"id":"abcdef","set":{"color":{"hex":"FF0000","rgb":"255,0,0"},"foo.bar":"baz","popularity":42}}` testRequest(t, requester.req, http.MethodPost, "https://api.stream-io-api.com/api/v1.0/activity/?api_key=key", body) - _, err = client.UpdateActivityByID("abcdef", nil, []string{"a", "b", "c"}) + _, err = client.UpdateActivityByID(ctx, "abcdef", nil, []string{"a", "b", "c"}) require.NoError(t, err) body = `{"id":"abcdef","unset":["a","b","c"]}` testRequest(t, requester.req, http.MethodPost, "https://api.stream-io-api.com/api/v1.0/activity/?api_key=key", body) } func TestPartialUpdateActivities(t *testing.T) { + ctx := context.Background() client, requester := newClient(t) _, err := client.PartialUpdateActivities( + ctx, stream.NewUpdateActivityRequestByID( "abcdef", map[string]interface{}{"foo.bar": "baz"}, @@ -177,6 +190,7 @@ func TestPartialUpdateActivities(t *testing.T) { tt, _ := time.Parse(stream.TimeLayout, "2006-01-02T15:04:05.999999") _, err = client.PartialUpdateActivities( + ctx, stream.NewUpdateActivityRequestByForeignID( "abcdef:123", stream.Time{Time: tt}, @@ -196,21 +210,22 @@ func TestPartialUpdateActivities(t *testing.T) { } func TestUpdateActivityByForeignID(t *testing.T) { + ctx := context.Background() client, requester := newClient(t) tt := stream.Time{Time: time.Date(2018, 6, 24, 11, 28, 0, 0, time.UTC)} - _, err := client.UpdateActivityByForeignID("fid:123", tt, map[string]interface{}{"foo.bar": "baz", "popularity": 42, "color": map[string]interface{}{"hex": "FF0000", "rgb": "255,0,0"}}, []string{"a", "b", "c"}) + _, err := client.UpdateActivityByForeignID(ctx, "fid:123", tt, map[string]interface{}{"foo.bar": "baz", "popularity": 42, "color": map[string]interface{}{"hex": "FF0000", "rgb": "255,0,0"}}, []string{"a", "b", "c"}) require.NoError(t, err) body := `{"foreign_id":"fid:123","time":"2018-06-24T11:28:00","set":{"color":{"hex":"FF0000","rgb":"255,0,0"},"foo.bar":"baz","popularity":42},"unset":["a","b","c"]}` testRequest(t, requester.req, http.MethodPost, "https://api.stream-io-api.com/api/v1.0/activity/?api_key=key", body) - _, err = client.UpdateActivityByForeignID("fid:123", tt, map[string]interface{}{"foo.bar": "baz", "popularity": 42, "color": map[string]interface{}{"hex": "FF0000", "rgb": "255,0,0"}}, nil) + _, err = client.UpdateActivityByForeignID(ctx, "fid:123", tt, map[string]interface{}{"foo.bar": "baz", "popularity": 42, "color": map[string]interface{}{"hex": "FF0000", "rgb": "255,0,0"}}, nil) require.NoError(t, err) body = `{"foreign_id":"fid:123","time":"2018-06-24T11:28:00","set":{"color":{"hex":"FF0000","rgb":"255,0,0"},"foo.bar":"baz","popularity":42}}` testRequest(t, requester.req, http.MethodPost, "https://api.stream-io-api.com/api/v1.0/activity/?api_key=key", body) - _, err = client.UpdateActivityByForeignID("fid:123", tt, nil, []string{"a", "b", "c"}) + _, err = client.UpdateActivityByForeignID(ctx, "fid:123", tt, nil, []string{"a", "b", "c"}) require.NoError(t, err) body = `{"foreign_id":"fid:123","time":"2018-06-24T11:28:00","unset":["a","b","c"]}` testRequest(t, requester.req, http.MethodPost, "https://api.stream-io-api.com/api/v1.0/activity/?api_key=key", body) diff --git a/collections.go b/collections.go index 4c0c5d5..00c277b 100644 --- a/collections.go +++ b/collections.go @@ -1,6 +1,7 @@ package stream import ( + "context" "encoding/json" "errors" "fmt" @@ -13,7 +14,7 @@ type CollectionsClient struct { } // Upsert creates new or updates existing objects for the given collection's name. -func (c *CollectionsClient) Upsert(collection string, objects ...CollectionObject) (*BaseResponse, error) { +func (c *CollectionsClient) Upsert(ctx context.Context, collection string, objects ...CollectionObject) (*BaseResponse, error) { if collection == "" { return nil, errors.New("collection name required") } @@ -23,12 +24,12 @@ func (c *CollectionsClient) Upsert(collection string, objects ...CollectionObjec collection: objects, }, } - return decode(c.client.post(endpoint, data, c.client.authenticator.collectionsAuth)) + return decode(c.client.post(ctx, endpoint, data, c.client.authenticator.collectionsAuth)) } // Select returns a list of CollectionObjects for the given collection name // having the given IDs. -func (c *CollectionsClient) Select(collection string, ids ...string) (*GetCollectionResponse, error) { +func (c *CollectionsClient) Select(ctx context.Context, collection string, ids ...string) (*GetCollectionResponse, error) { if collection == "" { return nil, errors.New("collection name required") } @@ -38,7 +39,7 @@ func (c *CollectionsClient) Select(collection string, ids ...string) (*GetCollec } endpoint := c.client.makeEndpoint("collections/") endpoint.addQueryParam(makeRequestOption("foreign_ids", strings.Join(foreignIDs, ","))) - resp, err := c.client.get(endpoint, nil, c.client.authenticator.collectionsAuth) + resp, err := c.client.get(ctx, endpoint, nil, c.client.authenticator.collectionsAuth) if err != nil { return nil, err } @@ -53,14 +54,14 @@ func (c *CollectionsClient) Select(collection string, ids ...string) (*GetCollec } // DeleteMany removes from a collection the objects having the given IDs. -func (c *CollectionsClient) DeleteMany(collection string, ids ...string) (*BaseResponse, error) { +func (c *CollectionsClient) DeleteMany(ctx context.Context, collection string, ids ...string) (*BaseResponse, error) { if collection == "" { return nil, errors.New("collection name required") } endpoint := c.client.makeEndpoint("collections/") endpoint.addQueryParam(makeRequestOption("collection_name", collection)) endpoint.addQueryParam(makeRequestOption("ids", strings.Join(ids, ","))) - return decode(c.client.delete(endpoint, nil, c.client.authenticator.collectionsAuth)) + return decode(c.client.delete(ctx, endpoint, nil, c.client.authenticator.collectionsAuth)) } func (c *CollectionsClient) decodeObject(resp []byte, err error) (*CollectionObjectResponse, error) { @@ -75,7 +76,7 @@ func (c *CollectionsClient) decodeObject(resp []byte, err error) (*CollectionObj } // Add adds a single object to a collection. -func (c *CollectionsClient) Add(collection string, object CollectionObject, opts ...AddObjectOption) (*CollectionObjectResponse, error) { +func (c *CollectionsClient) Add(ctx context.Context, collection string, object CollectionObject, opts ...AddObjectOption) (*CollectionObjectResponse, error) { if collection == "" { return nil, errors.New("collection name required") } @@ -90,21 +91,21 @@ func (c *CollectionsClient) Add(collection string, object CollectionObject, opts req.ID = object.ID req.Data = object.Data - return c.decodeObject(c.client.post(endpoint, req, c.client.authenticator.collectionsAuth)) + return c.decodeObject(c.client.post(ctx, endpoint, req, c.client.authenticator.collectionsAuth)) } // Get retrieves a collection object having the given ID. -func (c *CollectionsClient) Get(collection, id string) (*CollectionObjectResponse, error) { +func (c *CollectionsClient) Get(ctx context.Context, collection, id string) (*CollectionObjectResponse, error) { if collection == "" { return nil, errors.New("collection name required") } endpoint := c.client.makeEndpoint("collections/%s/%s/", collection, id) - return c.decodeObject(c.client.get(endpoint, nil, c.client.authenticator.collectionsAuth)) + return c.decodeObject(c.client.get(ctx, endpoint, nil, c.client.authenticator.collectionsAuth)) } // Update updates the given collection object's data. -func (c *CollectionsClient) Update(collection, id string, data map[string]interface{}) (*CollectionObjectResponse, error) { +func (c *CollectionsClient) Update(ctx context.Context, collection, id string, data map[string]interface{}) (*CollectionObjectResponse, error) { if collection == "" { return nil, errors.New("collection name required") } @@ -113,17 +114,17 @@ func (c *CollectionsClient) Update(collection, id string, data map[string]interf "data": data, } - return c.decodeObject(c.client.put(endpoint, reqData, c.client.authenticator.collectionsAuth)) + return c.decodeObject(c.client.put(ctx, endpoint, reqData, c.client.authenticator.collectionsAuth)) } // Delete removes from a collection the object having the given ID. -func (c *CollectionsClient) Delete(collection, id string) (*BaseResponse, error) { +func (c *CollectionsClient) Delete(ctx context.Context, collection, id string) (*BaseResponse, error) { if collection == "" { return nil, errors.New("collection name required") } endpoint := c.client.makeEndpoint("collections/%s/%s/", collection, id) - return decode(c.client.delete(endpoint, nil, c.client.authenticator.collectionsAuth)) + return decode(c.client.delete(ctx, endpoint, nil, c.client.authenticator.collectionsAuth)) } // CreateReference returns a new reference string in the form SO::. diff --git a/collections_test.go b/collections_test.go index 031bb3e..78144c8 100644 --- a/collections_test.go +++ b/collections_test.go @@ -1,6 +1,7 @@ package stream_test import ( + "context" "net/http" "net/url" "testing" @@ -18,6 +19,7 @@ func TestCollectionRefHelpers(t *testing.T) { } func TestUpsertCollectionObjects(t *testing.T) { + ctx := context.Background() client, requester := newClient(t) testCases := []struct { objects []stream.CollectionObject @@ -62,13 +64,14 @@ func TestUpsertCollectionObjects(t *testing.T) { }, } for _, tc := range testCases { - _, err := client.Collections().Upsert(tc.collection, tc.objects...) + _, err := client.Collections().Upsert(ctx, tc.collection, tc.objects...) require.NoError(t, err) testRequest(t, requester.req, http.MethodPost, tc.expectedURL, tc.expectedBody) } } func TestSelectCollectionObjects(t *testing.T) { + ctx := context.Background() client, requester := newClient(t) testCases := []struct { ids []string @@ -88,13 +91,14 @@ func TestSelectCollectionObjects(t *testing.T) { }, } for _, tc := range testCases { - _, err := client.Collections().Select(tc.collection, tc.ids...) + _, err := client.Collections().Select(ctx, tc.collection, tc.ids...) require.NoError(t, err) testRequest(t, requester.req, http.MethodGet, tc.expectedURL, tc.expectedBody) } } func TestDeleteManyCollectionObjects(t *testing.T) { + ctx := context.Background() client, requester := newClient(t) testCases := []struct { ids []string @@ -113,29 +117,32 @@ func TestDeleteManyCollectionObjects(t *testing.T) { }, } for _, tc := range testCases { - _, err := client.Collections().DeleteMany(tc.collection, tc.ids...) + _, err := client.Collections().DeleteMany(ctx, tc.collection, tc.ids...) require.NoError(t, err) testRequest(t, requester.req, http.MethodDelete, tc.expectedURL, "") } } func TestGetCollectionObject(t *testing.T) { + ctx := context.Background() client, requester := newClient(t) - _, err := client.Collections().Get("test-get-one", "id1") + _, err := client.Collections().Get(ctx, "test-get-one", "id1") require.NoError(t, err) testRequest(t, requester.req, http.MethodGet, "https://api.stream-io-api.com/api/v1.0/collections/test-get-one/id1/?api_key=key", "") } func TestDeleteCollectionObject(t *testing.T) { + ctx := context.Background() client, requester := newClient(t) - _, err := client.Collections().Delete("test-get-one", "id1") + _, err := client.Collections().Delete(ctx, "test-get-one", "id1") require.NoError(t, err) testRequest(t, requester.req, http.MethodDelete, "https://api.stream-io-api.com/api/v1.0/collections/test-get-one/id1/?api_key=key", "") } func TestAddCollectionObject(t *testing.T) { + ctx := context.Background() client, requester := newClient(t) testCases := []struct { object stream.CollectionObject @@ -171,19 +178,20 @@ func TestAddCollectionObject(t *testing.T) { }, } for _, tc := range testCases { - _, err := client.Collections().Add(tc.collection, tc.object, tc.opts...) + _, err := client.Collections().Add(ctx, tc.collection, tc.object, tc.opts...) require.NoError(t, err) testRequest(t, requester.req, http.MethodPost, tc.expectedURL, tc.expectedBody) } } func TestUpdateCollectionObject(t *testing.T) { + ctx := context.Background() client, requester := newClient(t) data := map[string]interface{}{ "name": "Jane", } - _, err := client.Collections().Update("test-collection", "123", data) + _, err := client.Collections().Update(ctx, "test-collection", "123", data) require.NoError(t, err) expectedBody := `{"data":{"name":"Jane"}}` testRequest(t, requester.req, http.MethodPut, "https://api.stream-io-api.com/api/v1.0/collections/test-collection/123/?api_key=key", expectedBody) diff --git a/feed.go b/feed.go index 14fe972..df08e68 100644 --- a/feed.go +++ b/feed.go @@ -1,6 +1,7 @@ package stream import ( + "context" "fmt" "regexp" ) @@ -17,14 +18,14 @@ type Feed interface { ID() string Slug() string UserID() string - AddActivity(Activity) (*AddActivityResponse, error) - AddActivities(...Activity) (*AddActivitiesResponse, error) - RemoveActivityByID(string) (*RemoveActivityResponse, error) - RemoveActivityByForeignID(string) (*RemoveActivityResponse, error) - Follow(*FlatFeed, ...FollowFeedOption) (*BaseResponse, error) - GetFollowing(...FollowingOption) (*FollowingResponse, error) - Unfollow(Feed, ...UnfollowOption) (*BaseResponse, error) - UpdateToTargets(Activity, ...UpdateToTargetsOption) (*UpdateToTargetsResponse, error) + AddActivity(context.Context, Activity) (*AddActivityResponse, error) + AddActivities(context.Context, ...Activity) (*AddActivitiesResponse, error) + RemoveActivityByID(context.Context, string) (*RemoveActivityResponse, error) + RemoveActivityByForeignID(context.Context, string) (*RemoveActivityResponse, error) + Follow(context.Context, *FlatFeed, ...FollowFeedOption) (*BaseResponse, error) + GetFollowing(context.Context, ...FollowingOption) (*FollowingResponse, error) + Unfollow(context.Context, Feed, ...UnfollowOption) (*BaseResponse, error) + UpdateToTargets(context.Context, Activity, ...UpdateToTargetsOption) (*UpdateToTargetsResponse, error) RealtimeToken(bool) string } @@ -66,30 +67,30 @@ func newFeed(slug, userID string, client *Client) (*feed, error) { } // AddActivity adds a new Activity to the feed. -func (f *feed) AddActivity(activity Activity) (*AddActivityResponse, error) { - return f.client.addActivity(f, activity) +func (f *feed) AddActivity(ctx context.Context, activity Activity) (*AddActivityResponse, error) { + return f.client.addActivity(ctx, f, activity) } // AddActivities adds multiple activities to the feed. -func (f *feed) AddActivities(activities ...Activity) (*AddActivitiesResponse, error) { - return f.client.addActivities(f, activities...) +func (f *feed) AddActivities(ctx context.Context, activities ...Activity) (*AddActivitiesResponse, error) { + return f.client.addActivities(ctx, f, activities...) } // RemoveActivityByID removes an activity from the feed (if present), using the provided // id string argument as the ID field of the activity. -func (f *feed) RemoveActivityByID(id string) (*RemoveActivityResponse, error) { - return f.client.removeActivityByID(f, id) +func (f *feed) RemoveActivityByID(ctx context.Context, id string) (*RemoveActivityResponse, error) { + return f.client.removeActivityByID(ctx, f, id) } // RemoveActivityByForeignID removes an activity from the feed (if present), using the provided // foreignID string argument as the foreign_id field of the activity. -func (f *feed) RemoveActivityByForeignID(foreignID string) (*RemoveActivityResponse, error) { - return f.client.removeActivityByForeignID(f, foreignID) +func (f *feed) RemoveActivityByForeignID(ctx context.Context, foreignID string) (*RemoveActivityResponse, error) { + return f.client.removeActivityByForeignID(ctx, f, foreignID) } // Follow follows the provided feed (which must be a FlatFeed), applying the provided FollowFeedOptions, // if any. -func (f *feed) Follow(feed *FlatFeed, opts ...FollowFeedOption) (*BaseResponse, error) { +func (f *feed) Follow(ctx context.Context, feed *FlatFeed, opts ...FollowFeedOption) (*BaseResponse, error) { followOptions := &followFeedOptions{ Target: fmt.Sprintf("%s:%s", feed.Slug(), feed.UserID()), ActivityCopyLimit: defaultActivityCopyLimit, @@ -97,24 +98,24 @@ func (f *feed) Follow(feed *FlatFeed, opts ...FollowFeedOption) (*BaseResponse, for _, opt := range opts { opt(followOptions) } - return f.client.follow(f, followOptions) + return f.client.follow(ctx, f, followOptions) } // GetFollowing returns the list of the feeds following the feed, applying the provided FollowingOptions, // if any. -func (f *feed) GetFollowing(opts ...FollowingOption) (*FollowingResponse, error) { - return f.client.getFollowing(f, opts...) +func (f *feed) GetFollowing(ctx context.Context, opts ...FollowingOption) (*FollowingResponse, error) { + return f.client.getFollowing(ctx, f, opts...) } // Unfollow unfollows the provided feed, applying the provided UnfollowOptions, if any. -func (f *feed) Unfollow(target Feed, opts ...UnfollowOption) (*BaseResponse, error) { - return f.client.unfollow(f, target.ID(), opts...) +func (f *feed) Unfollow(ctx context.Context, target Feed, opts ...UnfollowOption) (*BaseResponse, error) { + return f.client.unfollow(ctx, f, target.ID(), opts...) } // UpdateToTargets updates the "to" targets for the provided activity, with the options passed // as argument for replacing, adding, or removing to targets. -func (f *feed) UpdateToTargets(activity Activity, opts ...UpdateToTargetsOption) (*UpdateToTargetsResponse, error) { - return f.client.updateToTargets(f, activity, opts...) +func (f *feed) UpdateToTargets(ctx context.Context, activity Activity, opts ...UpdateToTargetsOption) (*UpdateToTargetsResponse, error) { + return f.client.updateToTargets(ctx, f, activity, opts...) } // RealtimeToken returns a token that can be used client-side to listen in real-time to feed changes. diff --git a/feed_test.go b/feed_test.go index f5b85b9..c4daead 100644 --- a/feed_test.go +++ b/feed_test.go @@ -1,6 +1,7 @@ package stream_test import ( + "context" "fmt" "net/http" "testing" @@ -35,27 +36,29 @@ func TestInvalidFeedUserID(t *testing.T) { func TestAddActivity(t *testing.T) { var ( client, requester = newClient(t) + ctx = context.Background() flat, _ = newFlatFeedWithUserID(client, "123") bobActivity = stream.Activity{Actor: "bob", Verb: "like", Object: "ice-cream", To: []string{"flat:456"}} ) - _, err := flat.AddActivity(bobActivity) + _, err := flat.AddActivity(ctx, bobActivity) require.NoError(t, err) body := `{"actor":"bob","object":"ice-cream","to":["flat:456"],"verb":"like"}` testRequest(t, requester.req, http.MethodPost, "https://api.stream-io-api.com/api/v1.0/feed/flat/123/?api_key=key", body) requester.resp = `{"duration": "1ms"}` - _, err = flat.AddActivity(bobActivity) + _, err = flat.AddActivity(ctx, bobActivity) require.NoError(t, err) } func TestAddActivities(t *testing.T) { var ( client, requester = newClient(t) + ctx = context.Background() flat, _ = newFlatFeedWithUserID(client, "123") bobActivity = stream.Activity{Actor: "bob", Verb: "like", Object: "ice-cream"} aliceActivity = stream.Activity{Actor: "alice", Verb: "dislike", Object: "ice-cream", To: []string{"flat:456"}} ) - _, err := flat.AddActivities(bobActivity, aliceActivity) + _, err := flat.AddActivities(ctx, bobActivity, aliceActivity) require.NoError(t, err) body := `{"activities":[{"actor":"bob","object":"ice-cream","verb":"like"},{"actor":"alice","object":"ice-cream","to":["flat:456"],"verb":"dislike"}]}` testRequest(t, requester.req, http.MethodPost, "https://api.stream-io-api.com/api/v1.0/feed/flat/123/?api_key=key", body) @@ -64,6 +67,7 @@ func TestAddActivities(t *testing.T) { func TestUpdateActivities(t *testing.T) { var ( client, requester = newClient(t) + ctx = context.Background() now = getTime(time.Now()) bobActivity = stream.Activity{ Actor: "bob", @@ -74,7 +78,7 @@ func TestUpdateActivities(t *testing.T) { Extra: map[string]interface{}{"influence": 42}, } ) - _, err := client.UpdateActivities(bobActivity) + _, err := client.UpdateActivities(ctx, bobActivity) require.NoError(t, err) body := fmt.Sprintf(`{"activities":[{"actor":"bob","foreign_id":"bob:123","influence":42,"object":"ice-cream","time":%q,"verb":"like"}]}`, now.Format(stream.TimeLayout)) @@ -84,6 +88,7 @@ func TestUpdateActivities(t *testing.T) { func TestFollow(t *testing.T) { var ( client, requester = newClient(t) + ctx = context.Background() f1, _ = newFlatFeedWithUserID(client, "f1") f2, _ = newFlatFeedWithUserID(client, "f2") ) @@ -103,7 +108,7 @@ func TestFollow(t *testing.T) { }, } for _, tc := range testCases { - _, err := f1.Follow(f2, tc.opts...) + _, err := f1.Follow(ctx, f2, tc.opts...) require.NoError(t, err) testRequest(t, requester.req, http.MethodPost, tc.expectedURL, tc.expectedBody) } @@ -112,6 +117,7 @@ func TestFollow(t *testing.T) { func TestGetFollowing(t *testing.T) { var ( client, requester = newClient(t) + ctx = context.Background() f1, _ = newFlatFeedWithUserID(client, "f1") ) testCases := []struct { @@ -127,7 +133,7 @@ func TestGetFollowing(t *testing.T) { }, } for _, tc := range testCases { - _, err := f1.GetFollowing(tc.opts...) + _, err := f1.GetFollowing(ctx, tc.opts...) require.NoError(t, err) testRequest(t, requester.req, http.MethodGet, tc.expected, "") } @@ -136,6 +142,7 @@ func TestGetFollowing(t *testing.T) { func TestGetFollowers(t *testing.T) { var ( client, requester = newClient(t) + ctx = context.Background() f1, _ = newFlatFeedWithUserID(client, "f1") ) testCases := []struct { @@ -151,7 +158,7 @@ func TestGetFollowers(t *testing.T) { }, } for _, tc := range testCases { - _, err := f1.GetFollowers(tc.opts...) + _, err := f1.GetFollowers(ctx, tc.opts...) require.NoError(t, err) testRequest(t, requester.req, http.MethodGet, tc.expected, "") } @@ -160,6 +167,7 @@ func TestGetFollowers(t *testing.T) { func TestUnfollow(t *testing.T) { var ( client, requester = newClient(t) + ctx = context.Background() f1, _ = newFlatFeedWithUserID(client, "f1") f2, _ = newFlatFeedWithUserID(client, "f2") ) @@ -181,19 +189,20 @@ func TestUnfollow(t *testing.T) { } for _, tc := range testCases { - _, err := f1.Unfollow(f2, tc.opts...) + _, err := f1.Unfollow(ctx, f2, tc.opts...) require.NoError(t, err) testRequest(t, requester.req, http.MethodDelete, tc.expected, "") } } func TestRemoveActivities(t *testing.T) { + ctx := context.Background() client, requester := newClient(t) flat, _ := newFlatFeedWithUserID(client, "123") - _, err := flat.RemoveActivityByID("id-to-remove") + _, err := flat.RemoveActivityByID(ctx, "id-to-remove") require.NoError(t, err) testRequest(t, requester.req, http.MethodDelete, "https://api.stream-io-api.com/api/v1.0/feed/flat/123/id-to-remove/?api_key=key", "") - _, err = flat.RemoveActivityByForeignID("bob:123") + _, err = flat.RemoveActivityByForeignID(ctx, "bob:123") require.NoError(t, err) testRequest(t, requester.req, http.MethodDelete, "https://api.stream-io-api.com/api/v1.0/feed/flat/123/bob:123/?api_key=key&foreign_id=1", "") } @@ -201,6 +210,7 @@ func TestRemoveActivities(t *testing.T) { func TestUpdateToTargets(t *testing.T) { var ( client, requester = newClient(t) + ctx = context.Background() flat, _ = newFlatFeedWithUserID(client, "123") f1, _ = newFlatFeedWithUserID(client, "f1") f2, _ = newFlatFeedWithUserID(client, "f2") @@ -208,11 +218,11 @@ func TestUpdateToTargets(t *testing.T) { now = getTime(time.Now()) activity = stream.Activity{Time: now, ForeignID: "bob:123", Actor: "bob", Verb: "like", Object: "ice-cream", To: []string{f1.ID()}, Extra: map[string]interface{}{"popularity": 9000}} ) - _, err := flat.UpdateToTargets(activity, stream.WithToTargetsAdd(f2.ID()), stream.WithToTargetsRemove(f1.ID())) + _, err := flat.UpdateToTargets(ctx, activity, stream.WithToTargetsAdd(f2.ID()), stream.WithToTargetsRemove(f1.ID())) require.NoError(t, err) body := fmt.Sprintf(`{"foreign_id":"bob:123","time":%q,"added_targets":["flat:f2"],"removed_targets":["flat:f1"]}`, now.Format(stream.TimeLayout)) testRequest(t, requester.req, http.MethodPost, "https://api.stream-io-api.com/api/v1.0/feed_targets/flat/123/activity_to_targets/?api_key=key", body) - _, err = flat.UpdateToTargets(activity, stream.WithToTargetsNew(f3.ID())) + _, err = flat.UpdateToTargets(ctx, activity, stream.WithToTargetsNew(f3.ID())) require.NoError(t, err) body = fmt.Sprintf(`{"foreign_id":"bob:123","time":%q,"new_targets":["flat:f3"]}`, now.Format(stream.TimeLayout)) testRequest(t, requester.req, http.MethodPost, "https://api.stream-io-api.com/api/v1.0/feed_targets/flat/123/activity_to_targets/?api_key=key", body) diff --git a/flat_feed.go b/flat_feed.go index 6857452..3f58d7f 100644 --- a/flat_feed.go +++ b/flat_feed.go @@ -1,6 +1,9 @@ package stream -import "encoding/json" +import ( + "context" + "encoding/json" +) // FlatFeed is a Stream flat feed. type FlatFeed struct { @@ -9,8 +12,8 @@ type FlatFeed struct { // GetActivities returns the activities for the given FlatFeed, filtering // results with the provided GetActivitiesOption parameters. -func (f *FlatFeed) GetActivities(opts ...GetActivitiesOption) (*FlatFeedResponse, error) { - body, err := f.client.getActivities(f, opts...) +func (f *FlatFeed) GetActivities(ctx context.Context, opts ...GetActivitiesOption) (*FlatFeedResponse, error) { + body, err := f.client.getActivities(ctx, f, opts...) if err != nil { return nil, err } @@ -23,29 +26,29 @@ func (f *FlatFeed) GetActivities(opts ...GetActivitiesOption) (*FlatFeedResponse // GetNextPageActivities returns the activities for the given FlatFeed at the "next" page // of a previous *FlatFeedResponse response, if any. -func (f *FlatFeed) GetNextPageActivities(resp *FlatFeedResponse) (*FlatFeedResponse, error) { +func (f *FlatFeed) GetNextPageActivities(ctx context.Context, resp *FlatFeedResponse) (*FlatFeedResponse, error) { opts, err := resp.parseNext() if err != nil { return nil, err } - return f.GetActivities(opts...) + return f.GetActivities(ctx, opts...) } // GetActivitiesWithRanking returns the activities (filtered) for the given FlatFeed, // using the provided ranking method. -func (f *FlatFeed) GetActivitiesWithRanking(ranking string, opts ...GetActivitiesOption) (*FlatFeedResponse, error) { - return f.GetActivities(append(opts, withActivitiesRanking(ranking))...) +func (f *FlatFeed) GetActivitiesWithRanking(ctx context.Context, ranking string, opts ...GetActivitiesOption) (*FlatFeedResponse, error) { + return f.GetActivities(ctx, append(opts, withActivitiesRanking(ranking))...) } // GetFollowers returns the feeds following the given FlatFeed. -func (f *FlatFeed) GetFollowers(opts ...FollowersOption) (*FollowersResponse, error) { - return f.client.getFollowers(f, opts...) +func (f *FlatFeed) GetFollowers(ctx context.Context, opts ...FollowersOption) (*FollowersResponse, error) { + return f.client.getFollowers(ctx, f, opts...) } // GetEnrichedActivities returns the enriched activities for the given FlatFeed, filtering // results with the provided GetActivitiesOption parameters. -func (f *FlatFeed) GetEnrichedActivities(opts ...GetActivitiesOption) (*EnrichedFlatFeedResponse, error) { - body, err := f.client.getEnrichedActivities(f, opts...) +func (f *FlatFeed) GetEnrichedActivities(ctx context.Context, opts ...GetActivitiesOption) (*EnrichedFlatFeedResponse, error) { + body, err := f.client.getEnrichedActivities(ctx, f, opts...) if err != nil { return nil, err } @@ -58,23 +61,23 @@ func (f *FlatFeed) GetEnrichedActivities(opts ...GetActivitiesOption) (*Enriched // GetNextPageEnrichedActivities returns the enriched activities for the given FlatFeed at the "next" page // of a previous *EnrichedFlatFeedResponse response, if any. -func (f *FlatFeed) GetNextPageEnrichedActivities(resp *EnrichedFlatFeedResponse) (*EnrichedFlatFeedResponse, error) { +func (f *FlatFeed) GetNextPageEnrichedActivities(ctx context.Context, resp *EnrichedFlatFeedResponse) (*EnrichedFlatFeedResponse, error) { opts, err := resp.parseNext() if err != nil { return nil, err } - return f.GetEnrichedActivities(opts...) + return f.GetEnrichedActivities(ctx, opts...) } // GetEnrichedActivitiesWithRanking returns the enriched activities (filtered) for the given FlatFeed, // using the provided ranking method. -func (f *FlatFeed) GetEnrichedActivitiesWithRanking(ranking string, opts ...GetActivitiesOption) (*EnrichedFlatFeedResponse, error) { - return f.GetEnrichedActivities(append(opts, withActivitiesRanking(ranking))...) +func (f *FlatFeed) GetEnrichedActivitiesWithRanking(ctx context.Context, ranking string, opts ...GetActivitiesOption) (*EnrichedFlatFeedResponse, error) { + return f.GetEnrichedActivities(ctx, append(opts, withActivitiesRanking(ranking))...) } // FollowStats returns the follower/following counts of the feed. // If options are given, counts are filtered for the given slugs. // Counts will be capped at 10K, if higher counts are needed and contact to support. -func (f *FlatFeed) FollowStats(opts ...FollowStatOption) (*FollowStatResponse, error) { - return f.client.followStats(f, opts...) +func (f *FlatFeed) FollowStats(ctx context.Context, opts ...FollowStatOption) (*FollowStatResponse, error) { + return f.client.followStats(ctx, f, opts...) } diff --git a/flat_feed_test.go b/flat_feed_test.go index f59310d..c3108f2 100644 --- a/flat_feed_test.go +++ b/flat_feed_test.go @@ -1,6 +1,7 @@ package stream_test import ( + "context" "fmt" "net/http" "testing" @@ -12,6 +13,7 @@ import ( ) func TestFlatFeedGetActivities(t *testing.T) { + ctx := context.Background() client, requester := newClient(t) flat, _ := newFlatFeedWithUserID(client, "123") testCases := []struct { @@ -50,80 +52,82 @@ func TestFlatFeedGetActivities(t *testing.T) { } for _, tc := range testCases { - _, err := flat.GetActivities(tc.opts...) + _, err := flat.GetActivities(ctx, tc.opts...) testRequest(t, requester.req, http.MethodGet, tc.url, "") assert.NoError(t, err) - _, err = flat.GetActivitiesWithRanking("popularity", tc.opts...) + _, err = flat.GetActivitiesWithRanking(ctx, "popularity", tc.opts...) testRequest(t, requester.req, http.MethodGet, fmt.Sprintf("%s&ranking=popularity", tc.url), "") assert.NoError(t, err) - _, err = flat.GetEnrichedActivities(tc.opts...) + _, err = flat.GetEnrichedActivities(ctx, tc.opts...) testRequest(t, requester.req, http.MethodGet, tc.enrichedURL, "") assert.NoError(t, err) - _, err = flat.GetEnrichedActivitiesWithRanking("popularity", tc.opts...) + _, err = flat.GetEnrichedActivitiesWithRanking(ctx, "popularity", tc.opts...) testRequest(t, requester.req, http.MethodGet, fmt.Sprintf("%s&ranking=popularity", tc.enrichedURL), "") assert.NoError(t, err) } } func TestFlatFeedGetNextPageActivities(t *testing.T) { + ctx := context.Background() client, requester := newClient(t) flat, _ := newFlatFeedWithUserID(client, "123") requester.resp = `{"next":"/api/v1.0/feed/flat/123/?id_lt=78c1a709-aff2-11e7-b3a7-a45e60be7d3b&limit=25"}` - resp, err := flat.GetActivities() + resp, err := flat.GetActivities(ctx) require.NoError(t, err) - _, err = flat.GetNextPageActivities(resp) + _, err = flat.GetNextPageActivities(ctx, resp) testRequest(t, requester.req, http.MethodGet, "https://api.stream-io-api.com/api/v1.0/feed/flat/123/?api_key=key&id_lt=78c1a709-aff2-11e7-b3a7-a45e60be7d3b&limit=25", "") require.NoError(t, err) requester.resp = `{"next":"/api/v1.0/enrich/feed/flat/123/?id_lt=78c1a709-aff2-11e7-b3a7-a45e60be7d3b&limit=25"}` - enrichedResp, err := flat.GetEnrichedActivities() + enrichedResp, err := flat.GetEnrichedActivities(ctx) require.NoError(t, err) - _, err = flat.GetNextPageEnrichedActivities(enrichedResp) + _, err = flat.GetNextPageEnrichedActivities(ctx, enrichedResp) testRequest(t, requester.req, http.MethodGet, "https://api.stream-io-api.com/api/v1.0/enrich/feed/flat/123/?api_key=key&id_lt=78c1a709-aff2-11e7-b3a7-a45e60be7d3b&limit=25", "") require.NoError(t, err) requester.resp = `{"next":123}` - _, err = flat.GetActivities() + _, err = flat.GetActivities(ctx) require.Error(t, err) requester.resp = `{"next":"123"}` - resp, err = flat.GetActivities() + resp, err = flat.GetActivities(ctx) require.NoError(t, err) - _, err = flat.GetNextPageActivities(resp) + _, err = flat.GetNextPageActivities(ctx, resp) require.Error(t, err) requester.resp = `{"next":"?q=a%"}` - resp, err = flat.GetActivities() + resp, err = flat.GetActivities(ctx) require.NoError(t, err) - _, err = flat.GetNextPageActivities(resp) + _, err = flat.GetNextPageActivities(ctx, resp) require.Error(t, err) } func TestFlatFeedFollowStats(t *testing.T) { + ctx := context.Background() endpoint := "https://api.stream-io-api.com/api/v1.0/stats/follow/?api_key=key" client, requester := newClient(t) flat, _ := newFlatFeedWithUserID(client, "123") - _, err := flat.FollowStats() + _, err := flat.FollowStats(ctx) testRequest(t, requester.req, http.MethodGet, endpoint+"&followers=flat%3A123&following=flat%3A123", "") assert.NoError(t, err) - _, err = flat.FollowStats(stream.WithFollowerSlugs("a", "b")) + _, err = flat.FollowStats(ctx, stream.WithFollowerSlugs("a", "b")) testRequest(t, requester.req, http.MethodGet, endpoint+"&followers=flat%3A123&followers_slugs=a%2Cb&following=flat%3A123", "") assert.NoError(t, err) - _, err = flat.FollowStats(stream.WithFollowingSlugs("c", "d")) + _, err = flat.FollowStats(ctx, stream.WithFollowingSlugs("c", "d")) testRequest(t, requester.req, http.MethodGet, endpoint+"&followers=flat%3A123&following=flat%3A123&following_slugs=c%2Cd", "") assert.NoError(t, err) - _, err = flat.FollowStats(stream.WithFollowingSlugs("c", "d"), stream.WithFollowerSlugs("a", "b")) + _, err = flat.FollowStats(ctx, stream.WithFollowingSlugs("c", "d"), stream.WithFollowerSlugs("a", "b")) testRequest(t, requester.req, http.MethodGet, endpoint+"&followers=flat%3A123&followers_slugs=a%2Cb&following=flat%3A123&following_slugs=c%2Cd", "") assert.NoError(t, err) } diff --git a/notification_feed.go b/notification_feed.go index a18d0de..bb936ca 100644 --- a/notification_feed.go +++ b/notification_feed.go @@ -1,6 +1,9 @@ package stream -import "encoding/json" +import ( + "context" + "encoding/json" +) // NotificationFeed is a Stream notification feed. type NotificationFeed struct { @@ -9,8 +12,8 @@ type NotificationFeed struct { // GetActivities returns the activities for the given NotificationFeed, filtering // results with the provided GetActivitiesOption parameters. -func (f *NotificationFeed) GetActivities(opts ...GetActivitiesOption) (*NotificationFeedResponse, error) { - body, err := f.client.getActivities(f, opts...) +func (f *NotificationFeed) GetActivities(ctx context.Context, opts ...GetActivitiesOption) (*NotificationFeedResponse, error) { + body, err := f.client.getActivities(ctx, f, opts...) if err != nil { return nil, err } @@ -23,18 +26,18 @@ func (f *NotificationFeed) GetActivities(opts ...GetActivitiesOption) (*Notifica // GetNextPageActivities returns the activities for the given NotificationFeed at the "next" page // of a previous *NotificationFeedResponse response, if any. -func (f *NotificationFeed) GetNextPageActivities(resp *NotificationFeedResponse) (*NotificationFeedResponse, error) { +func (f *NotificationFeed) GetNextPageActivities(ctx context.Context, resp *NotificationFeedResponse) (*NotificationFeedResponse, error) { opts, err := resp.parseNext() if err != nil { return nil, err } - return f.GetActivities(opts...) + return f.GetActivities(ctx, opts...) } // GetEnrichedActivities returns the enriched activities for the given NotificationFeed, filtering // results with the provided GetActivitiesOption parameters. -func (f *NotificationFeed) GetEnrichedActivities(opts ...GetActivitiesOption) (*EnrichedNotificationFeedResponse, error) { - body, err := f.client.getEnrichedActivities(f, opts...) +func (f *NotificationFeed) GetEnrichedActivities(ctx context.Context, opts ...GetActivitiesOption) (*EnrichedNotificationFeedResponse, error) { + body, err := f.client.getEnrichedActivities(ctx, f, opts...) if err != nil { return nil, err } @@ -47,10 +50,10 @@ func (f *NotificationFeed) GetEnrichedActivities(opts ...GetActivitiesOption) (* // GetNextPageEnrichedActivities returns the enriched activities for the given NotificationFeed at the "next" page // of a previous *EnrichedNotificationFeedResponse response, if any. -func (f *NotificationFeed) GetNextPageEnrichedActivities(resp *EnrichedNotificationFeedResponse) (*EnrichedNotificationFeedResponse, error) { +func (f *NotificationFeed) GetNextPageEnrichedActivities(ctx context.Context, resp *EnrichedNotificationFeedResponse) (*EnrichedNotificationFeedResponse, error) { opts, err := resp.parseNext() if err != nil { return nil, err } - return f.GetEnrichedActivities(opts...) + return f.GetEnrichedActivities(ctx, opts...) } diff --git a/notification_feed_test.go b/notification_feed_test.go index d1ddd3d..a72a023 100644 --- a/notification_feed_test.go +++ b/notification_feed_test.go @@ -1,6 +1,7 @@ package stream_test import ( + "context" "net/http" "testing" @@ -11,6 +12,7 @@ import ( ) func TestGetNotificationActivities(t *testing.T) { + ctx := context.Background() client, requester := newClient(t) notification, _ := newNotificationFeedWithUserID(client, "123") testCases := []struct { @@ -55,49 +57,50 @@ func TestGetNotificationActivities(t *testing.T) { } for _, tc := range testCases { - _, err := notification.GetActivities(tc.opts...) + _, err := notification.GetActivities(ctx, tc.opts...) testRequest(t, requester.req, http.MethodGet, tc.url, "") assert.NoError(t, err) - _, err = notification.GetEnrichedActivities(tc.opts...) + _, err = notification.GetEnrichedActivities(ctx, tc.opts...) testRequest(t, requester.req, http.MethodGet, tc.enrichedURL, "") assert.NoError(t, err) } } func TestNotificationFeedGetNextPageActivities(t *testing.T) { + ctx := context.Background() client, requester := newClient(t) notification, _ := newNotificationFeedWithUserID(client, "123") requester.resp = `{"next":"/api/v1.0/feed/notification/123/?id_lt=78c1a709-aff2-11e7-b3a7-a45e60be7d3b&limit=25"}` - resp, err := notification.GetActivities() + resp, err := notification.GetActivities(ctx) require.NoError(t, err) - _, err = notification.GetNextPageActivities(resp) + _, err = notification.GetNextPageActivities(ctx, resp) testRequest(t, requester.req, http.MethodGet, "https://api.stream-io-api.com/api/v1.0/feed/notification/123/?api_key=key&id_lt=78c1a709-aff2-11e7-b3a7-a45e60be7d3b&limit=25", "") require.NoError(t, err) requester.resp = `{"next":"/api/v1.0/enrich/feed/notification/123/?id_lt=78c1a709-aff2-11e7-b3a7-a45e60be7d3b&limit=25"}` - enrichedResp, err := notification.GetEnrichedActivities() + enrichedResp, err := notification.GetEnrichedActivities(ctx) require.NoError(t, err) - _, err = notification.GetNextPageEnrichedActivities(enrichedResp) + _, err = notification.GetNextPageEnrichedActivities(ctx, enrichedResp) testRequest(t, requester.req, http.MethodGet, "https://api.stream-io-api.com/api/v1.0/enrich/feed/notification/123/?api_key=key&id_lt=78c1a709-aff2-11e7-b3a7-a45e60be7d3b&limit=25", "") require.NoError(t, err) requester.resp = `{"next":123}` - _, err = notification.GetActivities() + _, err = notification.GetActivities(ctx) require.Error(t, err) requester.resp = `{"next":"123"}` - resp, err = notification.GetActivities() + resp, err = notification.GetActivities(ctx) require.NoError(t, err) - _, err = notification.GetNextPageActivities(resp) + _, err = notification.GetNextPageActivities(ctx, resp) require.Error(t, err) requester.resp = `{"next":"?q=a%"}` - resp, err = notification.GetActivities() + resp, err = notification.GetActivities(ctx) require.NoError(t, err) - _, err = notification.GetNextPageActivities(resp) + _, err = notification.GetNextPageActivities(ctx, resp) require.Error(t, err) } diff --git a/personalization.go b/personalization.go index be49233..a0da245 100644 --- a/personalization.go +++ b/personalization.go @@ -1,6 +1,7 @@ package stream import ( + "context" "encoding/json" "errors" "fmt" @@ -23,7 +24,7 @@ func (c *PersonalizationClient) decode(resp []byte, err error) (*Personalization } // Get obtains a PersonalizationResponse for the given resource and params. -func (c *PersonalizationClient) Get(resource string, params map[string]interface{}) (*PersonalizationResponse, error) { +func (c *PersonalizationClient) Get(ctx context.Context, resource string, params map[string]interface{}) (*PersonalizationResponse, error) { if resource == "" { return nil, errors.New("missing resource") } @@ -31,11 +32,11 @@ func (c *PersonalizationClient) Get(resource string, params map[string]interface for k, v := range params { endpoint.addQueryParam(makeRequestOption(k, v)) } - return c.decode(c.client.get(endpoint, nil, c.client.authenticator.personalizationAuth)) + return c.decode(c.client.get(ctx, endpoint, nil, c.client.authenticator.personalizationAuth)) } // Post sends data to the given resource, adding the given params to the request. -func (c *PersonalizationClient) Post(resource string, params, data map[string]interface{}) (*PersonalizationResponse, error) { +func (c *PersonalizationClient) Post(ctx context.Context, resource string, params, data map[string]interface{}) (*PersonalizationResponse, error) { if resource == "" { return nil, errors.New("missing resource") } @@ -48,11 +49,11 @@ func (c *PersonalizationClient) Post(resource string, params, data map[string]in "data": data, } } - return c.decode(c.client.post(endpoint, data, c.client.authenticator.personalizationAuth)) + return c.decode(c.client.post(ctx, endpoint, data, c.client.authenticator.personalizationAuth)) } // Delete removes data from the given resource, adding the given params to the request. -func (c *PersonalizationClient) Delete(resource string, params map[string]interface{}) (*PersonalizationResponse, error) { +func (c *PersonalizationClient) Delete(ctx context.Context, resource string, params map[string]interface{}) (*PersonalizationResponse, error) { if resource == "" { return nil, errors.New("missing resource") } @@ -60,5 +61,5 @@ func (c *PersonalizationClient) Delete(resource string, params map[string]interf for k, v := range params { endpoint.addQueryParam(makeRequestOption(k, v)) } - return c.decode(c.client.delete(endpoint, nil, c.client.authenticator.personalizationAuth)) + return c.decode(c.client.delete(ctx, endpoint, nil, c.client.authenticator.personalizationAuth)) } diff --git a/personalization_test.go b/personalization_test.go index d8bbc87..49df41e 100644 --- a/personalization_test.go +++ b/personalization_test.go @@ -1,6 +1,7 @@ package stream_test import ( + "context" "net/http" "testing" @@ -8,25 +9,27 @@ import ( ) func TestPersonalizationGet(t *testing.T) { + ctx := context.Background() client, requester := newClient(t) p := client.Personalization() params := map[string]interface{}{"answer": 42, "feed": "user:123"} - _, err := p.Get("", params) + _, err := p.Get(ctx, "", params) require.Error(t, err) - _, err = p.Get("some_resource", params) + _, err = p.Get(ctx, "some_resource", params) require.NoError(t, err) expectedURL := "https://personalization.stream-io-api.com/personalization/v1.0/some_resource/?answer=42&api_key=key&feed=user%3A123" testRequest(t, requester.req, http.MethodGet, expectedURL, "") } func TestPersonalizationPost(t *testing.T) { + ctx := context.Background() client, requester := newClient(t) p := client.Personalization() params := map[string]interface{}{"answer": 42, "feed": "user:123"} - _, err := p.Post("", params, nil) + _, err := p.Post(ctx, "", params, nil) require.Error(t, err) data := map[string]interface{}{"foo": "bar", "baz": 42} - _, err = p.Post("some_resource", params, data) + _, err = p.Post(ctx, "some_resource", params, data) require.NoError(t, err) expectedURL := "https://personalization.stream-io-api.com/personalization/v1.0/some_resource/?answer=42&api_key=key&feed=user%3A123" expectedBody := `{"data":{"baz":42,"foo":"bar"}}` @@ -34,12 +37,13 @@ func TestPersonalizationPost(t *testing.T) { } func TestPersonalizationDelete(t *testing.T) { + ctx := context.Background() client, requester := newClient(t) p := client.Personalization() params := map[string]interface{}{"answer": 42, "feed": "user:123"} - _, err := p.Delete("", params) + _, err := p.Delete(ctx, "", params) require.Error(t, err) - _, err = p.Delete("some_resource", params) + _, err = p.Delete(ctx, "some_resource", params) require.NoError(t, err) expectedURL := "https://personalization.stream-io-api.com/personalization/v1.0/some_resource/?answer=42&api_key=key&feed=user%3A123" testRequest(t, requester.req, http.MethodDelete, expectedURL, "") diff --git a/reactions.go b/reactions.go index 763a25e..e0bcac6 100644 --- a/reactions.go +++ b/reactions.go @@ -1,6 +1,7 @@ package stream import ( + "context" "encoding/json" "errors" "fmt" @@ -12,22 +13,22 @@ type ReactionsClient struct { } // Add adds a reaction. -func (c *ReactionsClient) Add(r AddReactionRequestObject) (*ReactionResponse, error) { +func (c *ReactionsClient) Add(ctx context.Context, r AddReactionRequestObject) (*ReactionResponse, error) { if r.ParentID != "" { return nil, errors.New("`Parent` not empty. For adding child reactions use `AddChild`") } - return c.addReaction(r) + return c.addReaction(ctx, r) } // AddChild adds a child reaction to the provided parent. -func (c *ReactionsClient) AddChild(parentID string, r AddReactionRequestObject) (*ReactionResponse, error) { +func (c *ReactionsClient) AddChild(ctx context.Context, parentID string, r AddReactionRequestObject) (*ReactionResponse, error) { r.ParentID = parentID - return c.addReaction(r) + return c.addReaction(ctx, r) } -func (c *ReactionsClient) addReaction(r AddReactionRequestObject) (*ReactionResponse, error) { +func (c *ReactionsClient) addReaction(ctx context.Context, r AddReactionRequestObject) (*ReactionResponse, error) { endpoint := c.client.makeEndpoint("reaction/") - return c.decode(c.client.post(endpoint, r, c.client.authenticator.reactionsAuth)) + return c.decode(c.client.post(ctx, endpoint, r, c.client.authenticator.reactionsAuth)) } func (c *ReactionsClient) decode(resp []byte, err error) (*ReactionResponse, error) { @@ -43,32 +44,32 @@ func (c *ReactionsClient) decode(resp []byte, err error) (*ReactionResponse, err } // Update updates the reaction's data and/or target feeds. -func (c *ReactionsClient) Update(id string, data map[string]interface{}, targetFeeds []string) (*ReactionResponse, error) { +func (c *ReactionsClient) Update(ctx context.Context, id string, data map[string]interface{}, targetFeeds []string) (*ReactionResponse, error) { endpoint := c.client.makeEndpoint("reaction/%s/", id) reqData := map[string]interface{}{ "data": data, "target_feeds": targetFeeds, } - return c.decode(c.client.put(endpoint, reqData, c.client.authenticator.reactionsAuth)) + return c.decode(c.client.put(ctx, endpoint, reqData, c.client.authenticator.reactionsAuth)) } // Get retrieves a reaction having the given id. -func (c *ReactionsClient) Get(id string) (*ReactionResponse, error) { +func (c *ReactionsClient) Get(ctx context.Context, id string) (*ReactionResponse, error) { endpoint := c.client.makeEndpoint("reaction/%s/", id) - return c.decode(c.client.get(endpoint, nil, c.client.authenticator.reactionsAuth)) + return c.decode(c.client.get(ctx, endpoint, nil, c.client.authenticator.reactionsAuth)) } // Delete deletes a reaction having the given id. -func (c *ReactionsClient) Delete(id string) (*ReactionResponse, error) { +func (c *ReactionsClient) Delete(ctx context.Context, id string) (*ReactionResponse, error) { endpoint := c.client.makeEndpoint("reaction/%s/", id) - return c.decode(c.client.delete(endpoint, nil, c.client.authenticator.reactionsAuth)) + return c.decode(c.client.delete(ctx, endpoint, nil, c.client.authenticator.reactionsAuth)) } // Filter lists reactions based on the provided criteria and with the specified pagination. -func (c *ReactionsClient) Filter(attr FilterReactionsAttribute, opts ...FilterReactionsOption) (*FilterReactionResponse, error) { +func (c *ReactionsClient) Filter(ctx context.Context, attr FilterReactionsAttribute, opts ...FilterReactionsOption) (*FilterReactionResponse, error) { endpointURI := fmt.Sprintf("reaction/%s/", attr()) endpoint := c.client.makeEndpoint(endpointURI) @@ -76,7 +77,7 @@ func (c *ReactionsClient) Filter(attr FilterReactionsAttribute, opts ...FilterRe endpoint.addQueryParam(opt) } - resp, err := c.client.get(endpoint, nil, c.client.authenticator.reactionsAuth) + resp, err := c.client.get(ctx, endpoint, nil, c.client.authenticator.reactionsAuth) if err != nil { return nil, err } @@ -89,10 +90,10 @@ func (c *ReactionsClient) Filter(attr FilterReactionsAttribute, opts ...FilterRe } // GetNextPageFilteredReactions returns the reactions at the "next" page of a previous *FilterReactionResponse response, if any. -func (c *ReactionsClient) GetNextPageFilteredReactions(resp *FilterReactionResponse) (*FilterReactionResponse, error) { +func (c *ReactionsClient) GetNextPageFilteredReactions(ctx context.Context, resp *FilterReactionResponse) (*FilterReactionResponse, error) { opts, err := resp.parseNext() if err != nil { return nil, err } - return c.Filter(resp.meta.attr, opts...) + return c.Filter(ctx, resp.meta.attr, opts...) } diff --git a/reactions_test.go b/reactions_test.go index 709be3f..d4f79e4 100644 --- a/reactions_test.go +++ b/reactions_test.go @@ -1,6 +1,7 @@ package stream_test import ( + "context" "net/http" "testing" @@ -10,20 +11,23 @@ import ( ) func TestGetReaction(t *testing.T) { + ctx := context.Background() client, requester := newClient(t) - _, err := client.Reactions().Get("id1") + _, err := client.Reactions().Get(ctx, "id1") require.NoError(t, err) testRequest(t, requester.req, http.MethodGet, "https://api.stream-io-api.com/api/v1.0/reaction/id1/?api_key=key", "") } func TestDeleteReaction(t *testing.T) { + ctx := context.Background() client, requester := newClient(t) - _, err := client.Reactions().Delete("id1") + _, err := client.Reactions().Delete(ctx, "id1") require.NoError(t, err) testRequest(t, requester.req, http.MethodDelete, "https://api.stream-io-api.com/api/v1.0/reaction/id1/?api_key=key", "") } func TestAddReaction(t *testing.T) { + ctx := context.Background() client, requester := newClient(t) testCases := []struct { @@ -72,13 +76,14 @@ func TestAddReaction(t *testing.T) { }, } for _, tc := range testCases { - _, err := client.Reactions().Add(tc.input) + _, err := client.Reactions().Add(ctx, tc.input) require.NoError(t, err) testRequest(t, requester.req, http.MethodPost, tc.expectedURL, tc.expectedBody) } } func TestAddChildReaction(t *testing.T) { + ctx := context.Background() client, requester := newClient(t) reaction := stream.AddReactionRequestObject{ @@ -100,12 +105,13 @@ func TestAddChildReaction(t *testing.T) { "target_feeds": ["stalker:timeline"],"target_feeds_extra_data":{"activity_field":"activity_value"} }` - _, err := client.Reactions().AddChild("pid", reaction) + _, err := client.Reactions().AddChild(ctx, "pid", reaction) require.NoError(t, err) testRequest(t, requester.req, http.MethodPost, expectedURL, expectedBody) } func TestUpdateReaction(t *testing.T) { + ctx := context.Background() client, requester := newClient(t) testCases := []struct { @@ -131,13 +137,14 @@ func TestUpdateReaction(t *testing.T) { }, } for _, tc := range testCases { - _, err := client.Reactions().Update(tc.id, tc.data, tc.targetFeeds) + _, err := client.Reactions().Update(ctx, tc.id, tc.data, tc.targetFeeds) require.NoError(t, err) testRequest(t, requester.req, http.MethodPut, tc.expectedURL, tc.expectedBody) } } func TestFilterReactions(t *testing.T) { + ctx := context.Background() client, requester := newClient(t) testCases := []struct { attr stream.FilterReactionsAttribute @@ -173,25 +180,27 @@ func TestFilterReactions(t *testing.T) { } for _, tc := range testCases { - _, err := client.Reactions().Filter(tc.attr, tc.opts...) + _, err := client.Reactions().Filter(ctx, tc.attr, tc.opts...) require.NoError(t, err) testRequest(t, requester.req, http.MethodGet, tc.expectedURL, "") } } func TestGetNextPageReactions(t *testing.T) { + ctx := context.Background() client, requester := newClient(t) requester.resp = `{"next":"/api/v1.0/reaction/user_id/uid/upvote/?api_key=key&id_gt=uid1&limit=100&with_activity_data=true"}` - resp, err := client.Reactions().Filter(stream.ByUserID("uid").ByKind("like"), stream.WithLimit(10), stream.WithActivityData(), stream.WithIDGT("id1")) + resp, err := client.Reactions().Filter(ctx, stream.ByUserID("uid").ByKind("like"), stream.WithLimit(10), stream.WithActivityData(), stream.WithIDGT("id1")) require.NoError(t, err) - _, err = client.Reactions().GetNextPageFilteredReactions(resp) + _, err = client.Reactions().GetNextPageFilteredReactions(ctx, resp) testRequest(t, requester.req, http.MethodGet, "https://api.stream-io-api.com/api/v1.0/reaction/user_id/uid/like/?api_key=key&id_gt=uid1&limit=100&with_activity_data=true", "") require.NoError(t, err) requester.resp = `{"next":"/api/v1.0/reaction/user_id/uid/upvote/?api_key=key&id_gt=uid1&limit=100&with_own_children=true"}` resp, err = client.Reactions().Filter( + ctx, stream.ByUserID("uid").ByKind("like"), stream.WithLimit(10), stream.WithOwnChildren(), @@ -201,6 +210,7 @@ func TestGetNextPageReactions(t *testing.T) { requester.resp = `{"next":"/api/v1.0/reaction/user_id/uid/upvote/?api_key=key&id_gt=uid1&limit=100&with_own_children=true&with_own_children_kinds=comment,like&user_id=something&children_user_id=child_user_id"}` _, err = client.Reactions().Filter( + ctx, stream.ByUserID("uid").ByKind("like"), stream.WithLimit(10), stream.WithIDGT("id1"), @@ -209,27 +219,27 @@ func TestGetNextPageReactions(t *testing.T) { ) require.NoError(t, err) - _, err = client.Reactions().GetNextPageFilteredReactions(resp) + _, err = client.Reactions().GetNextPageFilteredReactions(ctx, resp) testRequest(t, requester.req, http.MethodGet, "https://api.stream-io-api.com/api/v1.0/reaction/user_id/uid/like/?api_key=key&id_gt=uid1&limit=100&with_own_children=true", "") require.NoError(t, err) requester.resp = `{"next":"/api/v1.0/reaction/user_id/uid/upvote/?api_key=key&id_gt=uid1&limit=100&with_activity_data=false"}` - resp, err = client.Reactions().Filter(stream.ByUserID("uid").ByKind("like"), stream.WithLimit(10), stream.WithActivityData(), stream.WithIDGT("id1")) + resp, err = client.Reactions().Filter(ctx, stream.ByUserID("uid").ByKind("like"), stream.WithLimit(10), stream.WithActivityData(), stream.WithIDGT("id1")) require.NoError(t, err) - _, err = client.Reactions().GetNextPageFilteredReactions(resp) + _, err = client.Reactions().GetNextPageFilteredReactions(ctx, resp) testRequest(t, requester.req, http.MethodGet, "https://api.stream-io-api.com/api/v1.0/reaction/user_id/uid/like/?api_key=key&id_gt=uid1&limit=100", "") require.NoError(t, err) requester.resp = `{"next":"123"}` - resp, err = client.Reactions().Filter(stream.ByActivityID("aid")) + resp, err = client.Reactions().Filter(ctx, stream.ByActivityID("aid")) require.NoError(t, err) - _, err = client.Reactions().GetNextPageFilteredReactions(resp) + _, err = client.Reactions().GetNextPageFilteredReactions(ctx, resp) require.Error(t, err) requester.resp = `{"next":"?q=a%"}` - resp, err = client.Reactions().Filter(stream.ByActivityID("aid")) + resp, err = client.Reactions().Filter(ctx, stream.ByActivityID("aid")) require.NoError(t, err) - _, err = client.Reactions().GetNextPageFilteredReactions(resp) + _, err = client.Reactions().GetNextPageFilteredReactions(ctx, resp) require.Error(t, err) } diff --git a/run-lint.sh b/run-lint.sh index cfb9af4..966b3af 100755 --- a/run-lint.sh +++ b/run-lint.sh @@ -9,7 +9,7 @@ gopath="$(go env GOPATH)" if ! [[ -x "$gopath/bin/golangci-lint" ]]; then echo >&2 'Installing golangci-lint' curl --silent --fail --location \ - https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b "$gopath/bin" v1.45.2 + https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b "$gopath/bin" v1.46.0 fi # configured by .golangci.yml diff --git a/users.go b/users.go index 92d11ab..a3e17aa 100644 --- a/users.go +++ b/users.go @@ -1,6 +1,7 @@ package stream import ( + "context" "encoding/json" "fmt" ) @@ -22,35 +23,35 @@ func (c *UsersClient) decode(resp []byte, err error) (*UserResponse, error) { } // Add adds a new user with the specified id and optional extra data. -func (c *UsersClient) Add(user User, getOrCreate bool) (*UserResponse, error) { +func (c *UsersClient) Add(ctx context.Context, user User, getOrCreate bool) (*UserResponse, error) { endpoint := c.client.makeEndpoint("user/") endpoint.addQueryParam(makeRequestOption("get_or_create", getOrCreate)) - return c.decode(c.client.post(endpoint, user, c.client.authenticator.usersAuth)) + return c.decode(c.client.post(ctx, endpoint, user, c.client.authenticator.usersAuth)) } // Update updates the user's data. -func (c *UsersClient) Update(id string, data map[string]interface{}) (*UserResponse, error) { +func (c *UsersClient) Update(ctx context.Context, id string, data map[string]interface{}) (*UserResponse, error) { endpoint := c.client.makeEndpoint("user/%s/", id) reqData := map[string]interface{}{ "data": data, } - return c.decode(c.client.put(endpoint, reqData, c.client.authenticator.usersAuth)) + return c.decode(c.client.put(ctx, endpoint, reqData, c.client.authenticator.usersAuth)) } // Get retrieves a user having the given id. -func (c *UsersClient) Get(id string) (*UserResponse, error) { +func (c *UsersClient) Get(ctx context.Context, id string) (*UserResponse, error) { endpoint := c.client.makeEndpoint("user/%s/", id) - return c.decode(c.client.get(endpoint, nil, c.client.authenticator.usersAuth)) + return c.decode(c.client.get(ctx, endpoint, nil, c.client.authenticator.usersAuth)) } // Delete deletes a user having the given id. -func (c *UsersClient) Delete(id string) (*BaseResponse, error) { +func (c *UsersClient) Delete(ctx context.Context, id string) (*BaseResponse, error) { endpoint := c.client.makeEndpoint("user/%s/", id) - return decode(c.client.delete(endpoint, nil, c.client.authenticator.usersAuth)) + return decode(c.client.delete(ctx, endpoint, nil, c.client.authenticator.usersAuth)) } // CreateReference returns a new reference string in the form SU:. diff --git a/users_test.go b/users_test.go index 331a4a2..e6ef6b5 100644 --- a/users_test.go +++ b/users_test.go @@ -1,6 +1,7 @@ package stream_test import ( + "context" "net/http" "testing" @@ -17,22 +18,25 @@ func TestUserRefHelpers(t *testing.T) { } func TestGetUser(t *testing.T) { + ctx := context.Background() client, requester := newClient(t) - _, err := client.Users().Get("id1") + _, err := client.Users().Get(ctx, "id1") require.NoError(t, err) testRequest(t, requester.req, http.MethodGet, "https://api.stream-io-api.com/api/v1.0/user/id1/?api_key=key", "") } func TestDeleteUser(t *testing.T) { + ctx := context.Background() client, requester := newClient(t) - _, err := client.Users().Delete("id1") + _, err := client.Users().Delete(ctx, "id1") require.NoError(t, err) testRequest(t, requester.req, http.MethodDelete, "https://api.stream-io-api.com/api/v1.0/user/id1/?api_key=key", "") } func TestAddUser(t *testing.T) { + ctx := context.Background() client, requester := newClient(t) testCases := []struct { object stream.User @@ -66,19 +70,20 @@ func TestAddUser(t *testing.T) { } for _, tc := range testCases { - _, err := client.Users().Add(tc.object, tc.getOrCreate) + _, err := client.Users().Add(ctx, tc.object, tc.getOrCreate) require.NoError(t, err) testRequest(t, requester.req, http.MethodPost, tc.expectedURL, tc.expectedBody) } } func TestUpdateUser(t *testing.T) { + ctx := context.Background() client, requester := newClient(t) data := map[string]interface{}{ "name": "Jane", } - _, err := client.Users().Update("123", data) + _, err := client.Users().Update(ctx, "123", data) require.NoError(t, err) expectedBody := `{"data":{"name":"Jane"}}` testRequest(t, requester.req, http.MethodPut, "https://api.stream-io-api.com/api/v1.0/user/123/?api_key=key", expectedBody)