<commit_before>var vec2 = require('../math/vec2')
,   Utils = require('../utils/Utils');

module.exports = AABB;

/**
 * Axis aligned bounding box class.
 * @class AABB
 * @constructor
 * @param {Object}  [options]
 * @param {Array}   [options.upperBound]
 * @param {Array}   [options.lowerBound]
 */
function AABB(options){

    /**
     * The lower bound of the bounding box.
     * @property lowerBound
     * @type {Array}
     */
    this.lowerBound = vec2.create();
    if(options && options.lowerBound){
        vec2.copy(this.lowerBound, options.lowerBound);
    }

    /**
     * The upper bound of the bounding box.
     * @property upperBound
     * @type {Array}
     */
    this.upperBound = vec2.create();
    if(options && options.upperBound){
        vec2.copy(this.upperBound, options.upperBound);
    }
}

var tmp = vec2.create();

/**
 * Set the AABB bounds from a set of points, transformed by the given position and angle.
 * @method setFromPoints
 * @param {Array} points An array of vec2's.
 * @param {Array} position
 * @param {number} angle
 * @param {number} skinSize Some margin to be added to the AABB.
 */
AABB.prototype.setFromPoints = function(points, position, angle, skinSize){
    var l = this.lowerBound,
        u = this.upperBound;

    if(typeof(angle) !== "number"){
        angle = 0;
    }

    // Set to the first point
    if(angle !== 0){
        vec2.rotate(l, points[0], angle);
    } else {
        vec2.copy(l, points[0]);
    }
    vec2.copy(u, l);

    // Compute cosines and sines just once
    var cosAngle = Math.cos(angle),
        sinAngle = Math.sin(angle);
    for(var i = 1; i<points.length; i++){
        var p = points[i];

        if(angle !== 0){
            var x = p[0],
                y = p[1];
            tmp[0] = cosAngle * x -sinAngle * y;
            tmp[1] = sinAngle * x +cosAngle * y;
            p = tmp;
        }

        for(var j=0; j<2; j++){
            if(p[j] > u[j]){
                u[j] = p[j];
            }
            if(p[j] < l[j]){
                l[j] = p[j];
            }
        }
    }

    // Add offset
    if(position){
        vec2.add(this.lowerBound, this.lowerBound, position);
        vec2.add(this.upperBound, this.upperBound, position);
    }

    if(skinSize){
        this.lowerBound[0] -= skinSize;
        this.lowerBound[1] -= skinSize;
        this.upperBound[0] += skinSize;
        this.upperBound[1] += skinSize;
    }
};

/**
 * Copy bounds from an AABB to this AABB
 * @method copy
 * @param  {AABB} aabb
 */
AABB.prototype.copy = function(aabb){
    vec2.copy(this.lowerBound, aabb.lowerBound);
    vec2.copy(this.upperBound, aabb.upperBound);
};

/**
 * Extend this AABB so that it covers the given AABB too.
 * @method extend
 * @param  {AABB} aabb
 */
AABB.prototype.extend = function(aabb){
    // Loop over x and y
    var i = 2;
    while(i--){
        // Extend lower bound
        var l = aabb.lowerBound[i];
        if(this.lowerBound[i] > l){
            this.lowerBound[i] = l;
        }

        // Upper
        var u = aabb.upperBound[i];
        if(this.upperBound[i] < u){
            this.upperBound[i] = u;
        }
    }
};

/**
 * Returns true if the given AABB overlaps this AABB.
 * @method overlaps
 * @param  {AABB} aabb
 * @return {Boolean}
 */
AABB.prototype.overlaps = function(aabb){
    var l1 = this.lowerBound,
        u1 = this.upperBound,
        l2 = aabb.lowerBound,
        u2 = aabb.upperBound;

    //      l2        u2
    //      |---------|
    // |--------|
    // l1       u1

    return ((l2[0] <= u1[0] && u1[0] <= u2[0]) || (l1[0] <= u2[0] && u2[0] <= u1[0])) &&
           ((l2[1] <= u1[1] && u1[1] <= u2[1]) || (l1[1] <= u2[1] && u2[1] <= u1[1]));
};

AABB.prototype.overlapsRay = function(ray){

    var dirfrac = [];
    var t = 0;

    // ray.direction is unit direction vector of ray
    dirfrac[0] = 1 / ray._direction[0];
    dirfrac[1] = 1 / ray._direction[1];

    // this.lowerBound is the corner of AABB with minimal coordinates - left bottom, rt is maximal corner
    var t1 = (this.lowerBound[0] - ray.from[0]) * dirfrac[0];
    var t2 = (this.upperBound[0] - ray.from[0]) * dirfrac[0];
    var t3 = (this.lowerBound[1] - ray.from[1]) * dirfrac[1];
    var t4 = (this.upperBound[1] - ray.from[1]) * dirfrac[1];

    var tmin = Math.max(Math.max(Math.min(t1, t2), Math.min(t3, t4)));
    var tmax = Math.min(Math.min(Math.max(t1, t2), Math.max(t3, t4)));

    // if tmax < 0, ray (line) is intersecting AABB, but whole AABB is behing us
    if (tmax < 0){
        t = tmax;
        return false;
    }

    // if tmin > tmax, ray doesn't intersect AABB
    if (tmin > tmax){
        t = tmax;
        return false;
    }

    t = tmin;
    return true;
};<commit_msg>added AABB.prototype.containsPoint and some docs<commit_after>var vec2 = require('../math/vec2')
,   Utils = require('../utils/Utils');

module.exports = AABB;

/**
 * Axis aligned bounding box class.
 * @class AABB
 * @constructor
 * @param {Object}  [options]
 * @param {Array}   [options.upperBound]
 * @param {Array}   [options.lowerBound]
 */
function AABB(options){

    /**
     * The lower bound of the bounding box.
     * @property lowerBound
     * @type {Array}
     */
    this.lowerBound = vec2.create();
    if(options && options.lowerBound){
        vec2.copy(this.lowerBound, options.lowerBound);
    }

    /**
     * The upper bound of the bounding box.
     * @property upperBound
     * @type {Array}
     */
    this.upperBound = vec2.create();
    if(options && options.upperBound){
        vec2.copy(this.upperBound, options.upperBound);
    }
}

var tmp = vec2.create();

/**
 * Set the AABB bounds from a set of points, transformed by the given position and angle.
 * @method setFromPoints
 * @param {Array} points An array of vec2's.
 * @param {Array} position
 * @param {number} angle
 * @param {number} skinSize Some margin to be added to the AABB.
 */
AABB.prototype.setFromPoints = function(points, position, angle, skinSize){
    var l = this.lowerBound,
        u = this.upperBound;

    if(typeof(angle) !== "number"){
        angle = 0;
    }

    // Set to the first point
    if(angle !== 0){
        vec2.rotate(l, points[0], angle);
    } else {
        vec2.copy(l, points[0]);
    }
    vec2.copy(u, l);

    // Compute cosines and sines just once
    var cosAngle = Math.cos(angle),
        sinAngle = Math.sin(angle);
    for(var i = 1; i<points.length; i++){
        var p = points[i];

        if(angle !== 0){
            var x = p[0],
                y = p[1];
            tmp[0] = cosAngle * x -sinAngle * y;
            tmp[1] = sinAngle * x +cosAngle * y;
            p = tmp;
        }

        for(var j=0; j<2; j++){
            if(p[j] > u[j]){
                u[j] = p[j];
            }
            if(p[j] < l[j]){
                l[j] = p[j];
            }
        }
    }

    // Add offset
    if(position){
        vec2.add(this.lowerBound, this.lowerBound, position);
        vec2.add(this.upperBound, this.upperBound, position);
    }

    if(skinSize){
        this.lowerBound[0] -= skinSize;
        this.lowerBound[1] -= skinSize;
        this.upperBound[0] += skinSize;
        this.upperBound[1] += skinSize;
    }
};

/**
 * Copy bounds from an AABB to this AABB
 * @method copy
 * @param  {AABB} aabb
 */
AABB.prototype.copy = function(aabb){
    vec2.copy(this.lowerBound, aabb.lowerBound);
    vec2.copy(this.upperBound, aabb.upperBound);
};

/**
 * Extend this AABB so that it covers the given AABB too.
 * @method extend
 * @param  {AABB} aabb
 */
AABB.prototype.extend = function(aabb){
    // Loop over x and y
    var i = 2;
    while(i--){
        // Extend lower bound
        var l = aabb.lowerBound[i];
        if(this.lowerBound[i] > l){
            this.lowerBound[i] = l;
        }

        // Upper
        var u = aabb.upperBound[i];
        if(this.upperBound[i] < u){
            this.upperBound[i] = u;
        }
    }
};

/**
 * Returns true if the given AABB overlaps this AABB.
 * @method overlaps
 * @param  {AABB} aabb
 * @return {Boolean}
 */
AABB.prototype.overlaps = function(aabb){
    var l1 = this.lowerBound,
        u1 = this.upperBound,
        l2 = aabb.lowerBound,
        u2 = aabb.upperBound;

    //      l2        u2
    //      |---------|
    // |--------|
    // l1       u1

    return ((l2[0] <= u1[0] && u1[0] <= u2[0]) || (l1[0] <= u2[0] && u2[0] <= u1[0])) &&
           ((l2[1] <= u1[1] && u1[1] <= u2[1]) || (l1[1] <= u2[1] && u2[1] <= u1[1]));
};

/**
 * @method containsPoint
 * @param  {Array} point
 * @return {boolean}
 */
AABB.prototype.containsPoint = function(point){
    var l = this.lowerBound,
        u = this.upperBound;
    return l[0] <= point[0] && point[0] <= u[0] && l[1] <= point[1] && point[1] <= u[1];
};

/**
 * Check if the AABB is hit by a ray.
 * @method overlapsRay
 * @param  {Ray} ray
 * @return {number} -1 if no hit, a number between 0 and 1 if hit.
 */
AABB.prototype.overlapsRay = function(ray){
    var t = 0;

    // ray.direction is unit direction vector of ray
    var dirFracX = 1 / ray.direction[0];
    var dirFracY = 1 / ray.direction[1];

    // this.lowerBound is the corner of AABB with minimal coordinates - left bottom, rt is maximal corner
    var t1 = (this.lowerBound[0] - ray.from[0]) * dirFracX;
    var t2 = (this.upperBound[0] - ray.from[0]) * dirFracX;
    var t3 = (this.lowerBound[1] - ray.from[1]) * dirFracY;
    var t4 = (this.upperBound[1] - ray.from[1]) * dirFracY;

    var tmin = Math.max(Math.max(Math.min(t1, t2), Math.min(t3, t4)));
    var tmax = Math.min(Math.min(Math.max(t1, t2), Math.max(t3, t4)));

    // if tmax < 0, ray (line) is intersecting AABB, but whole AABB is behing us
    if (tmax < 0){
        //t = tmax;
        return -1;
    }

    // if tmin > tmax, ray doesn't intersect AABB
    if (tmin > tmax){
        //t = tmax;
        return -1;
    }

    return tmin;
};<|endoftext|>


<commit_before>var fs = require('fs');

