<commit_before>var URL = require('url');
var queue = require('queue-async');
var typeis = require('type-is').is;
var agent = require('../utils/agent');
var debug = require('debug')('raja:dom');
var CacheDebounce = require('cache-debounce');

module.exports = DomProxy;

function DomProxy(raja, opts) {
	if (!(this instanceof DomProxy)) return new DomProxy(raja, opts);
	this.raja = raja;
	var Dom = this.Dom = opts.dom;

	var ioreg;
	var iopath = raja.io && raja.io.socket && raja.io.socket.io.opts.path;
	if (iopath) ioreg = new RegExp("^" + iopath);

	Dom.settings.allow = "all";
	Dom.settings.verbose = true;

	var self = this;

	Dom.use(domRajaMeta, 'before');

	if (opts.disable) return;

	Dom.Handler.prototype.init = function() {
		this.opts.params = stealSession;
		this.listener = listener.bind(this);
		raja.on('message', this.listener);
	};

	var _finish = Dom.Handler.prototype.finish;
	Dom.Handler.prototype.finish = function(user, res) {
		res.set('X-Raja', opts.namespace);
		for (var name in user.headers) {
			if (name in {'Vary': 1, 'X-Grant': 1, 'ETag': 1}) {
				res.set(name, user.headers[name]);
			}
		}
		_finish.call(this, user, res);
	};

	Dom.Handler.prototype.loadLocal = raja.proxies.local.get.bind(raja.proxies.local);

	Dom.Handler.prototype.loadRemote = function(url, opts, cb) {
		raja.proxies.remote.get(url, opts, function(err, res) {
			cb(err, res && res.data);
		});
	};

	var _buildAuthored = Dom.Handler.prototype.buildAuthored;
	Dom.Handler.prototype.buildAuthored = CacheDebounce(function(resource, view, url, req, res, cb) {
		_buildAuthored.call(this, resource, view, url, req, res, cb);
	}, function(resource) {
		return resource.key;
	});

	var _buildUsed = Dom.Handler.prototype.buildUsed;
	Dom.Handler.prototype.buildUsed = CacheDebounce(function(resource, author, url, req, res, cb) {
		_buildUsed.call(this, resource, author, url, req, res, cb);
	}, function(resource) {
		return resource.key;
	});

	Dom.author(domResponsePlugin, 'before');
	Dom.use(domResponsePlugin, 'before');
	Dom.use(domUserPlugin, 'before');
	Dom.use(domDonePlugin, 'after');

	function listener(msg) {
		var h = this;
		var reload = false;
		var lastKey = msg.parents.slice(-1).pop();
		msg.parents.forEach(function(key) {
			raja.store.get(key, function(err, resource) {
				if (err) return raja.error(err);
				if (!resource) return;
				if (msg.key == key && msg.mtime > resource.mtime && (!resource.itime || msg.mtime > resource.itime)) {
					resource.itime = msg.mtime;
				}
				var contentType = resource.headers && resource.headers['Content-Type'];
				if (lastKey != resource.key && contentType && (typeis(contentType, '*/javascript') || typeis(contentType, 'text/html'))) {
					debug("Detected change in", key);
					reload = true;
				}
				if (reload && resource.page && !resource.page.locked) { // avoid multiple releases at once
					debug("Detected change in page dependencies, releasing it", resource.key);
					Dom.pool.release(resource.page);
				}
			});
		});
	}

	function domRajaMeta(page, resource, req, res) {
		page.when('ready', function(cb) {
			this.run(function(key, client, ns, done) {
				var node = document.getElementById('script');
				if (!node) node = document.createElement('script');
				node.id = 'raja';
				node.setAttribute('data-client', client);
				node.setAttribute('data-namespace', ns);
				node.setAttribute('data-room', key);
				node.type = "text/javascript";
				node.textContent = "(function(r) {if (r && r.ready) r.ready();})(window.raja);";
				if (!node.parentNode) {
					var tn = document.createTextNode("\n");
					document.head.insertBefore(node, document.head.firstChild);
					document.head.insertBefore(tn, node);
				}
				done();
			}, resource.key, opts.client, opts.namespace, cb);
		});
	}

	function domResponsePlugin(page, resource) {
		var host = URL.parse(resource.url).host;
		page.on('response', function(res) {
			if (!res.uri || res.uri == "about:blank") return;
			if (res.uri == resource.url) return; // ignore self
			if (!resource.resources) return;
			if (res.status < 200 || res.status >= 400) return;
			var uriObj = URL.parse(res.uri);
			if (ioreg && ioreg.test(uriObj.pathname)) {
				if (typeis(res.mime, 'javascript') == false) resource.live = true;
				return;
			}
			var headers = raja.headers(res);
			if (host == uriObj.host) {
				headers['X-Raja'] = raja.opts.namespace;
			}
			resource.depend(raja.create(res.uri, headers));
			// this covers the case where a resource finishes loading after idle
			if (resource.valid) {
				resource.itime = new Date();
				resource.valid = false;
				resource.save();
			}
		});
	}

	function domUserPlugin(page, resource) {
		var host = URL.parse(resource.url).host;
		page.filters.push([function(pool, ioreg) {
			var uriObj = new URL(this.uri);
			if (pool.some(function(obj) {
				if (obj.host == uriObj.host) return true;
			})) {
				if (ioreg && ioreg.test(uriObj.pathname)) {
					// ignore io request
					this.ignore = true;
				}
			} else if (uriObj.host != document.location.host) {
				// reject foreign hosts
				this.cancel = true;
			} else {
				this.headers['Cache-Control'] = 'no-cache';
			}
		}, raja.client && raja.client.pool || [], ioreg]);
		page.on('busy', function() {
			if (resource.itime && resource.itime > resource.mtime) {
				debug("busy page invalidates resource", resource.key);
				delete resource.itime;
				// update resources to make sure instances loaded on server receive messages
				resource.valid = false;
				resource.save();
			}
		});
	}

	function domDonePlugin(page, resource, req, res) {
		var output = resource.output;
		resource.output = function(page, cb) {
			resource.output = output;
			if (!resource.itime || resource.itime <= resource.mtime) {
				resource.valid = true;
				resource.mtime = new Date();
			} else if (!resource.mtime) {
				resource.mtime = new Date();
			}
			output.call(resource, page, cb);
		};
	}

	var _get = Dom.Handler.prototype.get;
	Dom.Handler.prototype.get = function(url, depend, havingHeaders, cb) {
		if (!cb) {
			cb = havingHeaders;
			havingHeaders = null;
		}
		if (!cb) {
			cb = depend;
			depend = null;
		}
		var obj = {url: url, builder: 'dom'};
		if (havingHeaders) obj.headers = raja.headers(havingHeaders);
		else obj.headers = {};
		obj.headers['X-Raja'] = opts.namespace;
		debug('get', obj);
		raja.upsert(obj, function(err, resource) {
			if (err) return cb(err);
			if (!resource.headers) resource.headers = {};
			if (!resource.resources) resource.resources = {};
			if (depend) resource.depend(depend);
			if (!resource.output) resource.output = function(page, cb) {
				if (!page) return cb(new Error("resource.page was unset before output of\n" + resource.key));
				page.when('idle', function(wcb) {
					this.html(function(err, str) {
						cb(err, str);
						wcb();
					});
				});
			};
			cb(null, resource);
		});
	};
}

function mergeHeader(headers, name, val) {
	var header = headers[name];
	header = header ? header.split(',') : [];
	if (header.indexOf(val) < 0) header.push(val);
	headers[name] = header.join(',');
}

function stealSession(opts, req) {
	var session = req.session;
	var store = req.sessionStore;
	if (!store) return;
	// 1) generate a new session
	var decoy = {sessionStore: store};
	store.generate(decoy);
	// 2) give it same data
	for (var key in session) {
		if (decoy.session[key] != null || key == 'passport') continue;
		decoy.session[key] = session[key];
	}
	decoy.session.save();
	debug("hosted session", decoy.sessionID, decoy.session);
	// 3) set that session cookie into opts.cookies
	opts.cookies = agent.sessionCookie(req, decoy.sessionID, true);
}

<commit_msg>Update page ping for express 3.1.3<commit_after>var URL = require('url');
var queue = require('queue-async');
var typeis = require('type-is').is;
var agent = require('../utils/agent');
var debug = require('debug')('raja:dom');
var CacheDebounce = require('cache-debounce');

module.exports = DomProxy;

