GetStream.io Go client
tbarbugli Merge pull request #26 from GetStream/feature/enrichment_options
add enrichment options to fetch activities
Latest commit 173a953 Dec 14, 2018
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
.gitignore adjusted tests for travis and codecov Oct 3, 2017
.travis.yml test go 1.11 Sep 4, 2018
Gopkg.lock updated Gopkg Sep 27, 2017
Gopkg.toml updated Gopkg Sep 27, 2017
LICENSE Initial commit Sep 6, 2017
README.md [AC-99] forgot smth Dec 12, 2018
activity.go [AC-99] enrichment Dec 11, 2018
activity_test.go test to activity encoding for to targets Jan 25, 2018
aggregated_feed.go [AC-99] update docs, refactor a method Dec 12, 2018
aggregated_feed_test.go [AC-99] enrichment Dec 11, 2018
analytics.go first implementation of analytics client Apr 9, 2018
analytics_test.go first implementation of analytics client Apr 9, 2018
analytics_types.go add godoc comments Apr 10, 2018
authenticator.go [AC-99] remove jwt claim Dec 12, 2018
authenticator_test.go to targets signature Oct 2, 2017
client.go [AC-99] enrichment Dec 11, 2018
client_internal_test.go Update client_internal_test.go Nov 16, 2018
client_test.go cleanup Oct 17, 2018
collections.go [AC-99] users endpoint Dec 10, 2018
collections_test.go [AC-99] users endpoint Dec 10, 2018
enriched_activities.go [AC-99] enrichment Dec 11, 2018
errors.go safer type for exception fields Oct 2, 2017
errors_test.go moved ownership Sep 28, 2017
feed.go move UpdateActivities to Client level Aug 6, 2018
feed_test.go move UpdateActivities to Client level Aug 6, 2018
flat_feed.go [AC-99] update docs, refactor a method Dec 12, 2018
flat_feed_test.go [AC-99] enrichment Dec 11, 2018
notification_feed.go [AC-99] update docs, refactor a method Dec 12, 2018
notification_feed_test.go [AC-99] enrichment Dec 11, 2018
options.go add enrichment options to fetch activities Dec 14, 2018
personalization.go collections endpoints and personalization client Apr 11, 2018
personalization_test.go collections endpoints and personalization client Apr 11, 2018
reactions.go [AC-99] update docs, refactor a method Dec 12, 2018
reactions_test.go [AC-99] forgot opt Dec 11, 2018
stream.go support alternate time layouts Apr 12, 2018
test.sh adjusted tests for travis and codecov Oct 3, 2017
types.go omit empty collectiond id Dec 14, 2018
types_internal_test.go proper flag parsing Dec 14, 2018
types_test.go moved ownership Sep 28, 2017
url.go Merge branch 'master' into personalization Apr 12, 2018
url_internal_test.go collections endpoints and personalization client Apr 11, 2018
users.go [AC-99] better name Dec 10, 2018
users_test.go [AC-99] better name Dec 10, 2018
utils.go proper flag parsing Dec 14, 2018
utils_internal_test.go test float duration in decodeJSONStringTimes Apr 13, 2018
utils_test.go moved ownership Sep 28, 2017
version.go Version 1.9.1 Jul 26, 2018

README.md

stream-go2

stream-go2 is a Go client for Stream API.

You can sign up for a Stream account at getstream.io/get_started.

Build Status godoc codecov Go Report Card

Contents

Usage

Get the client:

$ go get gopkg.in/GetStream/stream-go2.v1

stream-go2 uses dep for managing dependencies (see Gopkg.toml and Gopkg.lock). You can get required dependencies simply by running:

$ dep ensure

Even better: at Stream we have developed vg, a powerful workspace manager for Go based on dep itself. If you use vg (and you should!) you can just:

$ vg init && vg ensure

Creating a Client

key := "YOUR_API_KEY"
secret := "YOUR_API_SECRET"