module.exports = function logger(options) {
  var defaults = {
    include_meta: true,
    file_path: false
  };

  if(typeof options === 'object') {
    options = options || defaults;
  } else if(typeof options === 'string') {
    options = {
      file_path: options
    };
  } else {
    options = defaults;
  }

  return function logger(entry, acc, next) {
    var to_log = entry.data;

    if(options.include_meta) {
      to_log = JSON.stringify(entry) + '\n';
    }

    if(options.file_path) {
      var write_stream = fs.createWriteStream(options.file_path, {
        flags: "a",
        encoding: "utf8",
        mode: '0666'
      });

      write_stream.write(to_log);
    } else {
      console.log(to_log);
    }

    next(entry, acc);
  }
};
<commit_msg>Close write stream and added log_title option<commit_after>var fs = require('fs');

module.exports = function logger(options) {
  var defaults = {
    include_meta: true,
    file_path: false
  };

  if(typeof options === 'object') {
    options = options || defaults;
  } else if(typeof options === 'string') {
    options = {
      file_path: options
    };
  } else {
    options = defaults;
  }

  return function logger(entry, acc, next) {
    var to_log = entry.data + '\n';

    if(options.include_meta) {
      to_log = JSON.stringify(entry) + '\n';
    }

    if(options.log_title) {
      to_log = '[' + entry.log.name + '] ' + to_log;
    }

    if(options.file_path) {
      var write_stream = fs.createWriteStream(options.file_path, {
        flags: "a",
        encoding: "utf8",
        mode: '0666'
      });

      write_stream.write(to_log);
      write_stream.end();
    } else {
      console.log(to_log);
    }

    next(entry, acc);
  }
};
<|endoftext|>


<commit_before>'use strict';

/**
 * Slack integration for in-game chat. Allows coupling game groups
 * (e.g. global, live help) with Slack channels, using the Slack
 * real-time messaging API.
 *
 * @module
 */

// public interface
module.exports = {
	init: init,
	shutdown: shutdown,
	getClient: getClient,
	patchGroup: patchGroup,
	handleGroupMsg: handleGroupMsg,
};

var Slack = require('slack-client');
var config = require('config');
var pers = require('data/pers');
var RQ = require('data/RequestQueue');
var rpc = require('data/rpc');
var util = require('util');
var utils = require('utils');

var slack;
// bidirectional mapping between GS goups and Slack channels/groups
var groupToChannel = {};
var channelToGroup = {};

/**
 * Initialize Slack integration (if configured).
 * Should be called on each GS worker during startup.
 */
function init() {
	var token = config.get('slack:chat:token');
	if (!token) {
		log.info('Slack integration not configured (no token)');
		return;
	}
	log.debug('connecting to Slack');
	slack = new Slack(token);
	slack.logger = log.child({lib: 'slack-client'});
	slack.on('open', onSlackOpen);
	slack.on('error', onSlackError);
	slack.on('message', onSlackMessage);
	slack.login();
}

/**
 * Shuts down integration, closes connection to Slack.
 */
function shutdown(done) {
	if (slack) {
		log.info('Slack client shutdown');
		slack.disconnect();
	}
	if (done) done();
}

/**
 * After successful login, initializes the links between GS groups and
 * Slack channels/groups as configured.
 * @private
 */
function onSlackOpen() {
	log.info('connected to Slack as %s', slack.self.name);
	var groups = config.get('slack:chat:groups', {});
	for (var tsid in groups) {
		var chanName = groups[tsid];
		log.debug('hooking up group %s to Slack channel #%s', tsid, chanName);
		connectGroup(tsid, chanName, slack.channels);
		// no channel found? try groups, then
		if (!groupToChannel[tsid]) {
			connectGroup(tsid, chanName, slack.groups);
		}
		if (!groupToChannel[tsid]) {
			log.error('channel/group %s not found', chanName);
		}
	}
}

/**
 * Sets up the mapping between a {@link Group} and a Slack channel or
 * group. The reverse mapping (for incoming Slack messages) is only set
 * up if the GS instance is responsible for that particular group.
 *
 * @param {string} groupTsid TSID of the group to connect
 * @param {string} cogName Slack channel or group name to connect it to
 * @param {object[]} cogs a list of channel or group objects
 * @private
 */
function connectGroup(groupTsid, cogName, cogs) {
	for (var k in cogs) {
		var cog = cogs[k];
		if (cog.name === cogName) {
			if (cog.is_channel && !cog.is_member) {
				log.error('bot is not a member of #%s', cog.name);
			}
			else if (cog.is_archived) {
				log.error('channel/group %s is archived', cog.name);
			}
			else {
				groupToChannel[groupTsid] = cog;
				if (rpc.isLocal(groupTsid)) {
					channelToGroup[k] = groupTsid;
				}
				break;
			}
		}
	}
}

function onSlackError(err) {
	var msg = 'Slack client error';
	if (typeof err === 'string') {
		log.error('%s: %s', msg, err);
	}
	else {
		log.error({error: err}, msg);
	}
}

/**
 * Handles incoming messages from Slack. Figures out if the message is
 * something we're interested in (regular message sent to one of the
 * connected groups), and forwards it to the respective group if so.
 * @private
 */
function onSlackMessage(msg) {
	log.trace({data: msg}, 'incoming Slack message');
	if (msg.type !== 'message') return;  // ignore meta stuff
	// is this a connected group managed by this GS instance?
	var groupTsid = channelToGroup[msg.channel];
	if (groupTsid) {
		// special handling for edits of earlier messages
		if (msg.subtype === 'message_changed' && msg.message) {
			msg = msg.message;  // for edits, message prop seems to contain the actual payload
			msg.text = '[EDIT] ' + msg.text;
		}
		// prepare message payload
		var user = slack.getUserByID(msg.user);
		var out = {
			type: 'pc_groups_chat',
			tsid: groupTsid,
			pc: {
				tsid: 'PSLACK' + user.id,  // unique pseudo TSID so client assigns different colors
				label: user.name,
			},
			txt: processMsgText(msg.text),
		};
		utils.addNonEnumerable(out, 'fromSlack', true);
		// dispatch message to group in separate request context
		RQ.getGlobal('persget').push('slackMsg.getGroup',
			pers.get.bind(null, groupTsid),
			function cb(err, group) {
				if (err) {
					return log.error(err, 'group not found');
				}
				group.getRQ().push('slackMsg.dispatch',
					group.chat_send_msg.bind(null, out),
					function cb(err, res) {
						if (err) {
							log.error({err: err, data: msg}, 'error ' +
								'dispatching group chat message from Slack');
						}
					}
				);
			}
		);
	}
}

/**
 * Performs some adjustments on incoming chat messages from Slack
 * (specifically, replaces user/channel/group references with a
 * human-readable equivalent).
 * @private
 */