function DomProxy(raja, opts) {
	if (!(this instanceof DomProxy)) return new DomProxy(raja, opts);
	this.raja = raja;
	var Dom = this.Dom = opts.dom;

	var ioreg;
	var iopath = raja.io && raja.io.socket && raja.io.socket.io.opts.path;
	if (iopath) ioreg = new RegExp("^" + iopath);

	Dom.settings.allow = "all";
	Dom.settings.verbose = true;

	var self = this;

	Dom.use(domRajaMeta, 'before');

	if (opts.disable) return;

	Dom.Handler.prototype.init = function() {
		this.opts.params = stealSession;
		this.listener = listener.bind(this);
		raja.on('message', this.listener);
	};

	var _finish = Dom.Handler.prototype.finish;
	Dom.Handler.prototype.finish = function(user, res) {
		res.set('X-Raja', opts.namespace);
		for (var name in user.headers) {
			if (name in {'Vary': 1, 'X-Grant': 1, 'ETag': 1}) {
				res.set(name, user.headers[name]);
			}
		}
		_finish.call(this, user, res);
	};

	Dom.Handler.prototype.loadLocal = raja.proxies.local.get.bind(raja.proxies.local);

	Dom.Handler.prototype.loadRemote = function(url, opts, cb) {
		raja.proxies.remote.get(url, opts, function(err, res) {
			cb(err, res && res.data);
		});
	};

	var _buildAuthored = Dom.Handler.prototype.buildAuthored;
	Dom.Handler.prototype.buildAuthored = CacheDebounce(function(resource, view, url, req, res, cb) {
		_buildAuthored.call(this, resource, view, url, req, res, cb);
	}, function(resource) {
		return resource.key;
	});

	var _buildUsed = Dom.Handler.prototype.buildUsed;
	Dom.Handler.prototype.buildUsed = CacheDebounce(function(resource, author, url, req, res, cb) {
		_buildUsed.call(this, resource, author, url, req, res, cb);
	}, function(resource) {
		return resource.key;
	});

	Dom.author(domResponsePlugin, 'before');
	Dom.use(domResponsePlugin, 'before');
	Dom.use(domUserPlugin, 'before');
	Dom.use(domDonePlugin, 'after');

	function listener(msg) {
		var h = this;
		var reload = false;
		var lastKey = msg.parents.slice(-1).pop();
		msg.parents.forEach(function(key) {
			raja.store.get(key, function(err, resource) {
				if (err) return raja.error(err);
				if (!resource) return;
				if (msg.key == key && msg.mtime > resource.mtime && (!resource.itime || msg.mtime > resource.itime)) {
					resource.itime = msg.mtime;
					if (resource.page) resource.page.pingTime = Date.now();
				}
				var contentType = resource.headers && resource.headers['Content-Type'];
				if (lastKey != resource.key && contentType && (typeis(contentType, '*/javascript') || typeis(contentType, 'text/html'))) {
					debug("Detected change in", key);
					reload = true;
				}
				if (reload && resource.page && !resource.page.locked) { // avoid multiple releases at once
					debug("Detected change in page dependencies, releasing it", resource.key);
					Dom.pool.release(resource.page);
				}
			});
		});
	}

	function domRajaMeta(page, resource, req, res) {
		page.when('ready', function(cb) {
			this.run(function(key, client, ns, done) {
				var node = document.getElementById('script');
				if (!node) node = document.createElement('script');
				node.id = 'raja';
				node.setAttribute('data-client', client);
				node.setAttribute('data-namespace', ns);
				node.setAttribute('data-room', key);
				node.type = "text/javascript";
				node.textContent = "(function(r) {if (r && r.ready) r.ready();})(window.raja);";
				if (!node.parentNode) {
					var tn = document.createTextNode("\n");
					document.head.insertBefore(node, document.head.firstChild);
					document.head.insertBefore(tn, node);
				}
				done();
			}, resource.key, opts.client, opts.namespace, cb);
		});
	}

	function domResponsePlugin(page, resource) {
		var host = URL.parse(resource.url).host;
		page.on('response', function(res) {
			if (!res.uri || res.uri == "about:blank") return;
			if (res.uri == resource.url) return; // ignore self
			if (!resource.resources) return;
			if (res.status < 200 || res.status >= 400) return;
			var uriObj = URL.parse(res.uri);
			if (ioreg && ioreg.test(uriObj.pathname)) {
				if (typeis(res.mime, 'javascript') == false) resource.live = true;
				return;
			}
			var headers = raja.headers(res);
			if (host == uriObj.host) {
				headers['X-Raja'] = raja.opts.namespace;
			}
			resource.depend(raja.create(res.uri, headers));
			// this covers the case where a resource finishes loading after idle
			if (resource.valid) {
				resource.itime = new Date();
				resource.valid = false;
				resource.save();
			}
		});
	}

	function domUserPlugin(page, resource) {
		var host = URL.parse(resource.url).host;
		page.filters.push([function(pool, ioreg) {
			var uriObj = new URL(this.uri);
			if (pool.some(function(obj) {
				if (obj.host == uriObj.host) return true;
			})) {
				if (ioreg && ioreg.test(uriObj.pathname)) {
					// ignore io request
					this.ignore = true;
				}
			} else if (uriObj.host != document.location.host) {
				// reject foreign hosts
				this.cancel = true;
			} else {
				this.headers['Cache-Control'] = 'no-cache';
			}
		}, raja.client && raja.client.pool || [], ioreg]);
		page.on('busy', function() {
			if (resource.itime && resource.itime > resource.mtime) {
				debug("busy page invalidates resource", resource.key);
				delete resource.itime;
				// update resources to make sure instances loaded on server receive messages
				resource.valid = false;
				resource.save();
			}
		});
	}

	function domDonePlugin(page, resource, req, res) {
		var output = resource.output;
		resource.output = function(page, cb) {
			resource.output = output;
			if (!resource.itime || resource.itime <= resource.mtime) {
				resource.valid = true;
				resource.mtime = new Date();
			} else if (!resource.mtime) {
				resource.mtime = new Date();
			}
			output.call(resource, page, cb);
		};
	}

	var _get = Dom.Handler.prototype.get;
	Dom.Handler.prototype.get = function(url, depend, havingHeaders, cb) {
		if (!cb) {
			cb = havingHeaders;
			havingHeaders = null;
		}
		if (!cb) {
			cb = depend;
			depend = null;
		}
		var obj = {url: url, builder: 'dom'};
		if (havingHeaders) obj.headers = raja.headers(havingHeaders);
		else obj.headers = {};
		obj.headers['X-Raja'] = opts.namespace;
		debug('get', obj);
		raja.upsert(obj, function(err, resource) {
			if (err) return cb(err);
			if (!resource.headers) resource.headers = {};
			if (!resource.resources) resource.resources = {};
			if (depend) resource.depend(depend);
			if (!resource.output) resource.output = function(page, cb) {
				if (!page) return cb(new Error("resource.page was unset before output of\n" + resource.key));
				page.when('idle', function(wcb) {
					this.html(function(err, str) {
						cb(err, str);
						wcb();
					});
				});
			};
			cb(null, resource);
		});
	};
}

function mergeHeader(headers, name, val) {
	var header = headers[name];
	header = header ? header.split(',') : [];
	if (header.indexOf(val) < 0) header.push(val);
	headers[name] = header.join(',');
}

function stealSession(opts, req) {
	var session = req.session;
	var store = req.sessionStore;
	if (!store) return;
	// 1) generate a new session
	var decoy = {sessionStore: store};
	store.generate(decoy);
	// 2) give it same data
	for (var key in session) {
		if (decoy.session[key] != null || key == 'passport') continue;
		decoy.session[key] = session[key];
	}
	decoy.session.save();
	debug("hosted session", decoy.sessionID, decoy.session);
	// 3) set that session cookie into opts.cookies
	opts.cookies = agent.sessionCookie(req, decoy.sessionID, true);
}

<|endoftext|>


<commit_before>import { Meteor } from 'meteor/meteor'
import { Match, check } from 'meteor/check'
import { _ } from 'meteor/underscore'

import { debugLog } from './logging'
import PublishedDocumentList from './published_document_list'

class Publication {
  constructor (subscription, options, args) {
    check(options, {
      find: Function,
      children: Match.Optional(Match.OneOf([Object], Function)),
      collectionName: Match.Optional(String)
    })

    this.subscription = subscription
    this.options = options
    this.args = args || []
    this.childrenOptions = options.children || []
    this.publishedDocs = new PublishedDocumentList()
    this.collectionName = options.collectionName
  }

  publish () {
    this.cursor = this._getCursor()
    if (!this.cursor) { return }

    const collectionName = this._getCollectionName()

    // Use Meteor.bindEnvironment to make sure the callbacks are run with the same
    // environmentVariables as when publishing the "parent".
    // It's only needed when publish is being recursively run.
    this.observeHandle = this.cursor.observe({
      added: Meteor.bindEnvironment((doc) => {
        const alreadyPublished = this.publishedDocs.has(doc._id)

        if (alreadyPublished) {
          debugLog('Publication.observeHandle.added', `${collectionName}:${doc._id} already published`)
          this.publishedDocs.unflagForRemoval(doc._id)
          this._republishChildrenOf(doc)
          this.subscription.changed(collectionName, doc._id, doc)
        } else {
          this.publishedDocs.add(collectionName, doc._id)
          this._publishChildrenOf(doc)
          this.subscription.added(collectionName, doc)
        }
      }),
      changed: Meteor.bindEnvironment((newDoc) => {
        debugLog('Publication.observeHandle.changed', `${collectionName}:${newDoc._id}`)
        this._republishChildrenOf(newDoc)
      }),
      removed: (doc) => {
        debugLog('Publication.observeHandle.removed', `${collectionName}:${doc._id}`)
        this._removeDoc(collectionName, doc._id)
      }
    })

    this.observeChangesHandle = this.cursor.observeChanges({
      changed: (id, fields) => {
        debugLog('Publication.observeChangesHandle.changed', `${collectionName}:${id}`)
        this.subscription.changed(collectionName, id, fields)
      }
    })
  }

  unpublish () {
    debugLog('Publication.unpublish', this._getCollectionName())
    this._stopObservingCursor()
    this._unpublishAllDocuments()
  }

  _republish () {
    this._stopObservingCursor()

    this.publishedDocs.flagAllForRemoval()

    debugLog('Publication._republish', 'run .publish again')
    this.publish()

    debugLog('Publication._republish', 'unpublish docs from old cursor')
    this._removeFlaggedDocs()
  }

  _getCursor () {
    return this.options.find.apply(this.subscription.meteorSub, this.args)
  }

  _getCollectionName () {
    return this.collectionName || (this.cursor && this.cursor._getCollectionName())
  }

  _publishChildrenOf (doc) {
    const children = _.isFunction(this.childrenOptions)
      ? this.childrenOptions(doc, ...this.args) : this.childrenOptions
    _.each(children, function createChildPublication (options) {
      const pub = new Publication(this.subscription, options, [doc].concat(this.args))
      this.publishedDocs.addChildPub(doc._id, pub)
      pub.publish()
    }, this)
  }

  _republishChildrenOf (doc) {
    this.publishedDocs.eachChildPub(doc._id, (publication) => {
      publication.args[0] = doc
      publication._republish()
    })
  }

  _unpublishAllDocuments () {
    this.publishedDocs.eachDocument((doc) => {
      this._removeDoc(doc.collectionName, doc.docId)
    }, this)
  }

  _stopObservingCursor () {
    debugLog('Publication._stopObservingCursor', 'stop observing cursor')

    if (this.observeHandle) {
      this.observeHandle.stop()
      delete this.observeHandle
    }

    if (this.observeChangesHandle) {
      this.observeChangesHandle.stop()
      delete this.observeChangesHandle
    }
  }

  _removeFlaggedDocs () {
    this.publishedDocs.eachDocument((doc) => {
      if (doc.isFlaggedForRemoval()) {
        this._removeDoc(doc.collectionName, doc.docId)
      }
    }, this)
  }

  _removeDoc (collectionName, docId) {
    this.subscription.removed(collectionName, docId)
    this._unpublishChildrenOf(docId)
    this.publishedDocs.remove(docId)
  }

  _unpublishChildrenOf (docId) {
    debugLog('Publication._unpublishChildrenOf', `unpublishing children of ${this._getCollectionName()}:${docId}`)

    this.publishedDocs.eachChildPub(docId, (publication) => {
      publication.unpublish()
    })
  }
}

export default Publication
<commit_msg>Fix #68 & remove underscore<commit_after>import { Meteor } from 'meteor/meteor'
import { Match, check } from 'meteor/check'

import { debugLog } from './logging'
import PublishedDocumentList from './published_document_list'

class Publication {
  constructor (subscription, options, args) {
    check(options, {
      find: Function,
      children: Match.Optional(Match.OneOf([Object], Function)),
      collectionName: Match.Optional(String)
    })

    this.subscription = subscription
    this.options = options
    this.args = args || []
    this.childrenOptions = options.children || []
    this.publishedDocs = new PublishedDocumentList()
    this.collectionName = options.collectionName
  }

