Code Monkey home page Code Monkey logo

stream-node-orm's Introduction

Stream Node.js

Build Status npm version Coverage Status

NPM

stream-node-orm is a Node.js (Mongoose & Waterline) client for Stream.

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

Note there is also a lower level Node.js - Stream integration library which is suitable for all JavaScript applications.

Build Activity Streams & News Feeds

Examples of what you can build

You can build:

  • Activity streams such as those seen on Github
  • A Twitter style newsfeed
  • A feed like Instagram or Pinterest
  • Facebook style newsfeeds
  • A notification system

Supported ORMs

Stream node currently supports:

  • Mongoose (full support, both serialization and enrichment)
  • Waterline (partial support, enrichment only)

Demo

You can check out our example app on Github https://github.com/GetStream/Stream-Example-Nodejs

Installation

Step 1 - NPM

Install getstream_node package with npm:

npm install getstream-node --save

Step 2 - Config file

Copy getstream.js config file from node_modules/getstream-node into the root directory of your application Make sure you require the getstream-node early on in your application (eg. in app.js)

Step 3 - Get your API key

Login with Github on getstream.io and edit the configuration values for apiKey, apiSecret and apiAppId in your getstream.js file (you can find them in the dashboard).

Model integration

Stream Node.js can automatically publish new activities to your feeds. To do that you only need to register the models you want to publish with this library.

var stream = require('getstream-node');

var tweetSchema = Schema({
  text    : String,
  user   : { type: Schema.Types.ObjectId, ref: 'User' }
});

tweetSchema.plugin(stream.mongoose.activity);

// register your mongoose connection with the library
stream.mongoose.setupMongoose(mongoose);

Every time a Tweet is created it will be added to the user's feed. Users which follow the given user will also automatically get the new tweet in their feeds.

Activity Fields

Models are stored in feeds as activities. An activity is composed of at least the following fields: actor, verb, object, time. You can also add more custom data if needed. The Activity mixin will try to set things up automatically:

object is a reference to the model instance actor is a reference to the user attribute of the instance verb is a string representation of the class name

By default the actor field will look for an attribute called user or actor and a field called created_at to track creation time. If your user field is called differently you'll need to tell us where to look for it. Below shows an example how to set things up if your user field is called author.

var tweetSchema = Schema({
  text    : String,
  author   : { type: Schema.Types.ObjectId, ref: 'User' }
});

tweetSchema.plugin(stream.mongoose.activity);

tweetSchema.methods.activityActorProp = function() {
  return 'author';
}

Customizing the Activity

Sometimes you'll want full control over the activity that's send to getstream.io. To do that you can overwrite the default createActivity method on the model

tweetSchema.methods.createActivity = function() {
	// this is the default createActivity code, customize as you see fit.
      var activity = {};
      var extra_data = this.activityExtraData();
      for (var key in extra_data) {
          activity[key] = extra_data[key];
      }
      activity.to = (this.activityNotify() || []).map(function(x){return x.id});
      activity.actor = this.activityActor();
      activity.verb = this.activityVerb();
      activity.object = this.activityObject();
      activity.foreign_id = this.activityForeignId();
      if (this.activityTime()) {
          activity.time = this.activityTime();
      }
      return activity;
  }

Feed Manager

This packages comes with a feed_manager class that helps with all common feed operations.

Feeds Bundled with feed_manager

To get you started the manager has 4 feeds pre-configured. You can add more feeds if your application needs it. The three feeds are divided in three categories.

User Feed:

The user feed stores all activities for a user. Think of it as your personal Facebook page. You can easily get this feed from the manager.

FeedManager.getUserFeed(req.user.id);
News Feeds:

The news feeds store the activities from the people you follow. There is both a flat newsfeed (similar to twitter) and an aggregated newsfeed (like facebook).

var flatFeed = FeedManager.getNewsFeeds(foundUser._id)['timeline_flat'];
var aggregatedFeed = FeedManager.getNewsFeeds(req.user.id)['timeline_aggregated'];
Notification Feed:

The notification feed can be used to build notification functionality.

Notification feed

Below we show an example of how you can read the notification feed.

var notificationFeed = FeedManager.getNotificationFeed(req.user.id);

By default the notification feed will be empty. You can specify which users to notify when your model gets created. In the case of a retweet you probably want to notify the user of the parent tweet.

tweetSchema.methods.activityNotify = function() {
  if (this.isRetweet) {
	  target_feed = FeedManager.getNotificationFeed(this.parent.author.id);
	  return [target_feed];
  }
};

Another example would be following a user. You would commonly want to notify the user which is being followed.