function processMsgText(text) {
	return text.replace(/<[^>]*>/gi, function replacer(match) {
		var tokens = /<([@#]?)([^\|]+)\|?([^>]*)>/.exec(match);
		// parenthesized substring matches: tokens[1] is the prefix (@ or #),
		// tokens[2] is the ID, and tokens[3] is the label (if available)
		var prefix = tokens[1] ? tokens[1] : '';
		var label = tokens[3];
		if (!label) {
			var id = tokens[2];
			switch (id[0]) {
				case 'U':
					label = slack.getUserByID(id) ? slack.getUserByID(id).name : id;
					break;
				case 'C':
					label = slack.getChannelByID(id) ? slack.getChannelByID(id).name : id;
					break;
				case 'G':
					label = slack.getGroupByID(id) ? slack.getGroupByID(id).name : id;
					break;
				default:
					label = id;
					break;
			}
		}
		return prefix + label;
	});
}

/**
 * Returns a reference to the Slack client (for testing/debugging).
 *
 * @returns {object} reference to the Slack client
 * @private
 */
function getClient() {
	return slack;
}

/**
 * Forwards a group chat message to the Slack channel/group configured
 * for it (if any).
 *
 * @param {object} msg the chat message request (as sent from GSJS to
 *        the clients of players in the group)
 */
function handleGroupMsg(msg) {
	if (msg.type === 'pc_groups_chat' && !msg.fromSlack) {
		var channel = groupToChannel[msg.tsid];
		if (channel) {
			channel.send(util.format('*[%s]* %s', msg.pc.label, msg.txt));
		}
	}
}

/**
 * If Slack integration is configured for the given group, this
 * modifies its function reponsible for generating the list of chat
 * participants (i.e. the output of `/who`) to include Slack users.
 * @private
 */
function patchGroup(group) {
	if (!groupToChannel[group.tsid]) return;
	// monkey patch the function generating /who output
	var orig = group.chat_get_roster_msg;
	utils.addNonEnumerable(group, 'chat_get_roster_msg',
		//jscs:disable requireCamelCaseOrUpperCaseIdentifiers
		function chat_get_roster_msg() {
			var channel = groupToChannel[group.tsid];
			var roster = utils.shallowCopy(group.chat_roster);
			for (var i = 0; i < channel.members.length; i++) {
				var user = slack.getUserByID(channel.members[i]);
				if (user.presence === 'active' && user.id !== slack.self.id) {
					roster[user.id] = {label: user.name + ' (Slack)'};
				}
			}
			return orig.call({chat_roster: roster});
		}
		//jscs:enable requireCamelCaseOrUpperCaseIdentifiers
	);
}
<commit_msg>Fix Slack chat integration<commit_after>'use strict';

/**
 * Slack integration for in-game chat. Allows coupling game groups
 * (e.g. global, live help) with Slack channels, using the Slack
 * real-time messaging API.
 *
 * @module
 */

// public interface
module.exports = {
	init: init,
	shutdown: shutdown,
	getClient: getClient,
	patchGroup: patchGroup,
	handleGroupMsg: handleGroupMsg,
};

var Slack = require('slack-client');
var config = require('config');
var pers = require('data/pers');
var RQ = require('data/RequestQueue');
var rpc = require('data/rpc');
var util = require('util');
var utils = require('utils');

var slack;
// bidirectional mapping between GS goups and Slack channels/groups
var groupToChannel = {};
var channelToGroup = {};

/**
 * Initialize Slack integration (if configured).
 * Should be called on each GS worker during startup.
 */
function init() {
	var token = config.get('slack:chat:token');
	if (!token) {
		log.info('Slack integration not configured (no token)');
		return;
	}
	log.debug('connecting to Slack');
	slack = new Slack(token);
	slack.logger = log.child({lib: 'slack-client'});
	slack.on('open', onSlackOpen);
	slack.on('error', onSlackError);
	slack.on('message', onSlackMessage);
	slack.login();
}

/**
 * Shuts down integration, closes connection to Slack.
 */
function shutdown(done) {
	if (slack) {
		log.info('Slack client shutdown');
		slack.disconnect();
	}
	if (done) done();
}

/**
 * After successful login, initializes the links between GS groups and
 * Slack channels/groups as configured.
 * @private
 */
function onSlackOpen() {
	log.info('connected to Slack as %s', slack.self.name);
	var groups = config.get('slack:chat:groups', {});
	for (var tsid in groups) {
		var chanName = groups[tsid];
		log.debug('hooking up group %s to Slack channel #%s', tsid, chanName);
		connectGroup(tsid, chanName, slack.channels);
		// no channel found? try groups, then
		if (!groupToChannel[tsid]) {
			connectGroup(tsid, chanName, slack.groups);
		}
		if (!groupToChannel[tsid]) {
			log.error('channel/group %s not found', chanName);
		}
	}
}

/**
 * Sets up the mapping between a {@link Group} and a Slack channel or
 * group. The reverse mapping (for incoming Slack messages) is only set
 * up if the GS instance is responsible for that particular group.
 *
 * @param {string} groupTsid TSID of the group to connect
 * @param {string} cogName Slack channel or group name to connect it to
 * @param {object[]} cogs a list of channel or group objects
 * @private
 */
function connectGroup(groupTsid, cogName, cogs) {
	for (var k in cogs) {
		var cog = cogs[k];
		if (cog.name === cogName) {
			if (cog.is_channel && !cog.is_member) {
				log.error('bot is not a member of #%s', cog.name);
			}
			else if (cog.is_archived) {
				log.error('channel/group %s is archived', cog.name);
			}
			else {
				groupToChannel[groupTsid] = cog;
				if (rpc.isLocal(groupTsid)) {
					channelToGroup[k] = groupTsid;
				}
				break;
			}
		}
	}
}

function onSlackError(err) {
	var msg = 'Slack client error';
	if (typeof err === 'string') {
		log.error('%s: %s', msg, err);
	}
	else {
		log.error({error: err}, msg);
	}
}

/**
 * Handles incoming messages from Slack. Figures out if the message is
 * something we're interested in (regular message sent to one of the
 * connected groups), and forwards it to the respective group if so.
 * @private
 */
function onSlackMessage(msg) {
	log.trace({data: msg}, 'incoming Slack message');
	if (msg.type !== 'message') return;  // ignore meta stuff
	// is this a connected group managed by this GS instance?
	var groupTsid = channelToGroup[msg.channel];
	if (!groupTsid) return;
	// special handling for edits/removals of earlier messages
	if (msg.subtype === 'message_deleted') return;
	if (msg.subtype === 'message_changed' && msg.message) {
		msg = msg.message;  // for edits, message prop seems to contain the actual payload
		msg.text = '[EDIT] ' + msg.text;
	}
	// prepare message payload
	var user = slack.getUserByID(msg.user);
	var out = {
		type: 'pc_groups_chat',
		tsid: groupTsid,
		pc: {
			//TODO user is undefined when a Slack message is deleted!
			tsid: 'PSLACK' + user.id,  // unique pseudo TSID so client assigns different colors
			label: user.name,
		},
		txt: processMsgText(msg.text),
	};
	utils.addNonEnumerable(out, 'fromSlack', true);
	// dispatch message to group in separate request context
	RQ.getGlobal('persget').push('slackMsg.getGroup',
		pers.get.bind(null, groupTsid),
		function cb(err, group) {
			if (err) {
				return log.error(err, 'group not found');
			}
			group.getRQ().push('slackMsg.dispatch',
				group.chat_send_msg.bind(group, out),
				function cb(err, res) {
					if (err) {
						log.error({err: err, data: msg}, 'error ' +
							'dispatching group chat message from Slack');
					}
				}
			);
		}
	);
}

/**
 * Performs some adjustments on incoming chat messages from Slack
 * (specifically, replaces user/channel/group references with a
 * human-readable equivalent).
 * @private
 */
function processMsgText(text) {
	return text.replace(/<[^>]*>/gi, function replacer(match) {
		var tokens = /<([@#]?)([^\|]+)\|?([^>]*)>/.exec(match);
		// parenthesized substring matches: tokens[1] is the prefix (@ or #),
		// tokens[2] is the ID, and tokens[3] is the label (if available)
		var prefix = tokens[1] ? tokens[1] : '';
		var label = tokens[3];
		if (!label) {
			var id = tokens[2];
			switch (id[0]) {
				case 'U':
					label = slack.getUserByID(id) ? slack.getUserByID(id).name : id;
					break;
				case 'C':
					label = slack.getChannelByID(id) ? slack.getChannelByID(id).name : id;
					break;
				case 'G':
					label = slack.getGroupByID(id) ? slack.getGroupByID(id).name : id;
					break;
				default:
					label = id;
					break;
			}
		}
		return prefix + label;
	});
}

/**
 * Returns a reference to the Slack client (for testing/debugging).
 *
 * @returns {object} reference to the Slack client
 * @private
 */
function getClient() {
	return slack;
}

/**
 * Forwards a group chat message to the Slack channel/group configured
 * for it (if any).
 *
 * @param {object} msg the chat message request (as sent from GSJS to
 *        the clients of players in the group)
 */
function handleGroupMsg(msg) {
	if (msg.type === 'pc_groups_chat' && !msg.fromSlack) {
		var channel = groupToChannel[msg.tsid];
		if (channel) {
			channel.send(util.format('*[%s]* %s', msg.pc.label, msg.txt));
		}
	}
}

/**
 * If Slack integration is configured for the given group, this
 * modifies its function reponsible for generating the list of chat
 * participants (i.e. the output of `/who`) to include Slack users.
 * @private
 */
function patchGroup(group) {
	if (!groupToChannel[group.tsid]) return;
	// monkey patch the function generating /who output
	var orig = group.chat_get_roster_msg;
	utils.addNonEnumerable(group, 'chat_get_roster_msg',
		//jscs:disable requireCamelCaseOrUpperCaseIdentifiers
		function chat_get_roster_msg() {
			var channel = groupToChannel[group.tsid];
			var roster = utils.shallowCopy(group.chat_roster);
			for (var i = 0; i < channel.members.length; i++) {
				var user = slack.getUserByID(channel.members[i]);
				if (user.presence === 'active' && user.id !== slack.self.id) {
					roster[user.id] = {label: user.name + ' (Slack)'};
				}
			}
			return orig.call({chat_roster: roster});
		}
		//jscs:enable requireCamelCaseOrUpperCaseIdentifiers
	);
}
<|endoftext|>


<commit_before>/*global Promise:true*/
'use strict';

var EventSource = global.EventSource || require('eventsource')
  , url = require('url')
  ;

var _source;

var Streamable = {
  url: {
    get: function () {
      var prefix = '';
      if (this.prefix) {
        prefix = this.stream.prefix || this.prefix;
        if (prefix[0] !== '/') {
          prefix = '/' + prefix;
        }
        if (prefix[prefix.length - 1] === '/') {
          prefix = prefix.slice(0, -1);
        }
      }
      if (this.api_version) {
        prefix += '/' + this.api_version;
      }

      return url.format({
        protocol: this.stream.protocol || this.protocol,
        hostname: this.stream.host || this.host,
        port: this.stream.port || this.port,
        pathname: prefix + this.path
      });
    }
  },

  listen: {
    value: function () {
      return new Promise(function (resolve, reject) {
        var source
          , streamUrl = this.url;

        var query = [];

        if (!this.noQueryTokens && this.token.token) {
          query.push({
            key: 'x-auth-token',
            value: this.token.token
          });
        }

        if (!this.noQueryTokens && this.key.key) {
          query.push({
            key: 'st2-api-key',
            value: this.key.key
          });
        }

        if (query.length) {
          streamUrl += '?' + query.map(function (param) {
            return param.key + '=' + param.value;
          }).join('&');
        }

        try {
          source = _source = this.cacheStream && _source || new EventSource(streamUrl, {
            rejectUnauthorized: this.rejectUnauthorized,
            withCredentials: true
          });
        } catch (e) {
          return reject(e);
        }
        return resolve(source);
      }.bind(this));
    }
  }
};

module.exports = Streamable;
<commit_msg>Add wait method to streamable mixin<commit_after>/*global Promise:true*/
'use strict';

var EventSource = global.EventSource || require('eventsource')
  , url = require('url')
  ;

var _source;

var Streamable = {
  url: {
    get: function () {
      var prefix = '';
      if (this.prefix) {
        prefix = this.stream.prefix || this.prefix;
        if (prefix[0] !== '/') {
          prefix = '/' + prefix;
        }
        if (prefix[prefix.length - 1] === '/') {
          prefix = prefix.slice(0, -1);
        }
      }
      if (this.api_version) {
        prefix += '/' + this.api_version;
      }

      return url.format({
        protocol: this.stream.protocol || this.protocol,
        hostname: this.stream.host || this.host,
        port: this.stream.port || this.port,
        pathname: prefix + this.path
      });
    }
  },

  listen: {
    value: function () {
      return new Promise(function (resolve, reject) {
        var source
          , streamUrl = this.url;

        var query = [];

        if (!this.noQueryTokens && this.token.token) {
          query.push({
            key: 'x-auth-token',
            value: this.token.token
          });
        }

        if (!this.noQueryTokens && this.key.key) {
          query.push({
            key: 'st2-api-key',
            value: this.key.key
          });
        }

        if (query.length) {
          streamUrl += '?' + query.map(function (param) {
            return param.key + '=' + param.value;
          }).join('&');
        }

        try {
          source = _source = this.cacheStream && _source || new EventSource(streamUrl, {
            rejectUnauthorized: this.rejectUnauthorized,
            withCredentials: true
          });
        } catch (e) {
          return reject(e);
        }
        return resolve(source);
      }.bind(this));
    }
  },

  wait: {
    value: function (eventType, condition) {
      return this.listen()
        .then(function (source) {
          var listener;

          return new Promise((resolve, reject) => {
            listener = event => {
              var record = JSON.parse(event.data);

              var result = condition(record);

              if (result === true) {
                resolve(record);
              } else if (result === false) {
                reject(record);
              }
            };
            source.addEventListener(eventType, listener);
          }).then(record => {
            source.removeEventListener(eventType, listener);

            return record;
          })
          .catch(record => {
            source.removeEventListener(eventType, listener);

            throw record;
          });
        });
    }
  }
};

module.exports = Streamable;
<|endoftext|>


<commit_before>var _ = require('lodash');
var util = require('util');
var EventEmitter = require('events').EventEmitter;

var utils = require('./utils');
var Manager = require('./manager');
var Widget = require('./widget');

function WidgetType(manager, opts) {
    this.id = _.uniqueId('widgetsType');

    this.manager = manager;
    this.editor = this.manager.editor;
    this.opts = opts || {};
    this.enabled = false;

    this.onChange = _.throttle(this._onChange, this.debounceWait);

    _.bindAll(this);
}
util.inherits(WidgetType, EventEmitter);

WidgetType.prototype.className = 'widget';
WidgetType.prototype.debounceWait = 600;

// Return all occurrences in a text
WidgetType.prototype.find = function(text) {
    return [];
};

// Compare props of markers
WidgetType.prototype.compareProps = function(p1, p2) {
    return _.isEqual(p1, p2);
};

// If edition is not inside a marker, detect section to process
// For example if the widget is inline, we can limit this to a single line
// Should return a range
WidgetType.prototype.findEditRange = function(range) {
    return range;
};

// Create the dom element for a specific widget
WidgetType.prototype.createElement = function(widget) {
    var el = document.createElement('span');
    return el;
};

// Create a new widget
WidgetType.prototype.createWidget = function(range, props) {
    var that = this;
    var markers, cursor, doc, exists;

    doc = this.doc();

    // Check if the marker already exists
    markers = doc.findMarksAt(range.from);
    exists = _.find(markers, function(marker) {
        return (!marker.unrendered &&
            marker.xType == that.id &&
            that.compareProps(marker.xProps, props));
    });
    if (exists) return;

    // Is cursor inside?
    cursor = doc.getCursor();
    if (utils.posInsideRange(cursor, range)) {
        this.unrenderRange(range, props);
    } else {
        var widget = new Widget(this, range, props);

        widget.marker.on('clear', function(_from, _to) {
            if (!that.enabled) return;

            that.unrenderRange(utils.Range(_from, _to), props);
        });

        that.prepareWidget(widget);
    }
};

// Prepare a widget
// Can be extend by mixin (see "menu")
WidgetType.prototype.prepareWidget = function(widget) {

}

// Render as text a marker
WidgetType.prototype.unrenderRange = function(range, props) {
    var doc = this.doc();
    var marker = this.bindMarker(doc.markText(range.from, range.to), props);
    this.manager.setUnrendered(marker);
}

// Bind a marker to be identifiable
WidgetType.prototype.bindMarker = function(marker, props) {
    marker.xProps = props;
    marker.xType = this.id;
    return marker;
};

// Create a marker for a widget
WidgetType.prototype.createWidgetMarker = function(from, to, props, opts) {
    var marker = this.doc().markText(from, to, _.extend(opts || {}, {
        clearOnEnter: true,
        atomic: true
    }));
    return this.bindMarker(marker, props);
};

// Convert a list of matches in widgets
WidgetType.prototype.processMatches = function(from, matches) {
    if (matches.length == 0) return;

    var that = this;

    that.editor.operation(function() {

        var fromIndex = from? that.editor.indexFromPos(from) : 0;
        var doc = that.doc();

        _.each(matches, function(match) {
            var range = utils.Range(
                doc.posFromIndex(fromIndex + match.start),
                doc.posFromIndex(fromIndex + match.end)
            );

            that.createWidget(range, match.props);
        });
    });
};

// Apply widgets in a specific sections
WidgetType.prototype.processRange = function(range) {
    // Extract text
    var text = this.doc().getRange(range.from, range.to);

    // Extract matches
    var matches = this.find(text, range);

    // Process matches
    this.processMatches(range.from, matches);
};

// Render all widgets
WidgetType.prototype.renderAll = function() {
    var that = this;

    // Extract text
    var text = this.doc().getValue();

    // Extract matches
    var matches = this.find(text);

    this.processMatches({
        line: 0,
        ch: 0
    }, matches);
};

// Unrender all widgets
WidgetType.prototype.unrenderAll = function() {
    var that = this;

    _.each(this.doc().getAllMarks(), function(mark) {
        if (mark.xType != that.id) return;
        mark.clear();
    });
};

// Cleanup all markers in a specified range
// Return the largest range that contain markers
WidgetType.prototype.cleanupMarkers = function(origin) {
    var that = this;
    var range = { from: origin.from, to: origin.to };

    _.each(this.doc().getAllMarks(), function(mark) {
        if (mark.xType != that.id) return;

        var markRange = mark.find();
        if (!markRange) return;

        var isInRange = markRange.line? posInsideRange(markRange, origin) : utils.rangesOverlap(markRange, origin);
        if (!isInRange) return;

        if (utils.posIsBefore(markRange.from, range.from)) {
            range.from = markRange.from;
        }
        if (utils.posIsBefore(range.to, markRange.to)) {
            range.to = markRange.to;
        }

        mark.clear();
    });

    return range;
};

// Process change from the editor
WidgetType.prototype.processChange = function(change) {
    if (change.origin == 'setValue') {
        return this.renderAll();
    }

    // Calcul range of change (change is pre-operation)
    var changeRange = {
        from: utils.Pos(change.from.line, change.from.ch),
        to: utils.Pos(change.to.line + (change.text.length - 1), change.to.ch + _.last(change.text).length)
    };

    // Determine large range and cleanup markers in it
    var range = this.cleanupMarkers(changeRange);

    // If nothing found, find larger section to process
    if (utils.posEqual(changeRange, range)) {
        range = this.findEditRange(changeRange);
        this.cleanupMarkers(changeRange);
    }

    // Update markers in this range
    this.processRange(range);
};

// Editor content changed
// Debounce processing
WidgetType.prototype._onChange = function(inst, change) {
    this.processChange(change);
};

WidgetType.prototype.enable = function() {
    if (this.enabled) return;

    this.enabled = true;
    this.manager.editor.on('change', this.onChange);
    this.renderAll();
};

WidgetType.prototype.disable = function() {
    if (!this.enabled) return;

    this.enabled = false;
    this.manager.editor.off('change', this.onChange);
    this.unrenderAll();
};

// Return the concerned document
WidgetType.prototype.doc = function() {
    return this.manager.doc();
};

// Define option to CodeMirror
function setupOption(Type, CodeMirror) {
    CodeMirror.defineOption(Type.prototype.className + 'Widget', false, function(cm, val, old) {
        if (old == CodeMirror.Init) old = false;
        if (val == old) return;

        // If editor has no manager, add one
        if (!cm._widgets) Manager(cm);

        if (val) {
            cm._widgets.enable(Type, val);
        } else {
            cm._widgets.disable(Type);
        }
    });
}

// Create an new type
function createType(type) {
    var CustomType = function() {
        WidgetType.apply(this, arguments);
    };
    util.inherits(CustomType, WidgetType);

    CustomType.prototype = utils.composeMixins(
        CustomType.prototype,
        (type.mixins || []).concat([type]),
        {
            composables: ['enable', 'disable', 'prepareWidget']
        }
    );

    CustomType.id = _.uniqueId('WidgetType');
    CustomType.setupOption = _.partial(setupOption, CustomType);

    return CustomType;
}

module.exports = {
    WidgetType: WidgetType,
    create: createType
};
<commit_msg>Rollback to debouncing<commit_after>var _ = require('lodash');
var util = require('util');
var EventEmitter = require('events').EventEmitter;

var utils = require('./utils');
var Manager = require('./manager');
var Widget = require('./widget');

function WidgetType(manager, opts) {
    this.id = _.uniqueId('widgetsType');

    this.manager = manager;
    this.editor = this.manager.editor;
    this.opts = opts || {};
    this.enabled = false;

    this.onChange = _.debounce(this._onChange, this.debounceWait);

    _.bindAll(this);
}
util.inherits(WidgetType, EventEmitter);

WidgetType.prototype.className = 'widget';
WidgetType.prototype.debounceWait = 500;

// Return all occurrences in a text
WidgetType.prototype.find = function(text) {
    return [];
};

// Compare props of markers
WidgetType.prototype.compareProps = function(p1, p2) {
    return _.isEqual(p1, p2);
};

// If edition is not inside a marker, detect section to process
// For example if the widget is inline, we can limit this to a single line
// Should return a range
WidgetType.prototype.findEditRange = function(range) {
    return range;
};

// Create the dom element for a specific widget
WidgetType.prototype.createElement = function(widget) {
    var el = document.createElement('span');
    return el;
};

// Create a new widget
WidgetType.prototype.createWidget = function(range, props) {
    var that = this;
    var markers, cursor, doc, exists;

    doc = this.doc();

    // Check if the marker already exists
    markers = doc.findMarksAt(range.from);
    exists = _.find(markers, function(marker) {
        return (!marker.unrendered &&
            marker.xType == that.id &&
            that.compareProps(marker.xProps, props));
    });
    if (exists) return;

    // Is cursor inside?
    cursor = doc.getCursor();
    if (utils.posInsideRange(cursor, range)) {
        this.unrenderRange(range, props);
    } else {
        var widget = new Widget(this, range, props);

        widget.marker.on('clear', function(_from, _to) {
            if (!that.enabled) return;

            that.unrenderRange(utils.Range(_from, _to), props);
        });

        that.prepareWidget(widget);
    }
};

// Prepare a widget
// Can be extend by mixin (see "menu")
WidgetType.prototype.prepareWidget = function(widget) {

}

// Render as text a marker
WidgetType.prototype.unrenderRange = function(range, props) {
    var doc = this.doc();
    var marker = this.bindMarker(doc.markText(range.from, range.to), props);
    this.manager.setUnrendered(marker);
}

// Bind a marker to be identifiable
WidgetType.prototype.bindMarker = function(marker, props) {
    marker.xProps = props;
    marker.xType = this.id;
    return marker;
};

// Create a marker for a widget
WidgetType.prototype.createWidgetMarker = function(from, to, props, opts) {
    var marker = this.doc().markText(from, to, _.extend(opts || {}, {
        clearOnEnter: true,
        atomic: true
    }));
    return this.bindMarker(marker, props);
};

// Convert a list of matches in widgets
WidgetType.prototype.processMatches = function(from, matches) {
    if (matches.length == 0) return;

    var that = this;

    that.editor.operation(function() {

        var fromIndex = from? that.editor.indexFromPos(from) : 0;
        var doc = that.doc();

        _.each(matches, function(match) {
            var range = utils.Range(
                doc.posFromIndex(fromIndex + match.start),
                doc.posFromIndex(fromIndex + match.end)
            );

            that.createWidget(range, match.props);
        });
    });
};

// Apply widgets in a specific sections
WidgetType.prototype.processRange = function(range) {
    // Extract text
    var text = this.doc().getRange(range.from, range.to);

    // Extract matches
    var matches = this.find(text, range);

    // Process matches
    this.processMatches(range.from, matches);
};

// Render all widgets
WidgetType.prototype.renderAll = function() {
    var that = this;

    // Extract text
    var text = this.doc().getValue();

    // Extract matches
    var matches = this.find(text);

    this.processMatches({
        line: 0,
        ch: 0
    }, matches);
};

// Unrender all widgets
WidgetType.prototype.unrenderAll = function() {
    var that = this;

    _.each(this.doc().getAllMarks(), function(mark) {
        if (mark.xType != that.id) return;
        mark.clear();
    });
};

// Cleanup all markers in a specified range
// Return the largest range that contain markers
WidgetType.prototype.cleanupMarkers = function(origin) {
    var that = this;
    var range = { from: origin.from, to: origin.to };

    _.each(this.doc().getAllMarks(), function(mark) {
        if (mark.xType != that.id) return;

        var markRange = mark.find();
        if (!markRange) return;

        var isInRange = markRange.line? posInsideRange(markRange, origin) : utils.rangesOverlap(markRange, origin);
        if (!isInRange) return;

        if (utils.posIsBefore(markRange.from, range.from)) {
            range.from = markRange.from;
        }
        if (utils.posIsBefore(range.to, markRange.to)) {
            range.to = markRange.to;
        }

        mark.clear();
    });

    return range;
};

// Process change from the editor
WidgetType.prototype.processChange = function(change) {
    if (change.origin == 'setValue') {
        return this.renderAll();
    }

    // Calcul range of change (change is pre-operation)
    var changeRange = {
        from: utils.Pos(change.from.line, change.from.ch),
        to: utils.Pos(change.to.line + (change.text.length - 1), change.to.ch + _.last(change.text).length)
    };

    // Determine large range and cleanup markers in it
    var range = this.cleanupMarkers(changeRange);

    // If nothing found, find larger section to process
    if (utils.posEqual(changeRange, range)) {
        range = this.findEditRange(changeRange);
        this.cleanupMarkers(changeRange);
    }

    // Update markers in this range
    this.processRange(range);
};

// Editor content changed
// Debounce processing
WidgetType.prototype._onChange = function(inst, change) {
    this.processChange(change);
};

WidgetType.prototype.enable = function() {
    if (this.enabled) return;

    this.enabled = true;
    this.manager.editor.on('change', this.onChange);
    this.renderAll();
};

WidgetType.prototype.disable = function() {
    if (!this.enabled) return;

    this.enabled = false;
    this.manager.editor.off('change', this.onChange);
    this.unrenderAll();
};

// Return the concerned document
WidgetType.prototype.doc = function() {
    return this.manager.doc();
};

// Define option to CodeMirror
function setupOption(Type, CodeMirror) {
    CodeMirror.defineOption(Type.prototype.className + 'Widget', false, function(cm, val, old) {
        if (old == CodeMirror.Init) old = false;
        if (val == old) return;

        // If editor has no manager, add one
        if (!cm._widgets) Manager(cm);

        if (val) {
            cm._widgets.enable(Type, val);
        } else {
            cm._widgets.disable(Type);
        }
    });
}

// Create an new type
function createType(type) {
    var CustomType = function() {
        WidgetType.apply(this, arguments);
    };
    util.inherits(CustomType, WidgetType);

    CustomType.prototype = utils.composeMixins(
        CustomType.prototype,
        (type.mixins || []).concat([type]),
        {
            composables: ['enable', 'disable', 'prepareWidget']
        }
    );

    CustomType.id = _.uniqueId('WidgetType');
    CustomType.setupOption = _.partial(setupOption, CustomType);

    return CustomType;
}

module.exports = {
    WidgetType: WidgetType,
    create: createType
};
<|endoftext|>


<commit_before>const tgd = require('telegraf-googledrive');
const config = require('../config');
const oauthClient = require('../oauth');

const { rootId, fields } = config.drive;

const makeKeyboard = (ctx, next) => {
    console.log('ctx.state: ', ctx.state.folders[rootId]);

    const folders = ctx.state.folders[rootId];
    const inlineKeyboard = [];
    folders.forEach(file => {
        // this file is an subfolder
        if (file.mimeType === 'application/vnd.google-apps.folder') {
            inlineKeyboard.push([
                { text: file.name
                    , callback_data: `changeFolder,${file.id}`
                }
            ]);
        } else {
            inlineKeyboard.push([
                { text: file.name
                    , callback_data: `sendFile,${file.id}`
                }
            ]);
        }
    });

    inlineKeyboard.concat(ctx.state.defaultKeyboard || []);

    const replyOptions = { reply_markup: { inline_keyboard: inlineKeyboard }
        , disable_web_page_preview: true };
    const replyText = ctx.state.folders.description || 'default decription';
    return ctx.replyWithMarkdown(
        replyText, replyOptions
    ).then(next).catch(console.error);
};

const filesToState = tgd.getFolder({ rootId, fields, auth: oauthClient });

/* Sets message description to ctx.state.folder.description from google drive README.md file
 */
const setDescription = tgd.setDescription({ path: config.drive.tempFolder , auth: oauthClient}); //eslint-disable-line

module.exports = [
    filesToState
    , setDescription
    , makeKeyboard
];

<commit_msg>limit the size of intro text to 309 chars max<commit_after>const tgd = require('telegraf-googledrive');
const config = require('../config');
const oauthClient = require('../oauth');

const replyTextMaxLength = 309;

const { rootId, fields } = config.drive;

const makeKeyboard = (ctx, next) => {
    console.log('ctx.state: ', ctx.state.folders[rootId]);

    const folders = ctx.state.folders[rootId];
    const inlineKeyboard = [];
    folders.forEach(file => {
        // this file is an subfolder
        if (file.mimeType === 'application/vnd.google-apps.folder') {
            inlineKeyboard.push([
                { text: file.name
                    , callback_data: `changeFolder,${file.id}`
                }
            ]);
        } else {
            inlineKeyboard.push([
                { text: file.name
                    , callback_data: `sendFile,${file.id}`
                }
            ]);
        }
    });

    inlineKeyboard.concat(ctx.state.defaultKeyboard || []);

    const replyOptions = { reply_markup: { inline_keyboard: inlineKeyboard }
        , disable_web_page_preview: true };
    const replyText = ctx.state.folders.description.slice(0, replyTextMaxLength)
        || 'default decryption';
    return ctx.replyWithMarkdown(
        replyText, replyOptions
    ).then(next).catch(console.error);
};

const filesToState = tgd.getFolder({ rootId, fields, auth: oauthClient });

/* Sets message description to ctx.state.folder.description from google drive README.md file
 */
const setDescription = tgd.setDescription({ path: config.drive.tempFolder , auth: oauthClient}); //eslint-disable-line

module.exports = [
    filesToState
    , setDescription
    , makeKeyboard
];

<|endoftext|>


<commit_before>class UrlBuilder {
    constructor(name, absolute, ziggyObject) {

        this.name = name;
        this.ziggy = ziggyObject;
        this.route = this.ziggy.namedRoutes[this.name];

        if (typeof this.name === 'undefined') {
            throw new Error('Ziggy Error: You must provide a route name');
        } else if (typeof this.route === 'undefined') {
            throw new Error(`Ziggy Error: route '${this.name}' is not found in the route list`);
        }

        this.absolute = typeof absolute === 'undefined' ? true : absolute;
        this.domain = this.setDomain();
        this.path = this.route.uri.replace(/^\//, '');
    }

    setDomain() {
        if (!this.absolute)
            return '/';

        if (!this.route.domain)
            return this.ziggy.baseUrl.replace(/\/?$/, '/');

        let host = (this.route.domain || this.ziggy.baseDomain).replace(/\/+$/, '');

        if (this.ziggy.basePort && (host.replace(/\/+$/, '') === this.ziggy.baseDomain.replace(/\/+$/, '')))
            host = this.ziggy.baseDomain + ':' + this.ziggy.basePort;

        return this.ziggy.baseProtocol + '://' + host + '/';
    }

    construct() {
        return this.domain + this.path
    }
}

export default UrlBuilder;
<commit_msg>Remove check for matching base domain and update host string concatenation to *append* the port rather than reconstructing the whole string<commit_after>class UrlBuilder {
    constructor(name, absolute, ziggyObject) {

        this.name = name;
        this.ziggy = ziggyObject;
        this.route = this.ziggy.namedRoutes[this.name];

        if (typeof this.name === 'undefined') {
            throw new Error('Ziggy Error: You must provide a route name');
        } else if (typeof this.route === 'undefined') {
            throw new Error(`Ziggy Error: route '${this.name}' is not found in the route list`);
        }

        this.absolute = typeof absolute === 'undefined' ? true : absolute;
        this.domain = this.setDomain();
        this.path = this.route.uri.replace(/^\//, '');
    }

    setDomain() {
        if (!this.absolute)
            return '/';

        if (!this.route.domain)
            return this.ziggy.baseUrl.replace(/\/?$/, '/');

        let host = (this.route.domain || this.ziggy.baseDomain).replace(/\/+$/, '');

        if (this.ziggy.basePort) {
            host = `${host}:${this.ziggy.basePort}`;
        }

        return this.ziggy.baseProtocol + '://' + host + '/';
    }

    construct() {
        return this.domain + this.path
    }
}

export default UrlBuilder;
<|endoftext|>


<commit_before>import config from '../config';
import combatless from './combatless';
import discord from './discord';
import rules from './rules';
import say from './say';
import stats from './stats';
import ping from './ping';
import restart from './restart';
import help from './help';
import props from './props';

export function is_command(message) {
	return message[0] === config.command_prefix;
}

export function get_command_args(message) {
	return message.slice(1)
		.split(' ');
}

export const list = [
	help,
	combatless,
	discord,
	say,
	rules,
	stats,
	ping,
	restart,
	props
];

export const commands = new Map();

list.forEach((command) => {
	command.aliases.forEach((alias) => commands.set(alias, command));
});

<commit_msg>add autowoot to commands<commit_after>import config from '../config';
import combatless from './combatless';
import discord from './discord';
import rules from './rules';
import say from './say';
import stats from './stats';
import ping from './ping';
import restart from './restart';
import help from './help';
import props from './props';
import autowoot from './autowoot';

export function is_command(message) {
	return message[0] === config.command_prefix;
}

export function get_command_args(message) {
	return message.slice(1)
		.split(' ');
}

export const list = [
	help,
	combatless,
	discord,
	say,
	rules,
	stats,
	ping,
	restart,
	props,
	autowoot
];

export const commands = new Map();

list.forEach((command) => {
	command.aliases.forEach((alias) => commands.set(alias, command));
});

<|endoftext|>


<commit_before>module.exports = function(crowi) {
  var debug = require('debug')('crowi:models:attachment')
  var mongoose = require('mongoose')
  var ObjectId = mongoose.Schema.Types.ObjectId
  var fileUploader = require('../util/fileUploader')(crowi)

  function generateFileHash(fileName) {
    var hasher = require('crypto').createHash('md5')
    hasher.update(fileName)

    return hasher.digest('hex')
  }

  const attachmentSchema = new mongoose.Schema(
    {
      page: { type: ObjectId, ref: 'Page', index: true },
      creator: { type: ObjectId, ref: 'User', index: true },
      filePath: { type: String, required: true },
      fileName: { type: String, required: true },
      originalName: { type: String },
      fileFormat: { type: String, required: true },
      fileSize: { type: Number, default: 0 },
      createdAt: { type: Date, default: Date.now },
    },
    {
      toJSON: {
        virtuals: true,
      },
    },
  )

  attachmentSchema.virtual('fileUrl').get(function() {
    return `/files/${this._id}`
  })

  attachmentSchema.statics.findById = function(id) {
    var Attachment = this

    return new Promise(function(resolve, reject) {
      Attachment.findOne({ _id: id }, function(err, data) {
        if (err) {
          return reject(err)
        }

        if (data === null) {
          return reject(new Error('Attachment not found'))
        }
        return resolve(data)
      })
    })
  }

  attachmentSchema.statics.getListByPageId = function(id) {
    var self = this

    return new Promise(function(resolve, reject) {
      self
        .find({ page: id })
        .sort({ updatedAt: 1 })
        .populate('creator')
        .exec(function(err, data) {
          if (err) {
            return reject(err)
          }

          if (data.length < 1) {
            return resolve([])
          }

          debug(data)
          return resolve(data)
        })
    })
  }

  attachmentSchema.statics.create = function(pageId, creator, filePath, originalName, fileName, fileFormat, fileSize) {
    var Attachment = this

    return new Promise(function(resolve, reject) {
      var newAttachment = new Attachment()

      newAttachment.page = pageId
      newAttachment.creator = creator._id
      newAttachment.filePath = filePath
      newAttachment.originalName = originalName
      newAttachment.fileName = fileName
      newAttachment.fileFormat = fileFormat
      newAttachment.fileSize = fileSize
      newAttachment.createdAt = Date.now()

      newAttachment.save(function(err, data) {
        if (err) {
          debug('Error on saving attachment.', err)
          return reject(err)
        }
        debug('Attachment saved.', data)
        return resolve(data)
      })
    })
  }

  attachmentSchema.statics.guessExtByFileType = function(fileType) {
    let ext = ''
    const isImage = fileType.match(/^image\/(.+)/i)

    if (isImage) {
      ext = isImage[1].toLowerCase()
    }

    return ext
  }

  attachmentSchema.statics.createAttachmentFilePath = function(pageId, fileName, fileType) {
    const Attachment = this
    let ext = ''
    const fnExt = fileName.match(/(.*)(?:\.([^.]+$))/)

    if (fnExt) {
      ext = '.' + fnExt[2]
    } else {
      ext = Attachment.guessExtByFileType(fileType)
      if (ext !== '') {
        ext = '.' + ext
      }
    }

    return 'attachment/' + pageId + '/' + generateFileHash(fileName) + ext
  }

  attachmentSchema.statics.removeAttachmentsByPageId = function(pageId) {
    var Attachment = this

    return new Promise((resolve, reject) => {
      Attachment.getListByPageId(pageId)
        .then(attachments => {
          for (let attachment of attachments) {
            Attachment.removeAttachment(attachment)
              .then(res => {
                // do nothing
              })
              .catch(err => {
                debug('Attachment remove error', err)
              })
          }

          resolve(attachments)
        })
        .catch(err => {
          reject(err)
        })
    })
  }

  attachmentSchema.statics.findDeliveryFile = function(attachment, forceUpdate) {
    // TODO
    var forceUpdate = forceUpdate || false

    return fileUploader.findDeliveryFile(attachment._id, attachment.filePath)
  }

  attachmentSchema.statics.removeAttachment = function(attachment) {
    const Attachment = this
    const filePath = attachment.filePath

    return new Promise((resolve, reject) => {
      Attachment.remove({ _id: attachment._id }, (err, data) => {
        if (err) {
          return reject(err)
        }

        fileUploader
          .deleteFile(attachment._id, filePath)
          .then(data => {
            resolve(data) // this may null
          })
          .catch(err => {
            reject(err)
          })
      })
    })
  }

  return mongoose.model('Attachment', attachmentSchema)
}
<commit_msg>Reduce debug log<commit_after>module.exports = function(crowi) {
  var debug = require('debug')('crowi:models:attachment')
  var mongoose = require('mongoose')
  var ObjectId = mongoose.Schema.Types.ObjectId
  var fileUploader = require('../util/fileUploader')(crowi)

  function generateFileHash(fileName) {
    var hasher = require('crypto').createHash('md5')
    hasher.update(fileName)

    return hasher.digest('hex')
  }

  const attachmentSchema = new mongoose.Schema(
    {
      page: { type: ObjectId, ref: 'Page', index: true },
      creator: { type: ObjectId, ref: 'User', index: true },
      filePath: { type: String, required: true },
      fileName: { type: String, required: true },
      originalName: { type: String },
      fileFormat: { type: String, required: true },
      fileSize: { type: Number, default: 0 },
      createdAt: { type: Date, default: Date.now },
    },
    {
      toJSON: {
        virtuals: true,
      },
    },
  )

  attachmentSchema.virtual('fileUrl').get(function() {
    return `/files/${this._id}`
  })

  attachmentSchema.statics.findById = function(id) {
    var Attachment = this

    return new Promise(function(resolve, reject) {
      Attachment.findOne({ _id: id }, function(err, data) {
        if (err) {
          return reject(err)
        }

        if (data === null) {
          return reject(new Error('Attachment not found'))
        }
        return resolve(data)
      })
    })
  }

  attachmentSchema.statics.getListByPageId = function(id) {
    var self = this

    return new Promise(function(resolve, reject) {
      self
        .find({ page: id })
        .sort({ updatedAt: 1 })
        .populate('creator')
        .exec(function(err, data) {
          if (err) {
            return reject(err)
          }

          if (data.length < 1) {
            return resolve([])
          }

          return resolve(data)
        })
    })
  }

  attachmentSchema.statics.create = function(pageId, creator, filePath, originalName, fileName, fileFormat, fileSize) {
    var Attachment = this

    return new Promise(function(resolve, reject) {
      var newAttachment = new Attachment()

      newAttachment.page = pageId
      newAttachment.creator = creator._id
      newAttachment.filePath = filePath
      newAttachment.originalName = originalName
      newAttachment.fileName = fileName
      newAttachment.fileFormat = fileFormat
      newAttachment.fileSize = fileSize
      newAttachment.createdAt = Date.now()

      newAttachment.save(function(err, data) {
        if (err) {
          debug('Error on saving attachment.', err)
          return reject(err)
        }
        debug('Attachment saved.', data)
        return resolve(data)
      })
    })
  }

  attachmentSchema.statics.guessExtByFileType = function(fileType) {
    let ext = ''
    const isImage = fileType.match(/^image\/(.+)/i)

    if (isImage) {
      ext = isImage[1].toLowerCase()
    }

    return ext
  }

  attachmentSchema.statics.createAttachmentFilePath = function(pageId, fileName, fileType) {
    const Attachment = this
    let ext = ''
    const fnExt = fileName.match(/(.*)(?:\.([^.]+$))/)

    if (fnExt) {
      ext = '.' + fnExt[2]
    } else {
      ext = Attachment.guessExtByFileType(fileType)
      if (ext !== '') {
        ext = '.' + ext
      }
    }

    return 'attachment/' + pageId + '/' + generateFileHash(fileName) + ext
  }

  attachmentSchema.statics.removeAttachmentsByPageId = function(pageId) {
    var Attachment = this

    return new Promise((resolve, reject) => {
      Attachment.getListByPageId(pageId)
        .then(attachments => {
          for (let attachment of attachments) {
            Attachment.removeAttachment(attachment)
              .then(res => {
                // do nothing
              })
              .catch(err => {
                debug('Attachment remove error', err)
              })
          }

          resolve(attachments)
        })
        .catch(err => {
          reject(err)
        })
    })
  }

  attachmentSchema.statics.findDeliveryFile = function(attachment, forceUpdate) {
    // TODO
    var forceUpdate = forceUpdate || false

    return fileUploader.findDeliveryFile(attachment._id, attachment.filePath)
  }

  attachmentSchema.statics.removeAttachment = function(attachment) {
    const Attachment = this
    const filePath = attachment.filePath

    return new Promise((resolve, reject) => {
      Attachment.remove({ _id: attachment._id }, (err, data) => {
        if (err) {
          return reject(err)
        }

        fileUploader
          .deleteFile(attachment._id, filePath)
          .then(data => {
            resolve(data) // this may null
          })
          .catch(err => {
            reject(err)
          })
      })
    })
  }

  return mongoose.model('Attachment', attachmentSchema)
}
<|endoftext|>


<commit_before>import * as path from 'upath';
import 'colors';
import * as fs from 'fs-extra';
import utils from '../utils';
import {getnginxpath} from '../functions/nginx';
import Command from 'cmd-line/lib/Command';

export async function copyconf (name) {
	await utils.copy(ProyPath(name), path.join(await getnginxpath(), 'enabled_sites', name));
	console.log(`   ${'copying'.cyan}: ${name}`);
	await utils.shell('Restarting Nginx', ['nginx', '-s', 'reload'], process.cwd());
}

export default (new Command(__filename, 'helps bind the server to nginx'))
	.Options([
		['-i', '--install', 'creates and install the .conf in your nginx path.']
	])
	.Action(async function (options) {
		let nginxpath = await getnginxpath();
		let conf = fs.readFileSync(`${nginxpath}nginx.conf`, 'utf-8');
		if (conf.indexOf('include enabled_sites/*') === -1) {
			conf = conf.replace(/http ?\{/igm, 'http {\n\tinclude enabled_sites/*.conf;');
			fs.writeFileSync(nginxpath + 'nginx.conf', conf);
			console.log(`   ${'updated'.cyan}: nginx.conf`);
			await utils.mkdir(nginxpath + 'enabled_sites');
		}
		let serverTemplate = await utils.read(TemplatePath('subdomain.conf'), 'utf-8');
		let nginxConf = await utils.read(TemplatePath('server.conf'), 'utf-8');
		nginxConf = utils.compile(nginxConf, {
			hostname: scfg.host,
			port: scfg.port
		});
		const subdomains = require(`${process.cwd()}/config/server`).subdomains;
		for (const idx in subdomains) {
			nginxConf += utils.compile(serverTemplate, {
				subdomain: subdomains[idx],
				hostname: scfg.host,
				port: scfg.port
			});
		}
		utils.write(path.join(process.cwd(), `${require(path.join(process.cwd(), 'package.json')).name}.conf`), nginxConf);
		if (options.copy) {
			await copyconf(`${scfg.name}.conf`);
		}
	});
<commit_msg>fix: fixes wrong parameter used to copy the .conf file<commit_after>import * as path from 'upath';
import 'colors';
import * as fs from 'fs-extra';
import utils from '../utils';
import {getnginxpath} from '../functions/nginx';
import Command from 'cmd-line/lib/Command';

export async function copyconf (name) {
	await utils.copy(ProyPath(name), path.join(await getnginxpath(), 'enabled_sites', name));
	console.log(`   ${'copying'.cyan}: ${name}`);
	await utils.shell('Restarting Nginx', ['nginx', '-s', 'reload'], process.cwd());
}

export default (new Command(__filename, 'helps bind the server to nginx'))
	.Options([
		['-i', '--install', 'creates and install the .conf in your nginx path.']
	])
	.Action(async function (options) {
		let nginxpath = await getnginxpath();
		let conf = fs.readFileSync(`${nginxpath}nginx.conf`, 'utf-8');
		if (conf.indexOf('include enabled_sites/*') === -1) {
			conf = conf.replace(/http ?\{/igm, 'http {\n\tinclude enabled_sites/*.conf;');
			fs.writeFileSync(nginxpath + 'nginx.conf', conf);
			console.log(`   ${'updated'.cyan}: nginx.conf`);
			await utils.mkdir(nginxpath + 'enabled_sites');
		}
		let serverTemplate = await utils.read(TemplatePath('subdomain.conf'), 'utf-8');
		let nginxConf = await utils.read(TemplatePath('server.conf'), 'utf-8');
		nginxConf = utils.compile(nginxConf, {
			hostname: scfg.host,
			port: scfg.port
		});
		const subdomains = require(`${process.cwd()}/config/server`).subdomains;
		for (const idx in subdomains) {
			nginxConf += utils.compile(serverTemplate, {
				subdomain: subdomains[idx],
				hostname: scfg.host,
				port: scfg.port
			});
		}
		utils.write(path.join(process.cwd(), `${require(path.join(process.cwd(), 'package.json')).name}.conf`), nginxConf);
		if (options.install) {
			await copyconf(`${scfg.name}.conf`);
		}
	});
<|endoftext|>


<commit_before>'use strict';
var fs = require("fs");
var uuid = require('uuid');
var userCollectionName = "users";
var groupsCollectionName = "groups";

var guest = require("./guest.js");
var account = require("./account.js");
var user = require("./user.js");
var group = require("./group.js");
var token = require("./token.js");

var utils = require("./utils.js");

var Mongo = require("soajs").mongo;
var mongoCore = null;

var soajs = require("soajs");
var Auth = soajs.authorization;

var async = require('async');

function checkIfTenantIsDBTN(req, cb) {
	//todo tenantCode is hard coded for now until acl is fixed
	if (!mongoCore) {
		mongoCore = new Mongo(req.soajs.registry.coreDB.provision);
	}
	if (!req.soajs.inputmaskData) {
		req.soajs.inputmaskData = {};
	}

	req.soajs.inputmaskData.isOwner = true;
	//todo just for test cases
	if (process.env.SOAJS_TEST){
		mongoCore.findOne('tenants', { 'locked': true }, { applications: 0 }, function (error, tenant) {
			if (!error && tenant) {
				req.soajs.inputmaskData.tenantCode = tenant.code;
			}
			return cb(error, true);
		});
	}
	else {
		req.soajs.inputmaskData.tenantCode = 'DBTN';
		return cb(null, true);
	}
}

var libProduct = {
	"model": null,

	"guest": guest,

	"account": account,

	"admin": {
		/**
		 * List all the users and groups
		 * @param {Request Object} req
		 * @param {Callback Function} cb
		 */
		"listAll": function (req, cb) {
			checkIfTenantIsDBTN(req, function (error) {
				var data = {
					config: req.soajs.config,
					error: error,
					code: 405
				};
				utils.checkIfError(req, cb, data, false, function () {
					var combo = {
						collection: userCollectionName,
						condition: {},
						fields: {
							'password': 0, 'socialId': 0
						}
					};
					libProduct.model.initConnection(req.soajs);
					libProduct.model.findEntries(req.soajs, combo, function (err, userRecords) {
						data = {
							config: req.soajs.config,
							error: err,
							code: 405
						};
						utils.checkIfError(req, cb, data, false, function () {
							var combo = {
								collection: groupsCollectionName,
								condition: {}
							};
							libProduct.model.findEntries(req.soajs, combo, function (err, grpRecords) {
								libProduct.model.closeConnection(req.soajs);
								data.code = 415;
								data.error = err;
								utils.checkIfError(req, cb, data, false, function () {
									return cb(null, {
										'users': userRecords,
										'groups': grpRecords
									});
								});
							});
						});
					});
				});
			});
		},

		"user": {
			"countUsers": function (req, cb) {
				checkIfTenantIsDBTN(req, function (error) {
					var data = {
						config: req.soajs.config,
						error: error,
						code: 405
					};
					utils.checkIfError(req, cb, data, false, function () {
						user.countUsers(req, cb);
					});
				});
			},

			"listUsers": function (req, cb) {
				checkIfTenantIsDBTN(req, function (error) {
					var data = {
						config: req.soajs.config,
						error: error,
						code: 405
					};
					utils.checkIfError(req, cb, data, false, function () {
						user.listUsers(req, cb);
					});
				});
			},

			"getUser": function (req, cb) {
				checkIfTenantIsDBTN(req, function (error) {
					var data = {
						config: req.soajs.config,
						error: error,
						code: 405
					};
					utils.checkIfError(req, cb, data, false, function () {
						user.getUser(req, cb);
					});
				});
			},

			"addUser": function (req, cb) {
				checkIfTenantIsDBTN(req, function (error) {
					var data = {
						config: req.soajs.config,
						error: error,
						code: 405
					};
					utils.checkIfError(req, cb, data, false, function () {
						user.addUser(req, cb);
					});
				});
			},

			"editUser": function (req, cb) {
				checkIfTenantIsDBTN(req, function (error) {
					var data = {
						config: req.soajs.config,
						error: error,
						code: 405
					};
					utils.checkIfError(req, cb, data, false, function () {
						user.editUser(req, cb);
					});
				});
			},

			"editConfig": function (req, cb) {
				checkIfTenantIsDBTN(req, function (error) {
					var data = {
						config: req.soajs.config,
						error: error,
						code: 405
					};
					utils.checkIfError(req, cb, data, false, function () {
						user.editConfig(req, cb);
					});
				});
			},

			"changeStatus": function (req, cb) {
				checkIfTenantIsDBTN(req, function (error) {
					var data = {
						config: req.soajs.config,
						error: error,
						code: 405
					};
					utils.checkIfError(req, cb, data, false, function () {
						user.changeStatus(req, cb);
					});
				});
			}
		},

		"group": {
			"list": function (req, cb) {
				checkIfTenantIsDBTN(req, function (error) {
					var data = {
						config: req.soajs.config,
						error: error,
						code: 405
					};
					utils.checkIfError(req, cb, data, false, function () {
						group.list(req, cb);
					});
				});
			},

			"add": function (req, cb) {
				checkIfTenantIsDBTN(req, function (error) {
					var data = {
						config: req.soajs.config,
						error: error,
						code: 405
					};
					utils.checkIfError(req, cb, data, false, function () {
						group.add(req, cb);
					});
				});
			},

			"edit": function (req, cb) {
				checkIfTenantIsDBTN(req, function (error) {
					var data = {
						config: req.soajs.config,
						error: error,
						code: 405
					};
					utils.checkIfError(req, cb, data, false, function () {
						group.edit(req, cb);
					});
				});
			},

			"delete": function (req, cb) {
				checkIfTenantIsDBTN(req, function (error) {
					var data = {
						config: req.soajs.config,
						error: error,
						code: 405
					};
					utils.checkIfError(req, cb, data, false, function () {
						group.delete(req, cb);
					});
				});
			},

			"addUsers": function (req, cb) {
				checkIfTenantIsDBTN(req, function (error) {
					var data = {
						config: req.soajs.config,
						error: error,
						code: 405
					};
					utils.checkIfError(req, cb, data, false, function () {
						group.addUsers(req, cb);
					});
				});
			}
		},

		"tokens": token
	},

	"owner": {
		"user": user,

		"group": group,

		"tokens": token
	},

	"tenant": {

		"list": function (req, cb) {
			let config = req.soajs.config;

			var opts = {
				collection: "tenants"
			};

			if (req.soajs.inputmaskData.type) {
				if (Object.hasOwnProperty.call(req.soajs.inputmaskData, "negate") && req.soajs.inputmaskData.negate === true) {
					opts.conditions = { 'type': { $ne: req.soajs.inputmaskData.type } };
				}
				else {
					opts.conditions = { 'type': req.soajs.inputmaskData.type };
				}
			}

			opts.options = { "sort": { "name": 1 } };

			if (!mongoCore) {
				mongoCore = new Mongo(req.soajs.registry.coreDB.provision);
			}

			mongoCore.find(opts.collection, opts.conditions, opts.options, function (err, records) {
				utils.checkIfError(req, cb, { config: config, error: err, code: 436 }, false, function () {
					//generate oauth authorization if needed.
					records.forEach(function (oneTenant) {
						if (oneTenant.oauth && oneTenant.oauth.secret && oneTenant.oauth.secret !== '') {
							oneTenant.oauth.authorization = Auth.generate(oneTenant._id, oneTenant.oauth.secret);
						}
						else {
							oneTenant.oauth.authorization = "No Authorization Enabled, update tenant and set an 'oAuth Secret' to enable token generation.";
						}
					});
					return cb(null, records);
				});
			});
		},

		"getUserAclInfo": function (req, cb) {
			let config = req.soajs.config;
			let output = {};
			if (!mongoCore) {
				mongoCore = new Mongo(req.soajs.registry.coreDB.provision);
			}

			function listEnvironment(listEnvironmentCb) {
				let opts = {
					collection: "environment"
				};

				mongoCore.find(opts.collection, {locked: {$ne: true} }, function (err, records) {
					utils.checkIfError(req, listEnvironmentCb, {
						config: config,
						error: err,
						code: 437
					}, false, function () {
						return listEnvironmentCb(null, records);
					});
				});
			}

			function getTenant(getTenantCb) {

				function validateId(soajs, id, validateIdCb) {
					var id1;
					try {
						id1 = mongoCore.ObjectId(id);
						return validateIdCb(null, id1);
					}
					catch (e) {
						soajs.log.error(e);
						return validateIdCb({
							config: config,
							error: e,
							code: 426
						});
					}
				}

				var opts = {};
				validateId(req.soajs, req.soajs.inputmaskData.tenantId, function (err, tenantId) {
					utils.checkIfError(req, getTenantCb, { config: config, error: err, code: 438 }, false, function () {
						opts.collection = "tenants";
						opts.conditions = { "_id": tenantId };
						mongoCore.findOne(opts.collection, opts.conditions, function (err, data) {
							utils.checkIfError(req, getTenantCb, {
								config: config,
								error: err || !data,
								code: 438
							}, false, function () {
								return getTenantCb(null, data);
							});
						});
					});
				});
			}

			function listServices(listServicesCb) {
				var opts = {};
				opts.collection = "services";
				mongoCore.find(opts.collection, function (error, records) {
					if (error) {
						req.soajs.log.error(error);
						return listServicesCb({ code: 400, msg: error });
					}

					return listServicesCb(null, records);
				});
			}

			function getAndSetPackages(oneApplication, getPackagesCb) {
				var opts = {};
				opts.collection = "products";
				opts.conditions = { "code": oneApplication.product };
				mongoCore.findOne(opts.collection, opts.conditions, function (err, product) {
					utils.checkIfError(req, getPackagesCb, {
						config: config,
						error: err || !product,
						code: 460
					}, false, function () {
						var pck = {};
						var found = false;
						for (var i = 0; i < product.packages.length; i++) {
							if (product.packages[i].code === oneApplication.package) {
								pck = product.packages[i];
								found = true;
								break;
							}
						}

						utils.checkIfError(req, getPackagesCb, {
							config: config,
							error: !found,
							code: 461
						}, false, function () {
							oneApplication.parentPackageAcl = pck.acl;
							return getPackagesCb(null, pck);
						});
					});
				});
			}

			async.parallel([
					listEnvironment,
					getTenant,
					listServices
				],
				function (asyncParallelError, asyncParallelResults) {
					if (asyncParallelError) {
						return cb(asyncParallelError);
					}

					output.environment = asyncParallelResults[0];
					output.tenant = asyncParallelResults[1];
					output.services = asyncParallelResults[2];

					let applications = output.tenant.applications;
					async.each(applications, function (oneApplication, asyncEachCb) {
						if (!Object.hasOwnProperty.call(oneApplication, 'acl')) {
							getAndSetPackages(oneApplication, asyncEachCb);
						} else {
							return asyncEachCb();
						}
					}, function (asyncError) {
						if (asyncError) {
							req.soajs.log.error(asyncError);
							return cb(asyncError);
						} else {
							return cb(null, output);
						}
					});
				});
		}
	}
};

module.exports = {
	"clearMongo": function () { // added for unit test cases only
		mongoCore = null;
	},

	"init": function (modelName, cb) {
		var modelPath = __dirname + "/../model/" + modelName + ".js";
		return requireModel(modelPath, cb);

		/**
		 * checks if model file exists, requires it and returns it.
		 * @param filePath
		 * @param cb
		 */
		function requireModel(filePath, cb) {
			//check if file exist. if not return error
			fs.exists(filePath, function (exists) {
				if (!exists) {
					return cb(new Error("Requested Model Not Found!"));
				}

				account.model = require(filePath);
				guest.model = require(filePath);
				user.model = require(filePath);
				group.model = require(filePath);
				token.model = require(filePath);

				libProduct.model = require(filePath);
				return cb(null, libProduct);
			});
		}
	}
};<commit_msg>fixed admin API to make them multi tenant<commit_after>'use strict';
var fs = require("fs");
var uuid = require('uuid');
var userCollectionName = "users";
var groupsCollectionName = "groups";

var guest = require("./guest.js");
var account = require("./account.js");
var user = require("./user.js");
var group = require("./group.js");
var token = require("./token.js");

var utils = require("./utils.js");

var Mongo = require("soajs").mongo;
var mongoCore = null;

var soajs = require("soajs");
var Auth = soajs.authorization;

var async = require('async');

function checkIfTenantIsDBTN(req, cb) {
	//todo tenantCode is hard coded for now until acl is fixed
	if (!mongoCore) {
		mongoCore = new Mongo(req.soajs.registry.coreDB.provision);
	}
	if (!req.soajs.inputmaskData) {
		req.soajs.inputmaskData = {};
	}

	req.soajs.inputmaskData.isOwner = true;
	//todo just for test cases
	if (process.env.SOAJS_TEST){
		mongoCore.findOne('tenants', { 'locked': true }, { applications: 0 }, function (error, tenant) {
			if (!error && tenant) {
				req.soajs.inputmaskData.tenantCode = tenant.code;
			}
			return cb(error, true);
		});
	}
	else {
		req.soajs.inputmaskData.tenantCode = 'DBTN';
		return cb(null, true);
	}
}

var libProduct = {
	"model": null,

	"guest": guest,

	"account": account,

	"adminOLD": {
		/**
		 * List all the users and groups
		 * @param {Request Object} req
		 * @param {Callback Function} cb
		 */
		"listAll": function (req, cb) {
			checkIfTenantIsDBTN(req, function (error) {
				var data = {
					config: req.soajs.config,
					error: error,
					code: 405
				};
				utils.checkIfError(req, cb, data, false, function () {
					var combo = {
						collection: userCollectionName,
						condition: {},
						fields: {
							'password': 0, 'socialId': 0
						}
					};
					libProduct.model.initConnection(req.soajs);
					libProduct.model.findEntries(req.soajs, combo, function (err, userRecords) {
						data = {
							config: req.soajs.config,
							error: err,
							code: 405
						};
						utils.checkIfError(req, cb, data, false, function () {
							var combo = {
								collection: groupsCollectionName,
								condition: {}
							};
							libProduct.model.findEntries(req.soajs, combo, function (err, grpRecords) {
								libProduct.model.closeConnection(req.soajs);
								data.code = 415;
								data.error = err;
								utils.checkIfError(req, cb, data, false, function () {
									return cb(null, {
										'users': userRecords,
										'groups': grpRecords
									});
								});
							});
						});
					});
				});
			});
		},

		"user": {
			"countUsers": function (req, cb) {
				checkIfTenantIsDBTN(req, function (error) {
					var data = {
						config: req.soajs.config,
						error: error,
						code: 405
					};
					utils.checkIfError(req, cb, data, false, function () {
						user.countUsers(req, cb);
					});
				});
			},

			"listUsers": function (req, cb) {
				checkIfTenantIsDBTN(req, function (error) {
					var data = {
						config: req.soajs.config,
						error: error,
						code: 405
					};
					utils.checkIfError(req, cb, data, false, function () {
						user.listUsers(req, cb);
					});
				});
			},

			"getUser": function (req, cb) {
				checkIfTenantIsDBTN(req, function (error) {
					var data = {
						config: req.soajs.config,
						error: error,
						code: 405
					};
					utils.checkIfError(req, cb, data, false, function () {
						user.getUser(req, cb);
					});
				});
			},

			"addUser": function (req, cb) {
				checkIfTenantIsDBTN(req, function (error) {
					var data = {
						config: req.soajs.config,
						error: error,
						code: 405
					};
					utils.checkIfError(req, cb, data, false, function () {
						user.addUser(req, cb);
					});
				});
			},

			"editUser": function (req, cb) {
				checkIfTenantIsDBTN(req, function (error) {
					var data = {
						config: req.soajs.config,
						error: error,
						code: 405
					};
					utils.checkIfError(req, cb, data, false, function () {
						user.editUser(req, cb);
					});
				});
			},

			"editConfig": function (req, cb) {
				checkIfTenantIsDBTN(req, function (error) {
					var data = {
						config: req.soajs.config,
						error: error,
						code: 405
					};
					utils.checkIfError(req, cb, data, false, function () {
						user.editConfig(req, cb);
					});
				});
			},

			"changeStatus": function (req, cb) {
				checkIfTenantIsDBTN(req, function (error) {
					var data = {
						config: req.soajs.config,
						error: error,
						code: 405
					};
					utils.checkIfError(req, cb, data, false, function () {
						user.changeStatus(req, cb);
					});
				});
			}
		},

		"group": {
			"list": function (req, cb) {
				checkIfTenantIsDBTN(req, function (error) {
					var data = {
						config: req.soajs.config,
						error: error,
						code: 405
					};
					utils.checkIfError(req, cb, data, false, function () {
						group.list(req, cb);
					});
				});
			},

			"add": function (req, cb) {
				checkIfTenantIsDBTN(req, function (error) {
					var data = {
						config: req.soajs.config,
						error: error,
						code: 405
					};
					utils.checkIfError(req, cb, data, false, function () {
						group.add(req, cb);
					});
				});
			},

			"edit": function (req, cb) {
				checkIfTenantIsDBTN(req, function (error) {
					var data = {
						config: req.soajs.config,
						error: error,
						code: 405
					};
					utils.checkIfError(req, cb, data, false, function () {
						group.edit(req, cb);
					});
				});
			},

			"delete": function (req, cb) {
				checkIfTenantIsDBTN(req, function (error) {
					var data = {
						config: req.soajs.config,
						error: error,
						code: 405
					};
					utils.checkIfError(req, cb, data, false, function () {
						group.delete(req, cb);
					});
				});
			},

			"addUsers": function (req, cb) {
				checkIfTenantIsDBTN(req, function (error) {
					var data = {
						config: req.soajs.config,
						error: error,
						code: 405
					};
					utils.checkIfError(req, cb, data, false, function () {
						group.addUsers(req, cb);
					});
				});
			}
		},

		"tokens": token
	},

	"admin": {
		"user": user,

		"group": group,

		"tokens": token
	},

	"tenant": {

		"list": function (req, cb) {
			let config = req.soajs.config;

			var opts = {
				collection: "tenants"
			};

			if (req.soajs.inputmaskData.type) {
				if (Object.hasOwnProperty.call(req.soajs.inputmaskData, "negate") && req.soajs.inputmaskData.negate === true) {
					opts.conditions = { 'type': { $ne: req.soajs.inputmaskData.type } };
				}
				else {
					opts.conditions = { 'type': req.soajs.inputmaskData.type };
				}
			}

			opts.options = { "sort": { "name": 1 } };

			if (!mongoCore) {
				mongoCore = new Mongo(req.soajs.registry.coreDB.provision);
			}

			mongoCore.find(opts.collection, opts.conditions, opts.options, function (err, records) {
				utils.checkIfError(req, cb, { config: config, error: err, code: 436 }, false, function () {
					//generate oauth authorization if needed.
					records.forEach(function (oneTenant) {
						if (oneTenant.oauth && oneTenant.oauth.secret && oneTenant.oauth.secret !== '') {
							oneTenant.oauth.authorization = Auth.generate(oneTenant._id, oneTenant.oauth.secret);
						}
						else {
							oneTenant.oauth.authorization = "No Authorization Enabled, update tenant and set an 'oAuth Secret' to enable token generation.";
						}
					});
					return cb(null, records);
				});
			});
		},

		"getUserAclInfo": function (req, cb) {
			let config = req.soajs.config;
			let output = {};
			if (!mongoCore) {
				mongoCore = new Mongo(req.soajs.registry.coreDB.provision);
			}

			function listEnvironment(listEnvironmentCb) {
				let opts = {
					collection: "environment"
				};

				mongoCore.find(opts.collection, {locked: {$ne: true} }, function (err, records) {
					utils.checkIfError(req, listEnvironmentCb, {
						config: config,
						error: err,
						code: 437
					}, false, function () {
						return listEnvironmentCb(null, records);
					});
				});
			}

			function getTenant(getTenantCb) {

				function validateId(soajs, id, validateIdCb) {
					var id1;
					try {
						id1 = mongoCore.ObjectId(id);
						return validateIdCb(null, id1);
					}
					catch (e) {
						soajs.log.error(e);
						return validateIdCb({
							config: config,
							error: e,
							code: 426
						});
					}
				}

				var opts = {};
				validateId(req.soajs, req.soajs.inputmaskData.tenantId, function (err, tenantId) {
					utils.checkIfError(req, getTenantCb, { config: config, error: err, code: 438 }, false, function () {
						opts.collection = "tenants";
						opts.conditions = { "_id": tenantId };
						mongoCore.findOne(opts.collection, opts.conditions, function (err, data) {
							utils.checkIfError(req, getTenantCb, {
								config: config,
								error: err || !data,
								code: 438
							}, false, function () {
								return getTenantCb(null, data);
							});
						});
					});
				});
			}

			function listServices(listServicesCb) {
				var opts = {};
				opts.collection = "services";
				mongoCore.find(opts.collection, function (error, records) {
					if (error) {
						req.soajs.log.error(error);
						return listServicesCb({ code: 400, msg: error });
					}

					return listServicesCb(null, records);
				});
			}

			function getAndSetPackages(oneApplication, getPackagesCb) {
				var opts = {};
				opts.collection = "products";
				opts.conditions = { "code": oneApplication.product };
				mongoCore.findOne(opts.collection, opts.conditions, function (err, product) {
					utils.checkIfError(req, getPackagesCb, {
						config: config,
						error: err || !product,
						code: 460
					}, false, function () {
						var pck = {};
						var found = false;
						for (var i = 0; i < product.packages.length; i++) {
							if (product.packages[i].code === oneApplication.package) {
								pck = product.packages[i];
								found = true;
								break;
							}
						}

						utils.checkIfError(req, getPackagesCb, {
							config: config,
							error: !found,
							code: 461
						}, false, function () {
							oneApplication.parentPackageAcl = pck.acl;
							return getPackagesCb(null, pck);
						});
					});
				});
			}

			async.parallel([
					listEnvironment,
					getTenant,
					listServices
				],
				function (asyncParallelError, asyncParallelResults) {
					if (asyncParallelError) {
						return cb(asyncParallelError);
					}

					output.environment = asyncParallelResults[0];
					output.tenant = asyncParallelResults[1];
					output.services = asyncParallelResults[2];

					let applications = output.tenant.applications;
					async.each(applications, function (oneApplication, asyncEachCb) {
						if (!Object.hasOwnProperty.call(oneApplication, 'acl')) {
							getAndSetPackages(oneApplication, asyncEachCb);
						} else {
							return asyncEachCb();
						}
					}, function (asyncError) {
						if (asyncError) {
							req.soajs.log.error(asyncError);
							return cb(asyncError);
						} else {
							return cb(null, output);
						}
					});
				});
		}
	}
};

module.exports = {
	"clearMongo": function () { // added for unit test cases only
		mongoCore = null;
	},

	"init": function (modelName, cb) {
		var modelPath = __dirname + "/../model/" + modelName + ".js";
		return requireModel(modelPath, cb);

		/**
		 * checks if model file exists, requires it and returns it.
		 * @param filePath
		 * @param cb
		 */
		function requireModel(filePath, cb) {
			//check if file exist. if not return error
			fs.exists(filePath, function (exists) {
				if (!exists) {
					return cb(new Error("Requested Model Not Found!"));
				}

				account.model = require(filePath);
				guest.model = require(filePath);
				user.model = require(filePath);
				group.model = require(filePath);
				token.model = require(filePath);

				libProduct.model = require(filePath);
				return cb(null, libProduct);
			});
		}
	}
};<|endoftext|>