  publish () {
    this.cursor = this._getCursor()
    if (!this.cursor) { return }

    const collectionName = this._getCollectionName()

    // Use Meteor.bindEnvironment to make sure the callbacks are run with the same
    // environmentVariables as when publishing the "parent".
    // It's only needed when publish is being recursively run.
    this.observeHandle = this.cursor.observe({
      added: Meteor.bindEnvironment((doc) => {
        const alreadyPublished = this.publishedDocs.has(doc._id)

        if (alreadyPublished) {
          debugLog('Publication.observeHandle.added', `${collectionName}:${doc._id} already published`)
          this.publishedDocs.unflagForRemoval(doc._id)
          this._republishChildrenOf(doc)
          this.subscription.changed(collectionName, doc._id, doc)
        } else {
          this.publishedDocs.add(collectionName, doc._id)
          this._publishChildrenOf(doc)
          this.subscription.added(collectionName, doc)
        }
      }),
      changed: Meteor.bindEnvironment((newDoc) => {
        debugLog('Publication.observeHandle.changed', `${collectionName}:${newDoc._id}`)
        this._republishChildrenOf(newDoc)
      }),
      removed: (doc) => {
        debugLog('Publication.observeHandle.removed', `${collectionName}:${doc._id}`)
        this._removeDoc(collectionName, doc._id)
      }
    })

    this.observeChangesHandle = this.cursor.observeChanges({
      changed: (id, fields) => {
        debugLog('Publication.observeChangesHandle.changed', `${collectionName}:${id}`)
        this.subscription.changed(collectionName, id, fields)
      }
    })
  }

  unpublish () {
    debugLog('Publication.unpublish', this._getCollectionName())
    this._stopObservingCursor()
    this._unpublishAllDocuments()
  }

  _republish () {
    this._stopObservingCursor()

    this.publishedDocs.flagAllForRemoval()

    debugLog('Publication._republish', 'run .publish again')
    this.publish()

    debugLog('Publication._republish', 'unpublish docs from old cursor')
    this._removeFlaggedDocs()
  }

  _getCursor () {
    return this.options.find.apply(this.subscription.meteorSub, this.args)
  }

  _getCollectionName () {
    return this.collectionName || (this.cursor && this.cursor._getCollectionName())
  }

  _publishChildrenOf (doc) {
    const children = typeof this.childrenOptions === 'function'
      ? this.childrenOptions(doc, ...this.args) : this.childrenOptions
    children.forEach((options) => {
      const pub = new Publication(this.subscription, options, [doc].concat(this.args))
      this.publishedDocs.addChildPub(doc._id, pub)
      pub.publish()
    })
  }

  _republishChildrenOf (doc) {
    const parentArgs = this.args
    let newArgs
    this.publishedDocs.eachChildPub(doc._id, (publication) => {
      // First argument is the new document
      publication.args[0] = doc

      // Check if parent's args are the same length as this publication
      // Intuitively this should not ever be the case! However it does happen sometimes.
      // When it does the first argument of the parent publication is the doc.
      // So we skip this to avoid creating a duplicate of the first argument.
      if (parentArgs.length === publication.args.length) {
        newArgs = parentArgs.slice(1)
      } else {
        newArgs = parentArgs
      }

      // Subsequent args are passed down from parent.
      // These may have been updated by a grandparent publication.
      newArgs.forEach(function (arg, i) {
        publication.args[i + 1] = arg
      })

      publication._republish()
    })
  }

  _unpublishAllDocuments () {
    this.publishedDocs.eachDocument((doc) => {
      this._removeDoc(doc.collectionName, doc.docId)
    }, this)
  }

  _stopObservingCursor () {
    debugLog('Publication._stopObservingCursor', 'stop observing cursor')

    if (this.observeHandle) {
      this.observeHandle.stop()
      delete this.observeHandle
    }

    if (this.observeChangesHandle) {
      this.observeChangesHandle.stop()
      delete this.observeChangesHandle
    }
  }

  _removeFlaggedDocs () {
    this.publishedDocs.eachDocument((doc) => {
      if (doc.isFlaggedForRemoval()) {
        this._removeDoc(doc.collectionName, doc.docId)
      }
    }, this)
  }

  _removeDoc (collectionName, docId) {
    this.subscription.removed(collectionName, docId)
    this._unpublishChildrenOf(docId)
    this.publishedDocs.remove(docId)
  }

  _unpublishChildrenOf (docId) {
    debugLog('Publication._unpublishChildrenOf', `unpublishing children of ${this._getCollectionName()}:${docId}`)

    this.publishedDocs.eachChildPub(docId, (publication) => {
      publication.unpublish()
    })
  }
}

export default Publication
<|endoftext|>


<commit_before>'use strict';

var BoundQuery = require('./bound');
var SelectQuery = require('./select');
var BluebirdPromise = require('bluebird');

/**
 * A query bound to a specific model class.
 *
 * You will not create this query object directly. Instead, you will
 * receive it via {@link EntryQuery#bindModel}.
 *
 * @since 1.0
 * @protected
 * @constructor ModelQuery
 * @extends BoundQuery
 */
var ModelQuery = BoundQuery.extend(/** @lends ModelQuery# */ {
  init: function() { throw new Error('ModelQuery must be spawned.'); },

  _create: function(model) {
    this._super(model.tableName);
    this._model = model;
    this._prefetches = [];
  },

  /**
   * Duplication implementation.
   *
   * @see {@link BaseQuery#_take}
   */
  _take: function(orig) {
    this._super(orig);
    this._model = orig._model;
    this._prefetches = orig._prefetches;
  },

  /**
   * Override of {@link BaseQuery#_spawn}.
   *
   * @private
   * @method
   * @see {@link BaseQuery#_spawn}
   */
  _spawn: function(type) {
    if (this._prefetches.length) {
      this._validateSpawn(type, 'with', [SelectQuery]);
    }
    return this._super.apply(this, arguments);
  },

  /**
   * Documentation forthcoming.
   *
   * @since 1.0
   * @public
   * @method
   */
  _execute: function() {
    return this._super()
      .tap(this._prefetch.bind(this));
  },

  /**
   * Documentation forthcoming.
   *
   * @since 1.0
   * @public
   * @method
   */
  _prefetch: function(records) {
    return BluebirdPromise.map(this._prefetches, function(relation) {
      return relation.prefetch(records);
    });
  },

  /**
   * Documentation forthcoming.
   *
   * @since 1.0
   * @public
   * @method
   */
  with: function(association) {
    var dup = this._dup();
    var prototype = this._model.__class__.prototype;
    var relation = prototype[association + 'Relation'];
    dup._prefetches.push(relation);
    return dup;
  }

});

module.exports = ModelQuery.reopenClass({ __name__: 'ModelQuery' });
<commit_msg>Basic docs for model query.<commit_after>'use strict';

var BoundQuery = require('./bound');
var SelectQuery = require('./select');
var BluebirdPromise = require('bluebird');

/**
 * A query bound to a specific model class.
 *
 * You will not create this query object directly. Instead, you will
 * receive it via {@link EntryQuery#bindModel}.
 *
 * @since 1.0
 * @protected
 * @constructor ModelQuery
 * @extends BoundQuery
 */
var ModelQuery = BoundQuery.extend(/** @lends ModelQuery# */ {
  init: function() { throw new Error('ModelQuery must be spawned.'); },

  _create: function(model) {
    this._super(model.tableName);
    this._model = model;
    this._prefetches = [];
  },

  /**
   * Duplication implementation.
   *
   * @see {@link BaseQuery#_take}
   */
  _take: function(orig) {
    this._super(orig);
    this._model = orig._model;
    this._prefetches = orig._prefetches;
  },

  /**
   * Override of {@link BaseQuery#_spawn}.
   *
   * @private
   * @method
   * @see {@link BaseQuery#_spawn}
   */
  _spawn: function(type) {
    if (this._prefetches.length) {
      this._validateSpawn(type, 'with', [SelectQuery]);
    }
    return this._super.apply(this, arguments);
  },

  /**
   * Override of {@link BaseQuery#_execute}. Performs a pre-fetch after the
   * execution.
   *
   * @private
   * @method
   * @see {@link BaseQuery#_execute}
   */
  _execute: function() {
    return this._super()
      .tap(this._prefetch.bind(this));
  },

  /**
   * Perform all necessary pre-fetches.
   *
   * @since 1.0
   * @private
   * @method
   * @param {Array.<Model>} records The records for which to pre-fetch.
   * @return {Promise} A promise that will resolve when the pre-fetch has
   * completed.
   */
  _prefetch: function(records) {
    return BluebirdPromise.map(this._prefetches, function(relation) {
      return relation.prefetch(records);
    });
  },

  /**
   * Pre-fetch related objects.
   *
   * @since 1.0
   * @public
   * @method
   * @param {String} association The name of the relation to pre-fetch.
   * @return {ChainedQuery} The newly configured query.
   */
  with: function(association) {
    var dup = this._dup();
    var prototype = this._model.__class__.prototype;
    var relation = prototype[association + 'Relation'];
    dup._prefetches.push(relation);
    return dup;
  }

});

module.exports = ModelQuery.reopenClass({ __name__: 'ModelQuery' });
<|endoftext|>


<commit_before>'use strict';
var BufferUtil = require('./buffer-util');
var decode = require('./decode');

var map = function (decoder, len) {
	var value = {};
	var i;
	var k = new Array(len);
	var v = new Array(len);
	for (i = 0; i < len; i++) {
		k[i] = decode(decoder);
		v[i] = decode(decoder);
	}
	for (i = 0; i < len; i++) {
		value[k[i]] = v[i];
	}
	return value;
};

var array = function (decoder, len) {
	var value = new Array(len);
	for (var i = 0; i < len; i++) {
		value[i] = decode(decoder);
	}
	return value;
};

var str = function (decoder, len) {
	var start = decoder.offset;
	var end = decoder.offset = start + len;
	if (end > decoder.buffer.length) throw new RangeError('BUFFER_SHORTAGE');
	return BufferUtil.toString(decoder.buffer, start, end);
};

var bin = function (decoder, len) {
	var start = decoder.offset;
	var end = decoder.offset = start + len;
	if (end > decoder.buffer.length) throw new RangeError('BUFFER_SHORTAGE');
	return decoder.buffer.slice(start, end);
};

var ext = function (decoder, len) {
	var start = decoder.offset;
	var end = decoder.offset = start + len + 1;
	if (end > decoder.buffer.length) throw new RangeError('BUFFER_SHORTAGE');
	var type = decoder.buffer[start];
	var unpacker = decoder.codec.getUnpacker(type);
	if (!unpacker) throw new Error('Invalid ext type: ' + (type ? ('0x' + type.toString(16)) : type));
	var buf = decoder.buffer.slice(start + 1, end);
	return unpacker(buf);
};

var uint8 = function (decoder) {
	var buffer = decoder.buffer;
	if (decoder.offset >= buffer.length) throw new RangeError('BUFFER_SHORTAGE');
	return buffer[decoder.offset++];
};