followSchema.methods.activityNotify = function() {
  target_feed = FeedManager.getNotificationFeed(this.target._id);
  return [target_feed];
};

Follow a Feed

To follow the created newsfeeds you need to notify the system about follow relationships. The manager comes with APIs to let a user's news feeds follow another user's feed. This code lets the current user's flat and aggregated feeds follow the target_user's personal feed.

FeedManager.followUser(userId, targetId);

Showing the Newsfeed

Activity Enrichment

When you read data from feeds, a like activity will look like this:

{'actor': 'User:1', 'verb': 'like', 'object': 'Like:42'}

This is far from ready for usage in your template. We call the process of loading the references from the database enrichment. An example is shown below:

router.get('/flat', ensureAuthenticated, function(req, res, next){
    var flatFeed = FeedManager.getNewsFeeds(req.user.id)['timeline_flat'];

    flatFeed.get({})
    	.then(function (body) {
        	var activities = body.results;
        	return StreamBackend.enrichActivities(activities);
        })
        .then(function (enrichedActivities) {
            return res.render('feed', {location: 'feed', user: req.user, activities: enrichedActivities, path: req.url});
        })
        .catch(next)
    });
});

Promises are used to pipe the asynchronous result of flatFeed.get and StreamBackend.enrichActivities through our code.

Temporarily Disabling the Model Sync

Model synchronization can be disabled manually via environment variable.

NODE_ENV=test npm test

Automatically Populate Paths:

You can automatically populate paths during enrichment via the pathsToPopulate static.

tweetSchema.statics.pathsToPopulate = function() {
  return ['link'];
};

Full Documentation and Low Level APIs Access

When needed you can also use the low level JavaScript API directly. Documentation is available at the Stream website.

var streamNode = require('getstream-node');
var client = streamNode.FeedManager.client
// client.addActivity, client.removeActivity etc are all available

Enrichment

You can use the enrichment capabilities of this library directly.

var streamNode = require('getstream-node');
var streamMongoose = new streamNode.MongooseBackend()
// or
var streamWaterline = new streamNode.WaterlineBackend()
// now enrich the activities
streamWaterline.enrichActivities(activities).then(function(enrichedActivities) {
	res.json({'results': enrichedActivities})
}).catch(function(err) {
	sails.log.error('enrichment failed', err)
	return res.serverError('failed to load articles in the feed')
})

Customizing Enrichment (since 1.4.0)

By default the enrichment system assumes that you're referencing items by their id. Sometimes you'll want to customize this behavior. You might for instance use a username instead of an id. Alternatively you might mant to use a caching layer instead of the ORM for loading the data. The example below shows how to customize the lookup for all User entries.

// subclass streamMongoose
function streamCustomEnrichment() {};
streamCustomEnrichment.prototype = {
    loadUserFromStorage: function(modelClass, objectsIds, callback) {
        var found = {};
        var paths = [];
        if (typeof(modelClass.pathsToPopulate) === 'function') {
            var paths = modelClass.pathsToPopulate();
        }
	// Here's the magic, use a username instead of id
        modelClass.find({
            username: {
                $in: objectsIds
            }
        }).populate(paths).exec(function(err, docs) {
            for (var i in docs) {
                found[docs[i]._id] = docs[i];
            }
            callback(err, found);
        });
    }
}
util.inherits(streamCustomEnrichment, streamNode.mongoose.Backend);

Contributing

Running tests:

npm test

Releasing

Make sure your working directory is clean and run:

npm install
npm version [ major | minor | patch ]
npm publish

Supported Node.js Versions

v10.x
v12.x
v14.x

Copyright and License Information

Copyright (c) 2015-2017 Stream.io Inc, and individual contributors. All rights reserved.

See the file "LICENSE" for information on the history of this software, terms & conditions for usage, and a DISCLAIMER OF ALL WARRANTIES.

stream-node-orm's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

stream-node-orm's Issues

Errors unhandled in ORM

Another dev I'm working with ended up hunting for half-an-hour looking for a reason why an activity wasn't triggering as expected, turns out there was a typo in the field.

However, definitely something avoidable if errors were accessible from the mongoose hooks. Right now they end up unhandled:

doc.populate(paths, function(err, docP) {
if (docP.wasNew) {
stream.FeedManager.activityCreated(docP);
}
});

TypeScript / TypeORM Support

Hi guys, I see some issue stalled talking about mongoose, but I want to actually use stream with my TypeORM project, using TypeScript.

Should this be doable? Is there any shot at stream/typescript/typeorm I'm not aware of?

newsFeeds[slug].follow is not a function

I've been following the Node.js example and I keep getting
newsFeeds[slug].follow is not a function
and
newsFeeds[slug].unfollow is not a function,
while providing all the correct data.