client, err := stream.NewClient(key, secret)
if err != nil {
    // ...
}

You can pass additional options when creating a client using the available ClientOption functions:

client, err := stream.NewClient(key, secret,
    stream.WithAPIRegion("us-east"),
    stream.WithAPIVersion("1.0"),
    ...,
)

You can also create a client using environment variables:

client, err := stream.NewClientFromEnv()

Available environment variables:

  • STREAM_API_KEY
  • STREAM_API_SECRET
  • STREAM_API_REGION
  • STREAM_API_VERSION

Creating a Feed

Create a flat feed from slug and user ID:

flat := client.FlatFeed("user", "123")

Create an aggregated feed from slug and user ID:

aggr := client.AggregatedFeed("aggregated", "123")

Create a notification feed from slug and user ID:

notif := client.NotificationFeed("notification", "123")

Flat, aggregated, and notification feeds implement the Feed interface methods.

In the snippets below, feed indicates any kind of feed, while flat, aggregated, and notification are used to indicate that only that kind of feed has certain methods or can perform certain operations.

Retrieving activities

Flat feeds

resp, err := flat.GetActivities()
if err != nil {
    // ...
}

fmt.Println("Duration:", resp.Duration)
fmt.Println("Next:", resp.Next)
fmt.Println("Activities:")
for _, activity := range resp.Results {
    fmt.Println(activity)
}

You can retrieve flat feeds with custom ranking, using the dedicated method:

resp, err := flat.GetActivitiesWithRanking("popularity")
if err != nil {
    // ...
}

Aggregated feeds

resp, err := aggregated.GetActivities()
if err != nil {
    // ...
}

fmt.Println("Duration:", resp.Duration)
fmt.Println("Next:", resp.Next)
fmt.Println("Groups:")
for _, group := range resp.Results {
    fmt.Println("Group:", group.Name, "ID:", group.ID, "Verb:", group.Verb)
    fmt.Println("Activities:", group.ActivityCount, "Actors:", group.ActorCount)
    for _, activity := range group.Activities {
        // ...
    }
}

Notification feeds

resp, err := notification.GetActivities()
if err != nil {
    // ...
}

fmt.Println("Duration:", resp.Duration)
fmt.Println("Next:", resp.Next)
fmt.Println("Unseen:", resp.Unseen, "Unread:", resp.Unread)
fmt.Println("Groups:")
for _, group := range resp.Results {
    fmt.Println("Group:", group.Group, "ID:", group.ID, "Verb:", group.Verb)
    fmt.Println("Seen:", group.IsSeen, "Read:", group.IsRead)
    fmt.Println("Activities:", group.ActivityCount, "Actors:", group.ActorCount)
    for _, activity := range group.Activities {
        // ...
    }
}

Options

You can pass supported options and filters when retrieving activities:

resp, err := flat.GetActivities(
    stream.WithActivitiesIDGTE("f505b3fb-a212-11e7-..."),
    stream.WithActivitiesLimit(5),
    ...,
)

Adding activities

Add a single activity:

resp, err := feed.AddActivity(stream.Activity{Actor: "bob", ...})
if err != nil {
    // ...
}

fmt.Println("Duration:", resp.Duration)
fmt.Println("Activity:", resp.Activity) // resp wraps the stream.Activity type

Add multiple activities:

a1 := stream.Activity{Actor: "bob", ...}
a2 := stream.Activity{Actor: "john", ...}
a3 := stream.Activity{Actor: "alice", ...}

resp, err := feed.AddActivities(a1, a2, a3)
if err != nil {
    // ...
}

fmt.Println("Duration:", resp.Duration)
fmt.Println("Activities:")
for _, activity := range resp.Activities {
    fmt.Println(activity)
}

Updating activities

err := feed.UpdateActivities(a1, a2, ...)
if err != nil {
    // ...
}

Removing activities

You can either remove activities by ID or ForeignID:

err := feed.RemoveActivityByID("f505b3fb-a212-11e7-...")
if err != nil {
    // ...
}

err := feed.RemoveActivityByForeignID("bob:123")
if err != nil {
    // ...
}

Following another feed

err := feed.Follow(anotherFeed)
if err != nil {
    // ...
}

Beware that it's possible to follow only flat feeds.

Options

You can pass options to the Follow method. For example:

err := feed.Follow(anotherFeed,
    stream.WithFollowFeedActivityCopyLimit(15),
    ...,
)

Retrieving followers and followings

Following

Get the feeds that a feed is following:

resp, err := feed.GetFollowing()
if err != nil {
    // ...
}

fmt.Println("Duration:", resp.Duration)
for _, followed := range resp.Results {
    fmt.Println(followed.FeedID, followed.TargetID)
}

You can pass options to GetFollowing:

resp, err := feed.GetFollowing(
    stream.WithFollowingLimit(5),
    ...,
)

Followers

resp, err := flat.GetFollowers()
if err != nil {
    // ...
}

fmt.Println("Duration:", resp.Duration)
for _, follower := range resp.Results {
    fmt.Println(follower.FeedID, follower.TargetID)
}

Note: this is only possible for FlatFeed types.

You can pass options to GetFollowers:

resp, err := feed.GetFollowing(
    stream.WithFollowersLimit(5),
    ...,
)

Unfollowing a feed

err := flat.Unfollow(anotherFeed)
if err != nil {
    // ...
}

You can pass options to Unfollow:

err := flat.Unfollow(anotherFeed,
    stream.WithUnfollowKeepHistory(true),
    ...,
)

Updating an activity's to targets

Remove all old targets and set new ones (replace):

newTargets := []stream.Feed{f1, f2}

err := feed.UpdateToTargets(activity, stream.WithToTargetsNew(newTargets...))
if err != nil {
    // ...
}

Add some targets and remove some others:

add := []stream.Feed{target1, target2}
remove := []stream.Feed{oldTarget1, oldTarget2}

err := feed.UpdateToTargets(
    activity,
    stream.WithToTargetsAdd(add),
    stream.WithToTargetsRemove(remove),
)
if err != nil {
    // ...
}

Note: you can't mix stream.WithToTargetsNew with stream.WithToTargetsAdd or stream.WithToTargetsRemove.

Batch adding activities

You can add the same activities to multiple feeds at once with the (*Client).AddToMany method (docs):

err := client.AddToMany(activity,
    feed1, feed2, ...,
)
if err != nil {
    // ...
}

Batch creating follows

You can create multiple follow relationships at once with the (*Client).FollowMany method (docs):

relationships := []stream.FollowRelationship{
    stream.NewFollowRelationship(source, target),
    ...,
}

err := client.FollowMany(relationships)
if err != nil {
    // ...
}

Realtime tokens

You can get a token suitable for client-side real-time feed updates as:

// Read+Write token
token := feed.RealtimeToken(false)

// Read-only token
readonlyToken := feed.RealtimeToken(true)

Analytics

If your app is enabled for analytics collection you can use the Go client to track events. The main documentation for the analytics features is available in our Docs page.

Obtaining an Analytics client

You can obtain a specialized Analytics client (*stream.AnalyticsClient) from a regular client, which you can use to track events:

// Create the client
analytics := client.Analytics()

Tracking engagement

Engagement events can be tracked with the TrackEngagement method of AnalyticsClient. It accepts any number of EngagementEvents.

Events' syntax is not checked by the client, so be sure to follow our documentation about it.

Events are simple maps, but the stream package offers handy helpers to populate such events easily.

// Create the event
event := stream.EngagementEvent{}.
    WithLabel("click").
    WithForeignID("event:1234").
    WithUserData(stream.NewUserData().String("john")).
    WithFeatures(
        stream.NewEventFeature("color", "blue"),
        stream.NewEventFeature("shape", "rectangle"),
    ).
    WithLocation("homepage")