var uint16 = function (decoder) {
	var buffer = decoder.buffer;
	if (decoder.offset + 2 > buffer.length) throw new RangeError('BUFFER_SHORTAGE');
	return (buffer[decoder.offset++] << 8) | buffer[decoder.offset++];
};

var read = function (len, method) {
	return function(decoder) {
		var start = decoder.offset;
		var end = decoder.offset = start + len;
		if (end > decoder.buffer.length) throw new RangeError('BUFFER_SHORTAGE');
		return method.call(decoder.buffer, start, true);
	};
};

var readUInt64BE = function (start, noAssert) {
	var upper = this.readUInt32BE(start, noAssert);
	var lower = this.readUInt32BE(start + 4, noAssert);
	return upper ? (upper * 4294967296 + lower) : lower;
};

var readInt64BE = function (start, noAssert) {
	var upper = this.readInt32BE(start, noAssert);
	var lower = this.readUInt32BE(start + 4, noAssert);
	return upper ? (upper * 4294967296 + lower) : lower;
};

module.exports = {
	map: map,
	array: array,
	str: str,
	bin: bin,
	ext: ext,
	uint8: uint8,
	uint16: uint16,
	uint32: read(4, Buffer.prototype.readUInt32BE),
	uint64: read(8, readUInt64BE),
	int8: read(1, Buffer.prototype.readInt8),
	int16: read(2, Buffer.prototype.readInt16BE),
	int32: read(4, Buffer.prototype.readInt32BE),
	int64: read(8, readInt64BE),
	float32: read(4, Buffer.prototype.readFloatBE),
	float64: read(8, Buffer.prototype.readDoubleBE)
};
<commit_msg>shift more towards Uint8Array support<commit_after>'use strict';
var BufferUtil = require('./buffer-util');
var decode = require('./decode');

var map = function (decoder, len) {
	var value = {};
	var i;
	var k = new Array(len);
	var v = new Array(len);
	for (i = 0; i < len; i++) {
		k[i] = decode(decoder);
		v[i] = decode(decoder);
	}
	for (i = 0; i < len; i++) {
		value[k[i]] = v[i];
	}
	return value;
};

var array = function (decoder, len) {
	var value = new Array(len);
	for (var i = 0; i < len; i++) {
		value[i] = decode(decoder);
	}
	return value;
};

var str = function (decoder, len) {
	var start = decoder.offset;
	var end = decoder.offset = start + len;
	if (end > decoder.buffer.byteLength) throw new RangeError('BUFFER_SHORTAGE');
	return BufferUtil.toString(decoder.buffer, start, end);
};

var bin = function (decoder, len) {
	var start = decoder.offset;
	var end = decoder.offset = start + len;
	if (end > decoder.buffer.byteLength) throw new RangeError('BUFFER_SHORTAGE');
	return decoder.buffer.subarray(start, end);
};

var ext = function (decoder, len) {
	var start = decoder.offset;
	var end = decoder.offset = start + len + 1;
	if (end > decoder.buffer.byteLength) throw new RangeError('BUFFER_SHORTAGE');
	var type = decoder.buffer[start];
	var unpacker = decoder.codec.getUnpacker(type);
	if (!unpacker) throw new Error('Invalid ext type: ' + (type ? ('0x' + type.toString(16)) : type));
	var buf = decoder.buffer.slice(start + 1, end);
	return unpacker(buf);
};

var uint8 = function (decoder) {
	var buffer = decoder.buffer;
	if (decoder.offset >= buffer.byteLength) throw new RangeError('BUFFER_SHORTAGE');
	return buffer[decoder.offset++];
};

var uint16 = function (decoder) {
	var buffer = decoder.buffer;
	if (decoder.offset + 2 > buffer.byteLength) throw new RangeError('BUFFER_SHORTAGE');
	return (buffer[decoder.offset++] << 8) | buffer[decoder.offset++];
};

var read = function (len, method) {
	return function(decoder) {
		var start = decoder.offset;
		var end = decoder.offset = start + len;
		if (end > decoder.buffer.byteLength) throw new RangeError('BUFFER_SHORTAGE');
		return method.call(decoder.buffer, start, true);
	};
};

var readUInt64BE = function (start, noAssert) {
	var upper = this.readUInt32BE(start, noAssert);
	var lower = this.readUInt32BE(start + 4, noAssert);
	return upper ? (upper * 4294967296 + lower) : lower;
};

var readInt64BE = function (start, noAssert) {
	var upper = this.readInt32BE(start, noAssert);
	var lower = this.readUInt32BE(start + 4, noAssert);
	return upper ? (upper * 4294967296 + lower) : lower;
};

module.exports = {
	map: map,
	array: array,
	str: str,
	bin: bin,
	ext: ext,
	uint8: uint8,
	uint16: uint16,
	uint32: read(4, Buffer.prototype.readUInt32BE),
	uint64: read(8, readUInt64BE),
	int8: read(1, Buffer.prototype.readInt8),
	int16: read(2, Buffer.prototype.readInt16BE),
	int32: read(4, Buffer.prototype.readInt32BE),
	int64: read(8, readInt64BE),
	float32: read(4, Buffer.prototype.readFloatBE),
	float64: read(8, Buffer.prototype.readDoubleBE)
};
<|endoftext|>


<commit_before>var Redis = require('ioredis');

module.exports = redisClient;

function redisClient() {
  var LAST_SAVED_ID = '_lastSavedId';

  var redis = new Redis();

  return {
    /**
     * Save array of users and returns maximum seen id
     */
    save: save,

    /**
     * Returns a promise for the last saved user identifier, so that clients can
     * resume indexing at any time.
     */
    getLastSavedId: getLastSavedId,
    close: close
  };

  function close() {
    redis.disconnect();
  }

  function getLastSavedId() {
    return redis.get(LAST_SAVED_ID);
  }

  function save(users) {
    if (!users || typeof users.length !== 'number') throw new Error('Invalid users object: ' + users);

    var pipeline = redis.pipeline();
    var maxId = 0;
    for (var i = 0; i < users.length; ++i) {
      var user = users[i];
      pipeline.hmset(user.login, {id: user.id});
      if (user.id > maxId) maxId = user.id;
    }
    pipeline.set(LAST_SAVED_ID, maxId);

    pipeline.exec(function (err, results) {
      if (err) {
        console.log('ERROR: ' + err, results);
        throw (err);
      }
    });

    return maxId;
  }
}
<commit_msg>Added docs<commit_after>var Redis = require('ioredis');

module.exports = redisClient;

function redisClient() {
  var LAST_SAVED_ID = '_lastSavedId';

  var redis = new Redis();

  return {
    /**
     * Save array of users and returns maximum seen id
     */
    save: save,

    /**
     * Returns a promise for the last saved user identifier, so that clients can
     * resume indexing at any time.
     */
    getLastSavedId: getLastSavedId,

    /**
     * Close the connection and dispose the client
     */
    close: close
  };

  function close() {
    redis.disconnect();
  }

  function getLastSavedId() {
    return redis.get(LAST_SAVED_ID);
  }

  function save(users) {
    if (!users || typeof users.length !== 'number') throw new Error('Invalid users object: ' + users);

    var pipeline = redis.pipeline();
    var maxId = 0;
    for (var i = 0; i < users.length; ++i) {
      var user = users[i];
      pipeline.hmset(user.login, {id: user.id});
      if (user.id > maxId) maxId = user.id;
    }
    pipeline.set(LAST_SAVED_ID, maxId);

    pipeline.exec(function (err, results) {
      if (err) {
        console.log('ERROR: ' + err, results);
        throw (err);
      }
    });

    return maxId;
  }
}
<|endoftext|>


<commit_before>/**
 * @author kuyur@kuyur.info
 * @see https://ripple.com/build/ripple-rest/
 */

exports.HOST = 'https://api.ripple.com';

exports.API = {
  NEW_WALLET: {
    method: 'GET',
    url: exports.HOST + '/v1/wallet/new'
  },
  BALANCE: {
    method: 'GET',
    url: exports.HOST + '/v1/accounts/{address}/balances'
  },
  GET_TRUSTLINE: {
    method: 'GET',
    url: exports.HOST + '/v1/accounts/{address}/trustlines'
  },
  GRANT_TRUSTLINE: {
    method: 'POST',
    url: exports.HOST + '/v1/accounts/{address}/trustlines'
  },
  PREPARE_PAYMENT: {
    method: 'GET',
    url: exports.HOST + '/v1/accounts/{address}/payments/paths/{destination_account}/{destination_amount}'
  },
  SUBMIT_PAYMENT: {
    method: 'POST',
    url: exports.HOST + '/v1/accounts/{address}/payments'
  },
  GET_PAYMENT: {
    method: 'GET',
    url: exports.HOST + '/v1/accounts/{address}/payments/{id}'
  },
  GET_PAYMENTS: {
    method: 'GET',
    url: exports.HOST + '/v1/accounts/{address}/payments'
  },
  GET_ORDER: {
    method: 'GET',
    url: exports.HOST + '/v1/accounts/{address}/orders'
  },
  PLACE_ORDER: {
    method: 'POST',
    url: exports.HOST + '/v1/accounts/{address}/orders'
  },
  CANCEL_ORDER: {
    method: 'DELETE',
    url: exports.HOST + '/v1/accounts/{address}/orders/{sequence}'
  },
  UUID: {
    method: 'GET',
    url: exports.HOST + '/v1/uuid'
  },
  TRANSACTION: {
    method: 'GET',
    url: exports.HOST + '/v1/transactions/{hash}'
  },
  USER_INFO: {
    method: 'GET',
    url: 'https://id.ripple.com/v1/user/{address}'
  }
};
<commit_msg>add bridge url pattern.<commit_after>/**
 * @author kuyur@kuyur.info
 * @see https://ripple.com/build/ripple-rest/
 */

exports.HOST = 'https://api.ripple.com';

exports.API = {
  NEW_WALLET: {
    method: 'GET',
    url: exports.HOST + '/v1/wallet/new'
  },
  BALANCE: {
    method: 'GET',
    url: exports.HOST + '/v1/accounts/{address}/balances'
  },
  GET_TRUSTLINE: {
    method: 'GET',
    url: exports.HOST + '/v1/accounts/{address}/trustlines'
  },
  GRANT_TRUSTLINE: {
    method: 'POST',
    url: exports.HOST + '/v1/accounts/{address}/trustlines'
  },
  PREPARE_PAYMENT: {
    method: 'GET',
    url: exports.HOST + '/v1/accounts/{address}/payments/paths/{destination_account}/{destination_amount}'
  },
  SUBMIT_PAYMENT: {
    method: 'POST',
    url: exports.HOST + '/v1/accounts/{address}/payments'
  },
  GET_PAYMENT: {
    method: 'GET',
    url: exports.HOST + '/v1/accounts/{address}/payments/{id}'
  },
  GET_PAYMENTS: {
    method: 'GET',
    url: exports.HOST + '/v1/accounts/{address}/payments'
  },
  GET_ORDER: {
    method: 'GET',
    url: exports.HOST + '/v1/accounts/{address}/orders'
  },
  PLACE_ORDER: {
    method: 'POST',
    url: exports.HOST + '/v1/accounts/{address}/orders'
  },
  CANCEL_ORDER: {
    method: 'DELETE',
    url: exports.HOST + '/v1/accounts/{address}/orders/{sequence}'
  },
  UUID: {
    method: 'GET',
    url: exports.HOST + '/v1/uuid'
  },
  TRANSACTION: {
    method: 'GET',
    url: exports.HOST + '/v1/transactions/{hash}'
  },
  USER_INFO: {
    method: 'GET',
    url: 'https://id.ripple.com/v1/user/{address}'
  },
  BRIDGE: {
    method: 'GET',
    url: 'https://{domain}/bridge?type=federation&destination={destination}&domain={domain}'
  },
  QUOTE_BRIDGE_PAYMENT: {
    method: 'GET',
    url: 'https://{domain}/bridge?type=quote&destination={destination}&domain={domain}&amount={amount}&source={address}'
  }
};
<|endoftext|>