I looked into the module's FeedManager.js file, as the error was being generated there and swapped the enhanced loop
for (var slug in newsFeeds) {
on lines 52 and 64 to the regular
for(var slug=0; slug< newsFeeds.length; slug++){
and it worked perfectly.

Do you know why might this be happening and is there a way to fix this without making a custom module?

stream is not defined error

Hi,

I am getting the following error

ReferenceError: stream is not defined at F:\mightybolder\boldersocial\node_modules\getstream-node\src\backends\mongoose.js:100:9 at F:\mightybolder\boldersocial\node_modules\mongoose\lib\model.js:3369:16 at next (F:\mightybolder\boldersocial\node_modules\mongoose\lib\model.js:2621:5) at populate (F:\mightybolder\boldersocial\node_modules\mongoose\lib\model.js:2709:12) at _populate (F:\mightybolder\boldersocial\node_modules\mongoose\lib\model.js:2611:5) at Function.Model.populate (F:\mightybolder\boldersocial\node_modules\mongoose\lib\model.js:2571:5) at model.populate (F:\mightybolder\boldersocial\node_modules\mongoose\lib\document.js:2449:22) at model.<anonymous> (F:\mightybolder\boldersocial\node_modules\getstream-node\src\backends\mongoose.js:98:9) at EventEmitter.<anonymous> (F:\mightybolder\boldersocial\node_modules\mongoose\lib\schema.js:1084:17) at emitTwo (events.js:106:13) at EventEmitter.emit (events.js:191:7)

In recent commit it seems to be that stream import is deleted from src\backends\mongoose.js.
Please help, thanks in advance

Ability to restrict attributes when enriching activities

Problem: Not all attributes are needed when enriching activities. In some cases, it's desirable to hide values (e.g. e-mail address, hashed passwords, other secrets). Calling StreamBackend.enrichActivities appears to return all attributes for each document found.

It would be great to be able to define a projection, or some other spec to define which attributes will be included or excluded on a per-model/schema basis.

It seems that this is a common use-case. Suggestions/ideas on solutions to this problem would be much appreciated.

Thanks in advance!

Package still use getstream 5.0.0

Hello,
It would be nice to upgrade the dependency for the last version of getstream.
For example: I am not able to use client.feed('user', 'me').followStats() directly with the low level APIs access by using: streamNode.FeedManager.client.

Support Async Configuration for Managed Secrets

When using hosted managed secrets (ie. Google Secret Manager), it is impossible to pass async configuration into the automatically instantiated version of FeedManager. This can be achieved with the feedManagerFactory, however module.exports.FeedManager is still created, and will fail without getstream.js configuration present.

API keys not accessible from getstream.js file

I've copied the getstream.js file into the root directory and filled in all the API keys info, but seems like the module cannot access this data.

I've also tried creating a getstream.json as instructed here and adding the path to STREAM_NODE_CONFIG_DIR in the .env, but that doesn't work either.

Only way to make the module accept the keys was to enter them directly in config.default.js.

Is my configuration somehow incorrect? Please let me know what can be done to fix this.

Cannot find module 'mongoose'

I am trying to run this according this docs:
https://getstream.io/docs/js/#enrichment

I am getting this error:

Error: Cannot find module 'mongoose'
    at Function.Module._resolveFilename (module.js:455:15)
    at Function.Module._load (module.js:403:25)
    at Module.require (module.js:483:17)
    at require (internal/module.js:20:19)
    at Object.<anonymous> (/Users/alexprice/code/private/opt-in/node_modules/getstream-node/src/backends/mongoose.js:4:16)
    at Module._compile (module.js:556:32)
    at Object.Module._extensions..js (module.js:565:10)
    at Module.load (module.js:473:32)
    at tryModuleLoad (module.js:432:12)
    at Function.Module._load (module.js:424:3)
    at Module.require (module.js:483:17)
    at require (internal/module.js:20:19)
    at Object.<anonymous> (/Users/alexprice/code/private/opt-in/node_modules/getstream-node/src/index.js:5:16)

Initiate FeedManager with a configuration object

To make the module more flexible, would it be an idea to instantiate the Feedmanager with an object? Instead of a file in the application's root?
In my case I have a settings file ./config/settings.js which stores all application environment variables (including the stream keys).
And I have a ./config/getstream.js with:

var settings = require('./settings');
module.exports = {
  apiKey: settings.getStream.key,
  apiSecret: settings.getStream.secret,
  apiAppId: settings.getStream.appId,
  apiLocation: '',
  userFeed: 'user',
  notificationFeed: 'notification',
  newsFeeds: {
    flat: 'timeline',
    aggregated: 'timeline_aggregated'
  }
};

I then initiate the feedmanager with

var streamConfig = require('../../config/getStream');
var FeedManager = stream_node.FeedManager(streamConfig);

Node 10?

Hi! Trying to test getstream. However, I can't install it due to:

The engine "node" is incompatible with this module. Expected version ">=4.8 <=9".

Actor prop author not found on model instance

here's my code
`export const ArticleSchema = new Schema(
{
slug: {type: String, lowercase: true, unique: true},
title: {
type: String,
required: true,
},
body: {
type: String,
required: true,
},
description: {
type: String,
},
likes: {type: Number, default: 0},
tagList: [{ type: String}],
author:{ type: mongoose.Schema.Types.ObjectId, ref: 'User',
autopopulate:true
},
comments: [{
body:{ type: String, maxlength: 280},
commenter: { type: mongoose.Schema.Types.ObjectId, ref: "User", autopopulate:true},
commentedAt:Date,
replies:[{
reply:{ type: String, default: "", maxlength: 280},
replyAuthor:{type:mongoose.Schema.Types.ObjectId, ref:'User'}
}]
}],
}
);
ArticleSchema.plugin(stream.mongoose.activity);

ArticleSchema.methods.activityActorProp = function() {
return 'author';
}`

mocha test/backends fails

removed it from the test flow for now. let's review what those tests were supposed to do and if we need them.

Custom config file directory is not working

The code below is the one that allows someone to specifiy a different directory for the getstream.js config file"

if (typeof process != 'undefined' && process.env.STREAM_NODE_CONFIG_DIR) {
		config_file = process.env.STREAM_NODE_CONFIG_DIR + '/getstream.js';
	} else {
		config_file = process.cwd() + '/getstream.js';
	}

The main problem is that, in case we specify a value for STREAM_NODE_CONFIG_DIR this will always be some path relative to the project. Therefore, we must append the process.cwd() to the url.

Moreover, I'm working now with Node v14 and, because of compatibility things, I need to specifiy getstream.js with the .cjs extension. But this is not possible with the current setup because stream-node-orm is forcing us to use a file named getstream.js.

My proposal is just to use a STREAM_NODE_CONFIG_PATH variable that would be path to a file and not a directory path.

TypeError: instance.createActivity is not a function

Hey,

I'm getting the following error. I have no clue why.
Any input would be greatly appreciated.

(node:283) [MONGODB DRIVER] Warning: Top-level use of w, wtimeout, j, and fsync is deprecated. Use writeConcern instead.
events.js:377
      throw er; // Unhandled 'error' event
      ^
TypeError: instance.createActivity is not a function
    at FeedManager.activityCreated (/app/node_modules/getstream-node/src/FeedManager.js:78:28)
    at /app/node_modules/getstream-node/src/backends/mongoose.js:106:24
    at /app/node_modules/mongoose/lib/model.js:5074:18
    at processTicksAndRejections (internal/process/task_queues.js:77:11)
Emitted 'error' event on Function instance at:
    at /app/node_modules/mongoose/lib/model.js:5076:15
    at processTicksAndRejections (internal/process/task_queues.js:77:11)

Follow are my model and app.js files

/* model.js */
import mongoose, { Schema } from 'mongoose'
import stream from 'getstream-node'

var FeedManager = stream.FeedManager;

const favouriteSchema = new Schema({
  user: {
    type: Schema.ObjectId,
    ref: 'User',
    required: true
  },
  product: {
    type: Schema.ObjectId,
    ref: 'Product',
    required: true
  }
}, {
  timestamps: true,
  toJSON: {
    virtuals: true,
    transform: (obj, ret) => { delete ret._id }
  }
})

favouriteSchema.plugin(stream.mongoose.activity)

const model = mongoose.model('Favourite', favouriteSchema)

export const schema = model.schema
export default model
/* app.js */
import http from 'http'
import { env, mongo, port, ip, apiRoot } from './config'
import mongoose from './services/mongoose'
import express from './services/express'
import api from './api'
import stream from 'getstream-node'

const app = express(apiRoot, api)
const server = http.createServer(app)

if (mongo.uri) {
  mongoose.connect(mongo.uri)
}
mongoose.Promise = Promise



setImmediate(() => {
  server.listen(port, ip, () => {
    console.log('Express server listening on http://%s:%d, in %s mode', ip, port, env)
  })
});

stream.mongoose.setupMongoose(mongoose);

export default app

require('getstream-node') throws exception

I have a one-line test.js file:

require('getstream-node');

When running it with node test.js, I get this exception: Error: secretOrPrivateKey must have a value, requiring the getstream.js file and mongoose before doesn't help.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.