// Track the event(s)
err := analytics.TrackEngagement(event)
if err != nil {
    // ...
}

Tracking impressions

Impression events can be tracked with the TrackImpression method of AnalyticsClient (syntax docs):

// Create the impression events
imp := stream.ImpressionEventData{}.
    WithForeignIDs("product:1", "product:2", "product:3").
    WithUserData(stream.NewUserData().String("john")).
    WithLocation("storepage")

// Track the events
err := analytics.TrackImpression(imp)
if err != nil {
    // ...
}

Email tracking

You can generate URLs to track events and redirect to a specific URL with the RedirectAndTrack method of AnalyticsClient (syntax docs). It accepts any number of engagement and impression events:

// Create the events
engagement := stream.EngagementEvent{}.
    WithLabel("click").
    WithForeignID("event:1234").
    WithUserData(stream.NewUserData().String("john")).
    WithFeatures(
        stream.NewEventFeature("color", "blue"),
        stream.NewEventFeature("shape", "rectangle"),
    ).
    WithLocation("homepage")

impressions := stream.ImpressionEventData{}.
    WithForeignIDs("product:1", "product:2", "product:3").
    WithUserData(stream.NewUserData().String("john")).
    WithLocation("storepage")

// Generate the tracking and redirect URL, which once followed
// will redirect the user to the targetURL.
targetURL := "https://google.com"
url, err := analytics.RedirectAndTrack(targetURL, engagement, impression)
if err != nil {
    // ...
}

// Display the obtained url where needed.

Personalization

Personalization endpoints for enabled apps can be reached using a PersonalizationClient, a specialized client obtained with the Personalization() function of a regular Client.

personalization := client.Personalization()

The PersonalizationClient exposes three functions that you can use to retrieve and manipulate data: Get, Post, and Delete.

For example, to retrieve follow recommendations:

// Get follow recommendations
data := map[string]interface{}{
    "user_id":          123,
    "source_feed_slug": "timeline",
    "target_feed_slug": "user",
}
resp, err = personalization.Get("follow_recommendations", data)
if err != nil {
    // ...
}
fmt.Println(resp)

See the complete docs and examples about personalization features on Stream's documentation pages.

Collections

Collections endpoints can be reached using a specialized CollectionsClient which, like PersonalizationClient, can be obtained from a regular Client:

collections := client.Collections()

CollectionsClient exposes three batch functions, Upsert, Select, and DeleteMany as well as CRUD functions: Add, Get, Update, Delete:

// Upsert the "picture" collection
object := stream.CollectionObject{
    ID:   "123",
    Data: map[string]interface{}{
        "name": "Rocky Mountains",
        "location": "North America",
    },
}
err = collections.Upsert("picture", object)
if err != nil {
    // ...
}

// Get the data from the "picture" collection for ID "123" and "456"
objects, err := collections.Select("picture", "123", "456")
if err != nil {
    // ...
}

// Delete the data from the "picture" collection for picture with ID "123"
err = collections.Delete("picture", "123")
if err != nil {
    // ...
}

// Get a single collection object from the "pictures" collection with ID "123"
err = collections.Delete("pictures", "123")
if err != nil {
    // ...
}

See the complete docs and examples about collections on Stream's documentation pages.

Users

Users endpoints can be reached using a specialized UsersClient which, like CollectionsClient, can be obtained from a regular Client:

users := client.Users()

UsersClient exposes CRUD functions: Add, Get, Update, Delete:

user := stream.User{
    ID: "123",
    Data: map[string]interface{}{
        "name": "Bobby Tables",
    },
}

insertedUser, err := users.Add(user, false)
if err != nil {
    // ...
}

newUserData :=map[string]interface{}{
    "name": "Bobby Tables",
    "age": 7,
}

updatedUser, err := users.Update("123", newUserData)
if err != nil {
    // ...
}

err = users.Delete("123")
if err != nil {
    // ...
}