<commit_before>/*jshint unused:false*/
var slug = require('slug');
var uuid = require('uuid');
var moment = require('moment');
var request = require('request');
var https = require('https');
var url = require('url');
var cache = require('../cache');

var async = require('async');

var config = require('../../config.json');
var talks = require('libtlks').talk;
var users = require('libtlks').user;

/**
 * Route /search
 * @param req   HTTP Request object
 * @param req   HTTP Response object
 */
exports.search = function(req, res, next) {
    'use strict';
    cache.render(req, res, function() {
        var q = req.query.q;
        var user = req.session.user;
        var context = {
            title: 'Tech talks by \'' + q + '\'' + ' | tlks.io',
            description: 'Tech talks by \'' + q + '\'' + ' | tlks.io',
            user: user,
            q: q,
            talks: []
        };
        var url = config.elasticsearch + '/tlksio/talk/_search?q=' + q;
        request.get({
            url: url,
            method: 'GET',
            json: true
        }, function(err, response, body) {
            if (err) {
                return next(err);
            }
            var hits = body.hits.hits;
            async.map(hits, function(el, callback) {
                var obj = el._source;
                obj.tags = obj.tags.split(',').map(function(el) {
                    return el.trim();
                });
                talks.getBySlug(config.mongodb, obj.slug, function(err, talks) {
                    if (err) {
                        return next(err);
                    }
                    callback(null, talks[0]);
                });
            }, function(err, results) {
                if (err) {
                    return next(err);
                }
                context.talks = results;
                var filePath = __dirname + "/../../src/views/search.jade";
                var html = cache.compile(req.url, filePath, context);
                res.write(html);
                res.end();
            });
        });
    }, cache.min5);
};

/**
 * Route /talk/play/:id
 * @param req   HTTP Request object
 * @param res   HTTP Response object
 */
exports.play = function(req, res) {
    'use strict';

    var slug = req.params.slug;
    talks.play(config.mongodb, slug, function(err, talk) {
        if (err) {
            return next(err);
        }
        var url = 'https://www.youtube.com/watch?v=' + talk.code;
        res.redirect(url);
    });
};

/**
 * Route /talk/upvote/:id
 * @param req   HTTP Request object
 * @param res   HTTP Response object
 */
exports.upvote = function(req, res, next) {
    'use strict';

    var id = req.params.id;
    var user = req.session.user;
    if (user === undefined) {
        var err = {
            status: 401,
            message: 'Unauthorized'
        };
        return next(err);
    } else {
        var userid = user.id;
        talks.upvote(config.mongodb, id, userid, function(err, updated) {
            if (err) {
                return next(err);
            }
            var result = {
                'result': updated ? true : false
            };
            res.send(result);
        });
    }
};

/**
 * Route /talk/favorite/:id
 * @param req   HTTP Request object
 * @param res   HTTP Response object
 */
exports.favorite = function(req, res, next) {
    'use strict';

    var id = req.params.id;
    var user = req.session.user;
    if (user === undefined) {
        var err = {
            status: 401,
            message: 'Unauthorized'
        };
        return next(err);
    } else {
        var userid = user.id;
        talks.favorite(config.mongodb, id, userid, function(err, updated) {
            if (err) {
                return next(err);
            }
            var result = {
                'result': updated ? true : false
            };
            res.send(result);
        });
    }
};

/**
 * Route /talk/unfavorite/:id
 * @param req   HTTP Request object
 * @param res   HTTP Response object
 */
exports.unfavorite = function(req, res, next) {
    'use strict';

    var id = req.params.id;
    var user = req.session.user;
    if (user === undefined) {
        var err = {
            status: 401,
            message: 'Unauthorized'
        };
        return next(err);
    } else {
        var userid = user.id;
        talks.unfavorite(config.mongodb, id, userid, function(err, updated) {
            if (err) {
                return next(err);
            }
            var result = {
                'result': updated ? true : false
            };
            res.send(result);
        });
    }
};

/**
 * Route /talk/:slug
 * @param req   HTTP Request object
 * @param res   HTTP Response object
 */
exports.talk = function(req, res) {
    'use strict';
    cache.render(req, res, function() {
        var slug = req.params.slug;
        var user = req.session.user;
        var context = {
            user: user,
            moment: moment
        };
        talks.getBySlug(config.mongodb, slug, function(err, docs) {
            var talk = docs[0];
            if (err) {
                return next(err);
            }
            if (talk) {
                context.talk = talk;
                context.title = talk.title + ' | tlks.io';
                context.description = talk.description.substring(0, 200);
                talks.related(
                    config.mongodb,
                    talk.id,
                    talk.tags,
                    5,
                    function(err, docs) {
                        if (err) {
                            return next(err);
                        }
                        context.related = docs;

                        var userList = [];
                        users.getByIdentifiers(
                            config.mongodb,
                            talk.votes,
                            function(err, votes) {
                                votes.forEach(function(user) {
                                    userList.push(user);
                                });
                                context.voters = userList;
                                res.render('talk', context);
                            }
                        );
                    });
            } else {
                res.status(404).render('404', context);
            }
        });
    }, cache.min5);
};

/**
 * Route /add
 * @param req   HTTP Request object
 * @param res   HTTP Response object
 */
exports.add = function(req, res) {
    'use strict';

    var user = req.session.user;
    if (user === undefined) {
        res.status(401).send('401').end();
    } else {
        var context = {
            title: 'tlks.io : Add a new talk',
            user: user,
            csrfToken: req.csrfToken()
        };
        res.render('add', context);
    }
};

/*
 * @param string field req.body.code, from request.
 *
 * @return [code, type] extracted from url.
 */
function getCodeAndTypeFromRequest(requestUrl) {
    'use strict';

    var type, code;
    var domain = url.parse(requestUrl).hostname || '';
    var path = url.parse(requestUrl).path || '';

    switch (domain) {
        case 'www.youtube.com':
        case 'youtube.com':
        case 'youtu.be':
            type = "youtube";
            code = path.match(/^\/(watch\?v=)?(([^/])+)($|\/)/i)[2] || '';
            break;
        case 'vimeo.com':
            type = "vimeo";
            code = path.match(/^\/(\d+)(\/|$)/)[1] || '';
            break;
        default:
            throw new Error("Invalid URL");
    }

    if (code === "") {
        throw new Error("Invalid URL");
    }

    return [code, type];
}

/**
 * Route /save
 * @param req   HTTP Request object
 * @param res   HTTP Response object
 */
exports.save = function(req, res, next) {
    'use strict';

    function uniq(a) {
        var seen = {};
        return a.filter(function(item) {
            return seen.hasOwnProperty(item) ? false : (seen[item] = true);
        });
    }

    function nl2br(string) {
        var breakTag = '<br>';
        var subs = '$1' + breakTag + '$2';
        return string.replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, subs);
    }

    // user
    var user = req.session.user;
    if (user === undefined) {
        var err = {
            status: 401,
            message: 'Unauthorized'
        };
        return next(err);
    } else {
        var parts = getCodeAndTypeFromRequest(req.body.code);
        var code = parts[0];
        var type = parts[1];

        talks.getByCode(config.mongodb, code, function(err, docs) {
            var talk = docs[0];
            if (err) {
                return next(err);
            }
            if (talk) {
                res.redirect(301, "/talk/" + talk.slug);
            } else {
                var description = nl2br(req.body.description);
                talk = {
                    id: uuid.v1(),
                    code: code,
                    title: req.body.title,
                    slug: slug(req.body.title).toLowerCase(),
                    description: description,
                    author: {
                        id: user.id,
                        username: user.username,
                        avatar: user.avatar
                    },
                    'viewCount': 0,
                    'voteCount': 0,
                    'votes': [],
                    'favoriteCount': 0,
                    'favorites': [],
                    'tags': uniq(req.body.tags.split(',').map(function(el) {
                        return el.trim().toLowerCase();
                    })),
                    'created': Date.now(),
                    'updated': Date.now(),
                    'type': type
                };

                var thumb = "";
                if (type === "vimeo") {
                    var url = "https://vimeo.com/api/v2/video/" +
                        code + ".json";
                    https.get(url, function(responseVimeo) {
                        var body = '';
                        responseVimeo.on('data', function(chunk) {
                            body += chunk;
                        });

                        /*jshint camelcase: false */
                        responseVimeo.on('end', function() {
                            talk.thumb = JSON.parse(body)[0].thumbnail_medium;
                        });

                    }).on('error', function(e) {
                        console.log("Got error: " + e.message);
                    });
                }

                // create talk
                talks.createTalk(config.mongodb, talk, function(err, talk) {
                    if (err) {
                        return next(err);
                    }
                    // render
                    res.redirect('/');
                });
            }
        });
    }
};

// talk
/**
 * Route /popular
 * @param req   HTTP Request object
 * @param res   HTTP Response object
 */
exports.popular = function(req, res) {
    'use strict';

    var quantity = 25;
    var page = parseInt(req.params.page || 1);
    var pagination = {
        page: page,
        next: page + 1,
        prev: (page - 1 <= 1) ? 1 : page - 1
    };
    var user = req.session.user;
    var context = {
        title: 'tlks.io : Popular talks',
        user: user,
        page: page,
        pagination: pagination
    };
    talks.popular(config.mongodb, quantity, page, function(err, talks) {
        if (err) {
            return next(err);
        }
        if (talks.length === 0) {
            res.status(204).end();
            return;
        }
        context.latest = talks;
        res.render('popular', context);
    });
};

/**
 * Route /latest
 * @param req   HTTP Request object
 * @param res   HTTP Response object
 */
exports.latest = function(req, res) {
    'use strict';

    var quantity = 25;
    var page = parseInt(req.params.page || 1);
    var pagination = {
        page: page,
        next: page + 1,
        prev: (page - 1 <= 1) ? 1 : page - 1
    };
    var user = req.session.user;
    var context = {
        title: 'tlks.io : Latest talks',
        user: user,
        page: page,
        pagination: pagination
    };
    talks.latest(config.mongodb, quantity, page, function(err, talks) {
        if (err) {
            return next(err);
        }
        if (talks.length === 0) {
            res.status(204).end();
            return;
        }
        context.latest = talks;
        res.render('latest', context);
    });

};
<commit_msg>Cached detailed view talk<commit_after>/*jshint unused:false*/
var slug = require('slug');
var uuid = require('uuid');
var moment = require('moment');
var request = require('request');
var https = require('https');
var url = require('url');
var cache = require('../cache');

var async = require('async');

var config = require('../../config.json');
var talks = require('libtlks').talk;
var users = require('libtlks').user;

/**
 * Route /search
 *
 * Renders Search template.
 * It caches the result for 5 minutes.
 *
 * @param req   HTTP Request object
 * @param req   HTTP Response object
 */
exports.search = function(req, res, next) {
    'use strict';
    cache.render(req, res, function() {
        var q = req.query.q;
        var user = req.session.user;
        var context = {
            title: 'Tech talks by \'' + q + '\'' + ' | tlks.io',
            description: 'Tech talks by \'' + q + '\'' + ' | tlks.io',
            user: user,
            q: q,
            talks: []
        };
        var url = config.elasticsearch + '/tlksio/talk/_search?q=' + q;
        request.get({
            url: url,
            method: 'GET',
            json: true
        }, function(err, response, body) {
            if (err) {
                return next(err);
            }
            var hits = body.hits.hits;
            async.map(hits, function(el, callback) {
                var obj = el._source;
                obj.tags = obj.tags.split(',').map(function(el) {
                    return el.trim();
                });
                talks.getBySlug(config.mongodb, obj.slug, function(err, talks) {
                    if (err) {
                        return next(err);
                    }
                    callback(null, talks[0]);
                });
            }, function(err, results) {
                if (err) {
                    return next(err);
                }
                context.talks = results;
                var filePath = __dirname + "/../../src/views/search.jade";
                var html = cache.compile(req.url, filePath, context);
                res.write(html);
                res.end();
            });
        });
    }, cache.min5);
};

/**
 * Route /talk/play/:id
 *
 * Plays a talk by its slug field.
 *
 * @param req   HTTP Request object
 * @param res   HTTP Response object
 */
exports.play = function(req, res) {
    'use strict';

    var slug = req.params.slug;
    talks.play(config.mongodb, slug, function(err, talk) {
        if (err) {
            return next(err);
        }
        var url = 'https://www.youtube.com/watch?v=' + talk.code;
        res.redirect(url);
    });
};

/**
 * Route /talk/upvote/:id
 *
 * Upvotes a talk by its ID.
 *
 * @param req   HTTP Request object
 * @param res   HTTP Response object
 */
exports.upvote = function(req, res, next) {
    'use strict';

    var id = req.params.id;
    var user = req.session.user;
    if (user === undefined) {
        var err = {
            status: 401,
            message: 'Unauthorized'
        };
        return next(err);
    } else {
        var userid = user.id;
        talks.upvote(config.mongodb, id, userid, function(err, updated) {
            if (err) {
                return next(err);
            }
            var result = {
                'result': updated ? true : false
            };
            res.send(result);
        });
    }
};

/**
 * Route /talk/favorite/:id
 *
 * Favorites a talk by its ID.
 *
 * @param req   HTTP Request object
 * @param res   HTTP Response object
 */
exports.favorite = function(req, res, next) {
    'use strict';

    var id = req.params.id;
    var user = req.session.user;
    if (user === undefined) {
        var err = {
            status: 401,
            message: 'Unauthorized'
        };
        return next(err);
    } else {
        var userid = user.id;
        talks.favorite(config.mongodb, id, userid, function(err, updated) {
            if (err) {
                return next(err);
            }
            var result = {
                'result': updated ? true : false
            };
            res.send(result);
        });
    }
};

/**
 * Route /talk/unfavorite/:id
 *
 * Unfavorite a talk by its ID.
 *
 * @param req   HTTP Request object
 * @param res   HTTP Response object
 */
exports.unfavorite = function(req, res, next) {
    'use strict';

    var id = req.params.id;
    var user = req.session.user;
    if (user === undefined) {
        var err = {
            status: 401,
            message: 'Unauthorized'
        };
        return next(err);
    } else {
        var userid = user.id;
        talks.unfavorite(config.mongodb, id, userid, function(err, updated) {
            if (err) {
                return next(err);
            }
            var result = {
                'result': updated ? true : false
            };
            res.send(result);
        });
    }
};

/**
 * Route /talk/:slug
 *
 * Renders detailed talk template.
 * It caches the result for 5 minutes.
 *
 * @param req   HTTP Request object
 * @param res   HTTP Response object
 */
exports.talk = function(req, res) {
    'use strict';
    cache.render(req, res, function() {
        var slug = req.params.slug;
        var user = req.session.user;
        var context = {
            user: user,
            moment: moment
        };
        talks.getBySlug(config.mongodb, slug, function(err, docs) {
            var talk = docs[0];
            if (err) {
                return next(err);
            }
            if (talk) {
                context.talk = talk;
                context.title = talk.title + ' | tlks.io';
                context.description = talk.description.substring(0, 200);
                talks.related(
                    config.mongodb,
                    talk.id,
                    talk.tags,
                    5,
                    function(err, docs) {
                        if (err) {
                            return next(err);
                        }
                        context.related = docs;

                        var userList = [];
                        users.getByIdentifiers(
                            config.mongodb,
                            talk.votes,
                            function(err, votes) {
                                votes.forEach(function(user) {
                                    userList.push(user);
                                });
                                context.voters = userList;
                                var filePath = __dirname + "/../../src/views/talk.jade";
                                var html = cache.compile(req.url, filePath, context);
                                res.write(html);
                                res.end();
                            }
                        );
                    });
            } else {
                res.status(404).render('404', context);
            }
        });
    }, cache.min5);
};

/**
 * Route /add
 * @param req   HTTP Request object
 * @param res   HTTP Response object
 */
exports.add = function(req, res) {
    'use strict';

    var user = req.session.user;
    if (user === undefined) {
        res.status(401).send('401').end();
    } else {
        var context = {
            title: 'tlks.io : Add a new talk',
            user: user,
            csrfToken: req.csrfToken()
        };
        res.render('add', context);
    }
};

/*
 * @param string field req.body.code, from request.
 *
 * @return [code, type] extracted from url.
 */
function getCodeAndTypeFromRequest(requestUrl) {
    'use strict';

    var type, code;
    var domain = url.parse(requestUrl).hostname || '';
    var path = url.parse(requestUrl).path || '';

    switch (domain) {
        case 'www.youtube.com':
        case 'youtube.com':
        case 'youtu.be':
            type = "youtube";
            code = path.match(/^\/(watch\?v=)?(([^/])+)($|\/)/i)[2] || '';
            break;
        case 'vimeo.com':
            type = "vimeo";
            code = path.match(/^\/(\d+)(\/|$)/)[1] || '';
            break;
        default:
            throw new Error("Invalid URL");
    }

    if (code === "") {
        throw new Error("Invalid URL");
    }

    return [code, type];
}

/**
 * Route /save
 * @param req   HTTP Request object
 * @param res   HTTP Response object
 */
exports.save = function(req, res, next) {
    'use strict';

    function uniq(a) {
        var seen = {};
        return a.filter(function(item) {
            return seen.hasOwnProperty(item) ? false : (seen[item] = true);
        });
    }

    function nl2br(string) {
        var breakTag = '<br>';
        var subs = '$1' + breakTag + '$2';
        return string.replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, subs);
    }

    // user
    var user = req.session.user;
    if (user === undefined) {
        var err = {
            status: 401,
            message: 'Unauthorized'
        };
        return next(err);
    } else {
        var parts = getCodeAndTypeFromRequest(req.body.code);
        var code = parts[0];
        var type = parts[1];

        talks.getByCode(config.mongodb, code, function(err, docs) {
            var talk = docs[0];
            if (err) {
                return next(err);
            }
            if (talk) {
                res.redirect(301, "/talk/" + talk.slug);
            } else {
                var description = nl2br(req.body.description);
                talk = {
                    id: uuid.v1(),
                    code: code,
                    title: req.body.title,
                    slug: slug(req.body.title).toLowerCase(),
                    description: description,
                    author: {
                        id: user.id,
                        username: user.username,
                        avatar: user.avatar
                    },
                    'viewCount': 0,
                    'voteCount': 0,
                    'votes': [],
                    'favoriteCount': 0,
                    'favorites': [],
                    'tags': uniq(req.body.tags.split(',').map(function(el) {
                        return el.trim().toLowerCase();
                    })),
                    'created': Date.now(),
                    'updated': Date.now(),
                    'type': type
                };

                var thumb = "";
                if (type === "vimeo") {
                    var url = "https://vimeo.com/api/v2/video/" +
                        code + ".json";
                    https.get(url, function(responseVimeo) {
                        var body = '';
                        responseVimeo.on('data', function(chunk) {
                            body += chunk;
                        });

                        /*jshint camelcase: false */
                        responseVimeo.on('end', function() {
                            talk.thumb = JSON.parse(body)[0].thumbnail_medium;
                        });

                    }).on('error', function(e) {
                        console.log("Got error: " + e.message);
                    });
                }

                // create talk
                talks.createTalk(config.mongodb, talk, function(err, talk) {
                    if (err) {
                        return next(err);
                    }
                    // render
                    res.redirect('/');
                });
            }
        });
    }
};

// talk
/**
 * Route /popular
 * @param req   HTTP Request object
 * @param res   HTTP Response object
 */
exports.popular = function(req, res) {
    'use strict';

    var quantity = 25;
    var page = parseInt(req.params.page || 1);
    var pagination = {
        page: page,
        next: page + 1,
        prev: (page - 1 <= 1) ? 1 : page - 1
    };
    var user = req.session.user;
    var context = {
        title: 'tlks.io : Popular talks',
        user: user,
        page: page,
        pagination: pagination
    };
    talks.popular(config.mongodb, quantity, page, function(err, talks) {
        if (err) {
            return next(err);
        }
        if (talks.length === 0) {
            res.status(204).end();
            return;
        }
        context.latest = talks;
        res.render('popular', context);
    });
};

/**
 * Route /latest
 * @param req   HTTP Request object
 * @param res   HTTP Response object
 */
exports.latest = function(req, res) {
    'use strict';

    var quantity = 25;
    var page = parseInt(req.params.page || 1);
    var pagination = {
        page: page,
        next: page + 1,
        prev: (page - 1 <= 1) ? 1 : page - 1
    };
    var user = req.session.user;
    var context = {
        title: 'tlks.io : Latest talks',
        user: user,
        page: page,
        pagination: pagination
    };
    talks.latest(config.mongodb, quantity, page, function(err, talks) {
        if (err) {
            return next(err);
        }
        if (talks.length === 0) {
            res.status(204).end();
            return;
        }
        context.latest = talks;
        res.render('latest', context);
    });

};
<|endoftext|>