See the complete docs and examples about users on Stream's documentation pages.

Reactions

Reactions endpoints can be reached using a specialized Reactions which, like CollectionsClient, can be obtained from a regular Client:

reactions := client.Reactions()

Reactions exposes CRUD functions: Add, Get, Update, Delete, as well as two specialized functions AddChild and Filter:

r := stream.AddReactionRequestObject{
    Kind: "comment",
    UserID: "123",
    ActivityID: "87a9eec0-fd5f-11e8-8080-80013fed2f5b",
    Data: map[string]interface{}{
        "text": "Nice post!!",
    },
    TargetFeeds: []string{"user:bob", "timeline:alice"},
}

comment, err := reactions.Add(r)
if err != nil {
    // ...
}

like := stream.AddReactionRequestObject{
    Kind: "like",
    UserID: "456",
}

childReaction, err := reactions.AddChild(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)
if err != nil {
    // ...
}

for kind, children := range parent.ChildrenReactions {
    //child reactions are grouped by kind
}

//update the target feeds for the `comment` reaction
updatedReaction, err := reactions.Update(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"), stream.WithLimit(5), stream.WithActivityData())
if err != nil {
    // ...
}

//since we requested the activity, it will be present in the response
fmt.Println(response.Activity)

for _, reaction := range response.Results{
    //do something for each reaction
}

//get the next page of reactions
response, err = reactions.GetNextPageFilteredReactions(response)
if err != nil {
    // ...
}

// get all likes by user "123"
response, err = reactions.Filter(stream.ByUserID("123").ByKind("like"))
if err != nil {
    // ...
}

See the complete docs and examples about reactions on Stream's documentation pages.

Enrichment

Enrichment is a way of retrieving activities from feeds in which references to Users and Collections will be replaced with the corresponding objects

FlatFeed, AggregatedFeed and NotificationFeed each have a GetEnrichedActivities function to retrieve enriched activities.

u := stream.User{
    ID: "123",
    Data: map[string]interface{}{
        "name": "Bobby Tables",
    },
}

//We add a user
user, err := client.Users().Add(u, true)
if err != nil {
    // ...
}

c := stream.CollectionObject{
    ID: "123",
    Data: map[string]interface{}{
        "name":     "Rocky Mountains",
        "location": "North America",
    },
}

//We add a colection object
collectionObject, err := client.Collections().Add("picture", c)
if err != nil {
    // ...
}

act := stream.Activity{
    Time:      stream.Time{Time: time.Now()},
    Actor:     client.Users().CreateReference(user.ID),
    Verb:      "post",
    Object:    client.Collections().CreateReference("picture", collectionObject.ID),
    ForeignID: "picture:1",
}


//We add the activity to the user's feed
feed := client.FlatFeed("user", "123")
_, err = feed.AddActivity(act)
if err != nil {
    // ...
}

result, err := feed.GetActivities()
if err != nil {
    // ...
}
fmt.Println(result.Results[0].Actor) // Will ouput the user reference
fmt.Println(result.Results[0].Object) // Will ouput the collection reference

enrichedResult, err := feed.GetEnrichedActivities()
if err != nil {
    // ...
}
fmt.Println(enrichedResult.Results[0]["actor"].(map[string]interface{})) // Will output the user object
fmt.Println(enrichedResult.Results[0]["object"].(map[string]interface{})) // Will output the collection object

See the complete docs and examples about enrichment on Stream's documentation pages.

License

stream-go2 is licensed under the GNU General Public License v3.0.

Permissions of this strong copyleft license are conditioned on making available complete source code of licensed works and modifications, which include larger works using a licensed work, under the same license. Copyright and license notices must be preserved. Contributors provide an express grant of patent rights.

See the LICENSE file.

We're hiring!

Would you like to work on cool projects like this?

We are currently hiring for talented Gophers in Amsterdam and Boulder, get in touch with us if you are interested! tommaso@getstream.io