<commit_before>module.exports = function(crowi, app) {
  'use strict'

  const User = crowi.model('User')
  const Bookmark = crowi.model('Bookmark')
  const Page = crowi.model('Page')
  const ApiResponse = require('../util/apiResponse')
  const actions = {}
  const api = {}

  actions.api = api

  api.bookmarks = function(req, res) {
    var options = {
      skip: req.query.offset || 0,
      limit: req.query.limit || 50,
    }
    Bookmark.findByUser(req.user, options, function(err, bookmarks) {
      res.json(bookmarks)
    })
  }

  api.checkUsername = function(req, res) {
    var username = req.query.username

    User.findUserByUsername(username)
      .then(function(userData) {
        if (userData) {
          return res.json({ valid: false })
        } else {
          return res.json({ valid: true })
        }
      })
      .catch(function(err) {
        return res.json({ valid: true })
      })
  }

  /**
   * @api {get} /users.list Get user list
   * @apiName GetUserList
   * @apiGroup User
   *
   * @apiParam {String} user_ids
   */
  api.list = function(req, res) {
    var userIds = req.query.user_ids || null // TODO: handling

    var userFetcher
    if (!userIds || userIds.split(',').length <= 0) {
      userFetcher = User.findAllUsers()
    } else {
      userFetcher = User.findUsersByIds(userIds.split(','))
    }

    userFetcher
      .then(function(userList) {
        var result = {
          users: userList,
        }

        return res.json(ApiResponse.success(result))
      })
      .catch(function(err) {
        return res.json(ApiResponse.error(err))
      })
  }

  api.getRecentlyViewedPages = async function(req, res) {
    const pageIds = await crowi.lru.get(req.user._id.toString(), 10)
    const pages = await Page.findPagesByIds(pageIds)
    const sorted = pages.sort((a, b) => pageIds.indexOf(a._id.toString()) - pageIds.indexOf(b._id.toString()))
    const sliced = sorted.slice(0, 5)
    return res.json(ApiResponse.success({ pages: sliced }))
  }

  return actions
}
<commit_msg>Ignore root path<commit_after>module.exports = function(crowi, app) {
  'use strict'

  const User = crowi.model('User')
  const Bookmark = crowi.model('Bookmark')
  const Page = crowi.model('Page')
  const ApiResponse = require('../util/apiResponse')
  const actions = {}
  const api = {}

  actions.api = api

  api.bookmarks = function(req, res) {
    var options = {
      skip: req.query.offset || 0,
      limit: req.query.limit || 50,
    }
    Bookmark.findByUser(req.user, options, function(err, bookmarks) {
      res.json(bookmarks)
    })
  }

  api.checkUsername = function(req, res) {
    var username = req.query.username

    User.findUserByUsername(username)
      .then(function(userData) {
        if (userData) {
          return res.json({ valid: false })
        } else {
          return res.json({ valid: true })
        }
      })
      .catch(function(err) {
        return res.json({ valid: true })
      })
  }

  /**
   * @api {get} /users.list Get user list
   * @apiName GetUserList
   * @apiGroup User
   *
   * @apiParam {String} user_ids
   */
  api.list = function(req, res) {
    var userIds = req.query.user_ids || null // TODO: handling

    var userFetcher
    if (!userIds || userIds.split(',').length <= 0) {
      userFetcher = User.findAllUsers()
    } else {
      userFetcher = User.findUsersByIds(userIds.split(','))
    }

    userFetcher
      .then(function(userList) {
        var result = {
          users: userList,
        }

        return res.json(ApiResponse.success(result))
      })
      .catch(function(err) {
        return res.json(ApiResponse.error(err))
      })
  }

  api.getRecentlyViewedPages = async function(req, res) {
    const pageIds = await crowi.lru.get(req.user._id.toString(), 10)
    let pages = await Page.findPagesByIds(pageIds)
    pages = pages.sort((a, b) => pageIds.indexOf(a._id.toString()) - pageIds.indexOf(b._id.toString()))
    pages = pages.filter(({ path }) => path !== '/')
    pages = pages.slice(0, 5)
    return res.json(ApiResponse.success({ pages }))
  }

  return actions
}
<|endoftext|>


<commit_before>var _ = require('lodash'),
    SerialiseError = require('serialised-error'),

    proxyPropertyList,
    RunSummary;

/**
 * Create an object that mimics the SDK property list.
 *
 * @private
 * @param {Object} options - The object that contains the object to proxy.
 * @param {String} name - The name of the object to proxy from a parent object.
 *
 * @returns {Object} - A wrapper around object and toJSON helper methods, to convert the given summary object to JSON
 */
proxyPropertyList = function (options, name) {
    return {

        /**
         * Get an object containing a copy of all variables and their values
         *
         * @type {Function}
         * @returns {Object<String>} - The object representation of the provided property name.
         */
        object: function () {
            return _.get(options, name, {});
        },

        /**
         * Transform object in variable-list JSON format
         *
         * @type {Function}
         * @returns {Object} - A JSON represented list of properties
         */
        toJSON: function () {
            return {
                values: _.map(_.get(options, name, {}), function (value, key) {
                    return { key: key, name: key, value: value, type: 'string' };
                })
            };
        }
    };
};

/**
 * Creates and returns a RunSummary instance for the current collection run.
 *
 * @constructor
 * @param {EventEmitter} emitter - An EventEmitter instance with event handler attachments to add run information to.
 * @param {Object} options - A set of run summary creation options.
 */
RunSummary = function RunSummary (emitter, options) {
    // keep a copy of this instance since, we need to refer to this from various events
    var summary = this;

    // and store the trackers and failures in the summary object itself
    _.assign(summary, /** @lends RunSummary.prototype */ {
        /**
         * The collection that is being executed.
         *
         * @type {Collection}
         */
        collection: _.get(options, 'collection'),

        /**
         * The environment that is being used during the run
         * @type {VariableList}
         *
         * @todo add name and id here. implying, that .run accepts it and uses the format
         * @todo also, once we start using SDK VariableList, this should become simple toJSON
         */
        environment: proxyPropertyList(options, 'environment'),

        /**
         * Environment variables being used during the run
         *
         * @type {VariableList}
         *
         * @todo add name and id here. implying, that .run accepts it and uses the format
         * @todo also, once we start using SDK VariableList, this should become simple toJSON
         */
        globals: proxyPropertyList(options, 'globals'),

        /**
         * Holds information related to the run.
         */
        run: {
            /**
             * Holds the statistics of the run. Each property in it is the item being tracked and has three numeric
             * properties - total, failed, pending
             *
             * @type {Object.<Object>}
             */
            stats: {
                iterations: {},
                items: {},
                scripts: {},
                prerequests: {},
                requests: {},
                tests: {},
                assertions: {},
                testScripts: {},
                prerequestScripts: {}
            },

            /**
             * Stores all generic timing information
             *
             * @type {Object}
             */
            timings: {
                /**
                 * The average response time of the run
                 *
                 * @type {number}
                 */
                responseAverage: 0
            },

            /**
             * Stores detailed information about the order of execution, request, response and assertions
             *
             * @type {Array<Object>}
             */
            executions: [],

            /**
             * Stores information on data transfer made during the collection
             *
             * @type {Object}
             */
            transfers: {
                /**
                 * The total data received as response to every request
                 *
                 * @type {number}
                 */
                responseTotal: 0
            },

            /**
             * An array of all errors encountered during the run
             *
             * @type {Array.<Error>}
             */
            failures: [],

            /**
             * This stores any fatal error during the run that caused the run to abort prematurely.
             *
             * @type {Error}
             */
            error: null
        }
    });

    // track run timings (start and end)
    RunSummary.attachTimingTrackers(this, emitter);

    // accumulate statistics on all event
    // for all types of events track the counters for the event and its corresponding "before" counterpart
    RunSummary.attachStatisticTrackers(this, emitter);

    // accumulate statistics on requests - such as size and time
    RunSummary.attachRequestTracker(this, emitter);

    // accumulate errors (failures) from all events
    RunSummary.attachFailureTrackers(this, emitter);

    // accumulate all execution specific data in collection
    RunSummary.attachReportingTrackers(this, emitter);
};

_.assign(RunSummary, {
    attachReportingTrackers: function (summary, emitter) {
        var cache = {},
            executions = summary.run.executions;

        emitter.on('beforeItem', function (err, o) {
            if (!_.get(o, 'cursor.ref')) { return; }

            cache[o.cursor.ref] = _.assignIn(cache[o.cursor.ref] || {}, {
                cursor: o.cursor,
                item: o.item
            });
        });

        // save all responses in executions array
        emitter.on('request', function (err, o) {
            if (!_.get(o, 'cursor.ref')) { return; }

            var execution = cache[o.cursor.ref] = (cache[o.cursor.ref] || {});

            executions.push(_.assignIn(execution, {
                cursor: o.cursor,
                request: o.request,
                response: _.omit(o.response, 'stream'),
                id: _.get(o, 'item.id')
            }, err && {
                requestError: err || undefined
            }));
        });

        // save all script execution errors in each execution
        emitter.on('script', function (err, o) {
            if (!_.get(o, 'cursor.ref')) { return; }

            var execution = cache[o.cursor.ref] = (cache[o.cursor.ref] || {}),
                eventName = o && o.event && (o.event.listen + 'Script');

            // store the script error corresponding to the script event name
            err && (execution && eventName) && (execution[eventName] || (execution[eventName] = [])).push({
                error: err
            });
        });

        // save all assertions in each execution
        emitter.on('assertion', function (err, o) {
            if (!_.get(o, 'cursor.ref')) { return; }

            var execution = cache[o.cursor.ref] = (cache[o.cursor.ref] || {});
            if (!execution) { return; }

            (execution.assertions || (execution.assertions = [])).push({
                assertion: o.assertion,
                error: err || undefined
            });
        });
    },

    attachTimingTrackers: function (summary, emitter) {
        // mark the point when the run started
        // also mark the point when run completed and also store error if needed
        emitter.on('start', function () { summary.run.timings.started = Date.now(); });
        emitter.on('beforeDone', function () {
            summary.run.timings.completed = Date.now();
        });
        emitter.on('done', function (err) {
            err && (summary.error = err);
        });
    },
    attachStatisticTrackers: function (summary, emitter) {
        // accumulate statistics on all event
        // for all types of events track the counters for the event and its corresponding "before" counterpart
        _.forEach(summary.run.stats, function (tracker, name) {
            // the actual event names are singular than their plural trackers, so we make the name singular
            name = name.slice(0, -1); // remove last character

            // populate initial values of trackers
            _.assign(tracker, { total: 0, pending: 0, failed: 0 });

            // Set up common listeners for a set of events, which tracks how many times they were executed and records
            // the ones which had an error passed as first argument
            emitter.on(_.camelCase('before-' + name), function () {
                tracker.pending += 1;
            });

            emitter.on(name, function (err) {
                // check pending so that, it does not negate for items that do not have a `before` counterpart
                tracker.pending && (tracker.pending -= 1);
                err && (tracker.failed += 1);
                tracker.total += 1;
            });
        });
    },

    attachRequestTracker: function (summary, emitter) {
        // accumulate statistics on requests
        emitter.on('request', function (err, o) {
            if (err || !(o && o.response)) { return; }

            var size = _.isFunction(o.response.size) && o.response.size(),
                time = o.response.responseTime,

                requestCount = summary.run.stats.requests.total;

            // compute the response size total
            size && (summary.run.transfers.responseTotal += (size.body || 0 + size.headers || 0));

            // compute average response time
            time && (summary.run.timings.responseAverage =
                ((summary.run.timings.responseAverage * (requestCount - 1) + time) / requestCount));
        });
    },

    attachFailureTrackers: function (summary, emitter) {
        var eventsToTrack = ['beforeIteration', 'iteration', 'beforeItem', 'item', 'beforeScript', 'script',
            'beforePrerequest', 'prerequest', 'beforeRequest', 'request', 'beforeTest', 'test', 'beforeAssertion',
            'assertion'];

        // accumulate failures of all events
        // NOTE that surrogate events (which throw duplicate arguments) are not recorded
        _.forEach(eventsToTrack, function (event) {
            // push failures sent from "before" events
            emitter.on(event, function (err, o) {
                if (!err) { return; }

                var item = o && o.item,
                    source = event;

                // in case of user script error, point to the line and column of the script and its type
                if (event === 'script') {
                    o.event && (source = o.event.listen + '-script');
                    if (err.stacktrace && err.stacktrace[0] && err.stacktrace[0].lineNumber) {
                        source += (':' + (err.stacktrace[0].lineNumber - 2));
                        err.stacktrace[0].columnNumber && (source += (':' + err.stacktrace[0].columnNumber));
                    }
                }
                // assertion errors need to know which assertion in the test was this
                else if (event === 'assertion') {
                    _.has(err, 'index') && (source += (':' + (err.index + 1)));
                    source += ' in test-script';
                }

                // if this is a plain error, convert it to serialised error
                if (err.stack && !err.stacktrace) {
                    err = new SerialiseError(err, true);
                }

                summary.run.failures.push({
                    error: err,
                    at: source,
                    source: item || undefined,
                    parent: item && item.__parent && item.__parent.__parent || undefined,
                    cursor: o.cursor || {}
                });
            });
        });
    }
});

module.exports = RunSummary;
<commit_msg>Removed PropertyList proxy function and made summary expose the actual VariableScope in its object<commit_after>var _ = require('lodash'),
    SerialiseError = require('serialised-error'),
    RunSummary;

/**
 * Creates and returns a RunSummary instance for the current collection run.
 *
 * @constructor
 * @param {EventEmitter} emitter - An EventEmitter instance with event handler attachments to add run information to.
 * @param {Object} options - A set of run summary creation options.
 */
RunSummary = function RunSummary (emitter, options) {
    // keep a copy of this instance since, we need to refer to this from various events
    var summary = this;

    // and store the trackers and failures in the summary object itself
    _.assign(summary, /** @lends RunSummary.prototype */ {
        /**
         * The collection that is being executed.
         *
         * @type {Collection}
         */
        collection: _.get(options, 'collection'),

        /**
         * The environment that is being used during the run
         * @type {VariableScope}
         *
         */
        environment: _.get(options, 'environment'),

        /**
         * Global variables being used during the run
         * @type {VariableScope}
         */
        globals: _.get(options, 'globals'),

        /**
         * Holds information related to the run.
         */
        run: {
            /**
             * Holds the statistics of the run. Each property in it is the item being tracked and has three numeric
             * properties - total, failed, pending
             *
             * @type {Object.<Object>}
             */
            stats: {
                iterations: {},
                items: {},
                scripts: {},
                prerequests: {},
                requests: {},
                tests: {},
                assertions: {},
                testScripts: {},
                prerequestScripts: {}
            },

            /**
             * Stores all generic timing information
             *
             * @type {Object}
             */
            timings: {
                /**
                 * The average response time of the run
                 *
                 * @type {number}
                 */
                responseAverage: 0
            },

            /**
             * Stores detailed information about the order of execution, request, response and assertions
             *
             * @type {Array<Object>}
             */
            executions: [],

            /**
             * Stores information on data transfer made during the collection
             *
             * @type {Object}
             */
            transfers: {
                /**
                 * The total data received as response to every request
                 *
                 * @type {number}
                 */
                responseTotal: 0
            },

            /**
             * An array of all errors encountered during the run
             *
             * @type {Array.<Error>}
             */
            failures: [],

            /**
             * This stores any fatal error during the run that caused the run to abort prematurely.
             *
             * @type {Error}
             */
            error: null
        }
    });

    // track run timings (start and end)
    RunSummary.attachTimingTrackers(this, emitter);

    // accumulate statistics on all event
    // for all types of events track the counters for the event and its corresponding "before" counterpart
    RunSummary.attachStatisticTrackers(this, emitter);

    // accumulate statistics on requests - such as size and time
    RunSummary.attachRequestTracker(this, emitter);

    // accumulate errors (failures) from all events
    RunSummary.attachFailureTrackers(this, emitter);

    // accumulate all execution specific data in collection
    RunSummary.attachReportingTrackers(this, emitter);
};

_.assign(RunSummary, {
    attachReportingTrackers: function (summary, emitter) {
        var cache = {},
            executions = summary.run.executions;

        emitter.on('beforeItem', function (err, o) {
            if (!_.get(o, 'cursor.ref')) { return; }

            cache[o.cursor.ref] = _.assignIn(cache[o.cursor.ref] || {}, {
                cursor: o.cursor,
                item: o.item
            });
        });

        // save all responses in executions array
        emitter.on('request', function (err, o) {
            if (!_.get(o, 'cursor.ref')) { return; }

            var execution = cache[o.cursor.ref] = (cache[o.cursor.ref] || {});

            executions.push(_.assignIn(execution, {
                cursor: o.cursor,
                request: o.request,
                response: _.omit(o.response, 'stream'),
                id: _.get(o, 'item.id')
            }, err && {
                requestError: err || undefined
            }));
        });

        // save all script execution errors in each execution
        emitter.on('script', function (err, o) {
            if (!_.get(o, 'cursor.ref')) { return; }

            var execution = cache[o.cursor.ref] = (cache[o.cursor.ref] || {}),
                eventName = o && o.event && (o.event.listen + 'Script');

            // store the script error corresponding to the script event name
            err && (execution && eventName) && (execution[eventName] || (execution[eventName] = [])).push({
                error: err
            });
        });

        // save all assertions in each execution
        emitter.on('assertion', function (err, o) {
            if (!_.get(o, 'cursor.ref')) { return; }

            var execution = cache[o.cursor.ref] = (cache[o.cursor.ref] || {});
            if (!execution) { return; }

            (execution.assertions || (execution.assertions = [])).push({
                assertion: o.assertion,
                error: err || undefined
            });
        });
    },

    attachTimingTrackers: function (summary, emitter) {
        // mark the point when the run started
        // also mark the point when run completed and also store error if needed
        emitter.on('start', function () { summary.run.timings.started = Date.now(); });
        emitter.on('beforeDone', function () {
            summary.run.timings.completed = Date.now();
        });
        emitter.on('done', function (err) {
            err && (summary.error = err);
        });
    },
    attachStatisticTrackers: function (summary, emitter) {
        // accumulate statistics on all event
        // for all types of events track the counters for the event and its corresponding "before" counterpart
        _.forEach(summary.run.stats, function (tracker, name) {
            // the actual event names are singular than their plural trackers, so we make the name singular
            name = name.slice(0, -1); // remove last character

            // populate initial values of trackers
            _.assign(tracker, { total: 0, pending: 0, failed: 0 });

            // Set up common listeners for a set of events, which tracks how many times they were executed and records
            // the ones which had an error passed as first argument
            emitter.on(_.camelCase('before-' + name), function () {
                tracker.pending += 1;
            });

            emitter.on(name, function (err) {
                // check pending so that, it does not negate for items that do not have a `before` counterpart
                tracker.pending && (tracker.pending -= 1);
                err && (tracker.failed += 1);
                tracker.total += 1;
            });
        });
    },

    attachRequestTracker: function (summary, emitter) {
        // accumulate statistics on requests
        emitter.on('request', function (err, o) {
            if (err || !(o && o.response)) { return; }

            var size = _.isFunction(o.response.size) && o.response.size(),
                time = o.response.responseTime,

                requestCount = summary.run.stats.requests.total;

            // compute the response size total
            size && (summary.run.transfers.responseTotal += (size.body || 0 + size.headers || 0));

            // compute average response time
            time && (summary.run.timings.responseAverage =
                ((summary.run.timings.responseAverage * (requestCount - 1) + time) / requestCount));
        });
    },

    attachFailureTrackers: function (summary, emitter) {
        var eventsToTrack = ['beforeIteration', 'iteration', 'beforeItem', 'item', 'beforeScript', 'script',
            'beforePrerequest', 'prerequest', 'beforeRequest', 'request', 'beforeTest', 'test', 'beforeAssertion',
            'assertion'];

        // accumulate failures of all events
        // NOTE that surrogate events (which throw duplicate arguments) are not recorded
        _.forEach(eventsToTrack, function (event) {
            // push failures sent from "before" events
            emitter.on(event, function (err, o) {
                if (!err) { return; }

                var item = o && o.item,
                    source = event;

                // in case of user script error, point to the line and column of the script and its type
                if (event === 'script') {
                    o.event && (source = o.event.listen + '-script');
                    if (err.stacktrace && err.stacktrace[0] && err.stacktrace[0].lineNumber) {
                        source += (':' + (err.stacktrace[0].lineNumber - 2));
                        err.stacktrace[0].columnNumber && (source += (':' + err.stacktrace[0].columnNumber));
                    }
                }
                // assertion errors need to know which assertion in the test was this
                else if (event === 'assertion') {
                    _.has(err, 'index') && (source += (':' + (err.index + 1)));
                    source += ' in test-script';
                }

                // if this is a plain error, convert it to serialised error
                if (err.stack && !err.stacktrace) {
                    err = new SerialiseError(err, true);
                }

                summary.run.failures.push({
                    error: err,
                    at: source,
                    source: item || undefined,
                    parent: item && item.__parent && item.__parent.__parent || undefined,
                    cursor: o.cursor || {}
                });
            });
        });
    }
});

module.exports = RunSummary;
<|endoftext|>