<commit_before>(function (define) {
define(function (require) {
"use strict";

	var eventNames,
		beforePhase, propagatingPhase, afterPhase, canceledPhase,
		resolver, addPropertyTransforms, simpleStrategy,
		undef;

	// TODO: make these configurable/extensible
	eventNames = {
		// collection item events
		add: 1,
		remove: 1,
		update: 1,
		target: 1,
		// edit mode events
		edit: 1,
		cancel: 1,
		save: 1,
		// multi-item events
		select: 1,
		unselect: 1,
		// network-level events
		join: 1,
		sync: 1,
		leave: 1
	};

	/**
	 * Signal that event has not yet been pushed onto the network.
	 * Return false to prevent the event from being pushed.
	 */
	beforePhase = {};

	/**
	 * Signal that event is currently being propagated to adapters.
	 */
	propagatingPhase = {};

	/**
	 * Signal that an event has already been pushed onto the network.
	 * Return value is ignored since the event has already propagated.
	 */
	afterPhase = {};

	/**
	 * Signal that an event was canceled and not pushed onto the network.
	 * Return value is ignored since the event has already propagated.
	 */
	canceledPhase = {};

	resolver = require('./AdapterResolver');
	addPropertyTransforms = require('./addPropertyTransforms');
	simpleStrategy = require('./network/strategy/default');

	/**
	 * @constructor
	 * @param primary {Object} primary data source
	 * @param options.strategy {strategyFunction} a strategy
	 *   Strategies determine if an event gets onto the network and then how
	 *   it's processed by the other data sources in the network.
	 *   Only one strategy can be applied to a network. However, strategies
	 *   can be composed/combined.
	 * @param [options.events] {Object} an object to receive events
	 *   from the hub's network
	 *
	 * @description
	 * The hub exposes actions that can be propagated through the network
	 * of data sources.  These actions are the same actions supported by
	 * the adapters that wrap the data sources.  Examples include: add(),
	 * remove(), update(), select(), unselect().  Each action has the same
	 * signature: `function (data, [source]) { return bool; }`  The source
	 * parameter is optional, but may be critical for proper operation of
	 * certain strategies.  Supplying a dom event from a dom node that was
	 * added to a hub (i.e. the dom node is under a NodeListAdapter's
	 * root node) will automatically be translated to the correct data item and
	 * the correct source.
	 */
	function Hub (options) {
		var adapters, eventQueue, strategy, publicApi, eventsApi;

		// TODO: Determine if we need to save Hub options and mix them into
		// options passed thru to adapters in addSource

		// all adapters in network
		adapters = [];

		// events to be processed (fifo)
		eventQueue = [];

//		callPublicEvent = callEventsApi;

		if (!options) options = {};

		strategy = options.strategy;
		if (!strategy) strategy = simpleStrategy(options.strategyOptions);

		// create public api
		eventsApi = publicApi = {
			addSource: addSource,
			destroy: destroy
		};

		// add standard events to publicApi
		addApiMethods(eventNames);

		// add events
		addApiEvents(eventNames);

		return publicApi;

		/**
		 * @memberOf Hub
		 * @param source
		 * @param options {Object}
		 * @param [options.eventNames] {Function} function that returns a
		 *   list of method names that should be considered events
		 *   If omitted, all methods, the standard event names are used.
		 * @param options.provide {Boolean} if true, initiates a 'sync' event
		 *   from this source's adapter
		 */
		function addSource (source, options) {
			var Adapter, adapter, proxy, method, eventFinder;

			if (!options) options = {};

			// create an adapter for this source
			// if we can't find an Adapter constructor, it is assumed to be an
			// adapter already.
			// TODO: revisit this assumption?
			// TODO: how to detect whether to use 'collection' or 'object' types
			Adapter = resolver(source, 'collection');
			adapter = Adapter ? new Adapter(source, options) : source;
			if (options.bindings) {
				adapter = addPropertyTransforms(adapter, collectPropertyTransforms(options.bindings));
			}

			proxy = beget(adapter);

			// keep copy of original source so we can match it up later
			proxy.origSource = source;
			proxy.provide = options.provide;

			// sniff for event hooks
			eventFinder = configureEventFinder(options.eventNames);

			// override methods that require event hooks
			for (method in adapter) {
				if (typeof adapter[method] == 'function') {
					if (eventFinder(method)) {
						// store original method on proxy (to stop recursion)
						proxy[method] = adapter[method];
						// change public api of adapter to call back into hub
						observeAdapterMethod(adapter, method, adapter[method]);
						// ensure hub has a public method of the same name
						addApiMethod(method);
						addApiEvent(method);
					}
				}
			}

			// save the proxied adapter
			adapters.push(proxy);

			// TODO: Should we use a boolean for the 2nd param to indicate join vs. leave
			// instead of using separate events?
			// Right now the second param is unused
			processEvent(proxy, null, 'join');

			return adapter;
		}

		function queueEvent (source, data, type) {
			var queueNeedsRestart;

			// if queue length is zero, we need to start processing it again
			queueNeedsRestart = eventQueue.length == 0;

			// enqueue event
			eventQueue.push({ source: source, data: data, type: type });

			// start processing, if necessary
			if (queueNeedsRestart) processNextEvent();
		}

		function processNextEvent () {
			var event;

			// get the next event, if any
			event = eventQueue.shift();

			// if there was an event, process it soon
			if (event) {
				setTimeout(function () {
					processEvent(event.source, event.data, event.type);
				}, 0);
			}
		}

		/*
			1. call events.beforeXXX(data)
			2. call strategy on each source/dest pair w/ event XXX and data
				- cancel iteration if any strategy returns false for any pair
			3. if not canceled, call events.XXX(data)
		 */
		function processEvent (source, data, type) {
			var context, strategyApi, i, adapter, canceled;

			// give public api a chance to see (and possibly cancel) event
			canceled = false === callEventsApi(data, camelize('before', type));

			// if public api cancels, the network never sees the event at all
			if (!canceled) {

				context = { phase: beforePhase };
				strategyApi = createStrategyApi(context);

				canceled = false === strategy(source, undef, data, type, strategyApi);
				i = adapters.length;

				context.phase = propagatingPhase;
				while (!canceled && (adapter = adapters[--i])) {
					if (false === strategy(source, adapter, data, type, strategyApi)) {
						canceled = true;
						break;
					}
				}

				context.phase = canceled ? canceledPhase : afterPhase;
				canceled = false === strategy(source, undef, data, type, strategyApi);

			}

			if (!canceled) callEventsApi(data, camelize('on', type));

			processNextEvent();

			return canceled;
		}

		function createStrategyApi (context) {
			function isPhase (phase) { return context.phase == phase; }
			return {
				queueEvent: queueEvent,
				isBefore: function () { return isPhase(beforePhase); },
				isAfter: function () { return isPhase(afterPhase); },
				isCanceled: function () { return isPhase(canceledPhase); },
				isPropagating: function () { return isPhase(propagatingPhase); }
			};
		}

		function observeAdapterMethod (adapter, type, origMethod) {
			return adapter[type] = function (data) {
				processEvent(adapter, data, type);
				return origMethod.call(adapter, data);
			};
		}

		function addApiMethods (eventNames) {
			for (var name in eventNames) {
				addApiMethod(name);
			}
		}

		function addApiMethod (name) {
			if (!publicApi[name]) {
				publicApi[name] = function (itemOrDomEvent) {
					var sourceInfo;
					if (isDomEvent(itemOrDomEvent)) {
						sourceInfo = convertFromDomEvent(itemOrDomEvent, adapters);
					}
					else {
						sourceInfo = {
							item: itemOrDomEvent,
							source: findAdapterForSource(arguments[1], adapters)
						};
					}
					return processEvent(sourceInfo.source, sourceInfo.item, name);
				};
			}
		}

		function addApiEvents (eventNames) {
			for (var name in eventNames) {
				addApiEvent(name);
			}
		}

		function addApiEvent (name) {
			var eventName = camelize('on', name);
			// add function stub to api
			if (!eventsApi[eventName]) {
				eventsApi[eventName] = function (data) {};
			}
			// add beforeXXX stub, too
			eventName = camelize('before', name);
			if (!eventsApi[eventName]) {
				eventsApi[eventName] = function (data) {};
			}
		}

		function callEventsApi (data, name) {
			try {
				return eventsApi[name](data);
			}
			catch (ex) {
				return false;
			}
		}

		function destroy () {
			var adapter;
			while ((adapter = adapters.pop())) {
				if (typeof adapter.destroy == 'function') {
					adapter.destroy();
				}
			}
		}

	}

	// TODO: get rid of this mess
	resolver.register(require('./ArrayAdapter'), 'collection');
	resolver.register(require('./dom/NodeListAdapter'), 'collection');
	resolver.register(require('./ResultSetAdapter'), 'collection');
	resolver.register(require('./QueryAdapter'), 'collection');
	resolver.register(require('./dom/NodeAdapter'), 'object');
	resolver.register(require('./ObjectAdapter'), 'object');
	resolver.register(require('./ResultAdapter'), 'object');

	return Hub;

	/**
	 * Signature for all network strategy functions.
	 * @param source {Object} the adapter that sourced the event
	 * @param dest {Object} the adapter receiving the event
	 * @param data {Object} any data associated with the event
	 * @param type {String} the type of event
	 * @param api {Object} helpful functions for strategies
	 * @returns {Boolean} whether event is allowed.
	 */
	function strategyFunction (source, dest, data, type, api) {};

	function configureEventFinder (option) {
		if (typeof option == 'function') return option;

		return function (name) { return eventNames.hasOwnProperty(name); };
	}

	function convertFromDomEvent (itemOrEvent, adapters) {
		var item, i, adapter;

		// loop through adapters that have the getItemForEvent() method
		// to try to find out which adapter and which data item
		i = 0;
		while (!item && (adapter = adapters[i++])) {
			if (adapter.getItemForEvent) {
				item = adapter.getItemForEvent(itemOrEvent);
			}
		}

		// TODO: make this an option?
//		if (!item) {
//			var err = new Error('Hub: could not find data item for dom event.');
//			err.event = itemOrEvent; // TODO: is this helpful?
//			throw err;
//		}

		return { item: item, source: adapter };
	}

	function isDomEvent (e) {
		// using feature sniffing to detect if this is an event object
		return e.target && e.stopPropagation && e.preventDefault;
	}

	function findAdapterForSource (source, adapters) {
		var i, adapter, found;

		// loop through adapters and find which one was created for this source
		i = 0;
		while (!found && (adapter = adapters[i++])) {
			if (adapter.origSource == source) {
				found = adapter;
			}
		}

		return found;
	}

	function collectPropertyTransforms (bindings) {
		var name, propertyTransforms, transform;

		propertyTransforms = {};
		for (name in bindings) {
			transform = bindings[name].transform;
			if (transform) {
				propertyTransforms[name] = transform;
			}
		}

		return propertyTransforms;
	}

	function Begetter () {}
	function beget (proto) {
		var obj;
		Begetter.prototype = proto;
		obj = new Begetter();
		Begetter.prototype = undef;
		return obj;
	}

	function noop () {}

	function camelize (prefix, name) {
		return prefix + name.charAt(0).toUpperCase() + name.substr(1);
	}

});
}(
	typeof define == 'function' && define.amd
		? define
		: function (factory) { module.exports = factory(require); }
));
<commit_msg>stop failing in isDomEvent() when data == null<commit_after>(function (define) {
define(function (require) {
"use strict";

	var eventNames,
		beforePhase, propagatingPhase, afterPhase, canceledPhase,
		resolver, addPropertyTransforms, simpleStrategy,
		undef;

	// TODO: make these configurable/extensible
	eventNames = {
		// collection item events
		add: 1,
		remove: 1,
		update: 1,
		target: 1,
		// edit mode events
		edit: 1,
		cancel: 1,
		save: 1,
		// multi-item events
		select: 1,
		unselect: 1,
		// network-level events
		join: 1,
		sync: 1,
		leave: 1
	};

	/**
	 * Signal that event has not yet been pushed onto the network.
	 * Return false to prevent the event from being pushed.
	 */
	beforePhase = {};

	/**
	 * Signal that event is currently being propagated to adapters.
	 */
	propagatingPhase = {};

	/**
	 * Signal that an event has already been pushed onto the network.
	 * Return value is ignored since the event has already propagated.
	 */
	afterPhase = {};

	/**
	 * Signal that an event was canceled and not pushed onto the network.
	 * Return value is ignored since the event has already propagated.
	 */
	canceledPhase = {};

	resolver = require('./AdapterResolver');
	addPropertyTransforms = require('./addPropertyTransforms');
	simpleStrategy = require('./network/strategy/default');

	/**
	 * @constructor
	 * @param primary {Object} primary data source
	 * @param options.strategy {strategyFunction} a strategy
	 *   Strategies determine if an event gets onto the network and then how
	 *   it's processed by the other data sources in the network.
	 *   Only one strategy can be applied to a network. However, strategies
	 *   can be composed/combined.
	 * @param [options.events] {Object} an object to receive events
	 *   from the hub's network
	 *
	 * @description
	 * The hub exposes actions that can be propagated through the network
	 * of data sources.  These actions are the same actions supported by
	 * the adapters that wrap the data sources.  Examples include: add(),
	 * remove(), update(), select(), unselect().  Each action has the same
	 * signature: `function (data, [source]) { return bool; }`  The source
	 * parameter is optional, but may be critical for proper operation of
	 * certain strategies.  Supplying a dom event from a dom node that was
	 * added to a hub (i.e. the dom node is under a NodeListAdapter's
	 * root node) will automatically be translated to the correct data item and
	 * the correct source.
	 */
	function Hub (options) {
		var adapters, eventQueue, strategy, publicApi, eventsApi;

		// TODO: Determine if we need to save Hub options and mix them into
		// options passed thru to adapters in addSource

		// all adapters in network
		adapters = [];

		// events to be processed (fifo)
		eventQueue = [];

//		callPublicEvent = callEventsApi;

		if (!options) options = {};

		strategy = options.strategy;
		if (!strategy) strategy = simpleStrategy(options.strategyOptions);

		// create public api
		eventsApi = publicApi = {
			addSource: addSource,
			destroy: destroy
		};

		// add standard events to publicApi
		addApiMethods(eventNames);

		// add events
		addApiEvents(eventNames);

		return publicApi;

		/**
		 * @memberOf Hub
		 * @param source
		 * @param options {Object}
		 * @param [options.eventNames] {Function} function that returns a
		 *   list of method names that should be considered events
		 *   If omitted, all methods, the standard event names are used.
		 * @param options.provide {Boolean} if true, initiates a 'sync' event
		 *   from this source's adapter
		 */
		function addSource (source, options) {
			var Adapter, adapter, proxy, method, eventFinder;

			if (!options) options = {};

			// create an adapter for this source
			// if we can't find an Adapter constructor, it is assumed to be an
			// adapter already.
			// TODO: revisit this assumption?
			// TODO: how to detect whether to use 'collection' or 'object' types
			Adapter = resolver(source, 'collection');
			adapter = Adapter ? new Adapter(source, options) : source;
			if (options.bindings) {
				adapter = addPropertyTransforms(adapter, collectPropertyTransforms(options.bindings));
			}

			proxy = beget(adapter);

			// keep copy of original source so we can match it up later
			proxy.origSource = source;
			proxy.provide = options.provide;

			// sniff for event hooks
			eventFinder = configureEventFinder(options.eventNames);

			// override methods that require event hooks
			for (method in adapter) {
				if (typeof adapter[method] == 'function') {
					if (eventFinder(method)) {
						// store original method on proxy (to stop recursion)
						proxy[method] = adapter[method];
						// change public api of adapter to call back into hub
						observeAdapterMethod(adapter, method, adapter[method]);
						// ensure hub has a public method of the same name
						addApiMethod(method);
						addApiEvent(method);
					}
				}
			}

			// save the proxied adapter
			adapters.push(proxy);

			// TODO: Should we use a boolean for the 2nd param to indicate join vs. leave
			// instead of using separate events?
			// Right now the second param is unused
			processEvent(proxy, null, 'join');

			return adapter;
		}

		function queueEvent (source, data, type) {
			var queueNeedsRestart;

			// if queue length is zero, we need to start processing it again
			queueNeedsRestart = eventQueue.length == 0;

			// enqueue event
			eventQueue.push({ source: source, data: data, type: type });

			// start processing, if necessary
			if (queueNeedsRestart) processNextEvent();
		}

		function processNextEvent () {
			var event;

			// get the next event, if any
			event = eventQueue.shift();

			// if there was an event, process it soon
			if (event) {
				setTimeout(function () {
					processEvent(event.source, event.data, event.type);
				}, 0);
			}
		}

		/*
			1. call events.beforeXXX(data)
			2. call strategy on each source/dest pair w/ event XXX and data
				- cancel iteration if any strategy returns false for any pair
			3. if not canceled, call events.XXX(data)
		 */
		function processEvent (source, data, type) {
			var context, strategyApi, i, adapter, canceled;

			// give public api a chance to see (and possibly cancel) event
			canceled = false === callEventsApi(data, camelize('before', type));

			// if public api cancels, the network never sees the event at all
			if (!canceled) {

				context = { phase: beforePhase };
				strategyApi = createStrategyApi(context);

				canceled = false === strategy(source, undef, data, type, strategyApi);
				i = adapters.length;

				context.phase = propagatingPhase;
				while (!canceled && (adapter = adapters[--i])) {
					if (false === strategy(source, adapter, data, type, strategyApi)) {
						canceled = true;
						break;
					}
				}

				context.phase = canceled ? canceledPhase : afterPhase;
				canceled = false === strategy(source, undef, data, type, strategyApi);

			}

			if (!canceled) callEventsApi(data, camelize('on', type));

			processNextEvent();

			return canceled;
		}

		function createStrategyApi (context) {
			function isPhase (phase) { return context.phase == phase; }
			return {
				queueEvent: queueEvent,
				isBefore: function () { return isPhase(beforePhase); },
				isAfter: function () { return isPhase(afterPhase); },
				isCanceled: function () { return isPhase(canceledPhase); },
				isPropagating: function () { return isPhase(propagatingPhase); }
			};
		}

		function observeAdapterMethod (adapter, type, origMethod) {
			return adapter[type] = function (data) {
				processEvent(adapter, data, type);
				return origMethod.call(adapter, data);
			};
		}

		function addApiMethods (eventNames) {
			for (var name in eventNames) {
				addApiMethod(name);
			}
		}

		function addApiMethod (name) {
			if (!publicApi[name]) {
				publicApi[name] = function (itemOrDomEvent) {
					var sourceInfo;
					if (isDomEvent(itemOrDomEvent)) {
						sourceInfo = convertFromDomEvent(itemOrDomEvent, adapters);
					}
					else {
						sourceInfo = {
							item: itemOrDomEvent,
							source: findAdapterForSource(arguments[1], adapters)
						};
					}
					return processEvent(sourceInfo.source, sourceInfo.item, name);
				};
			}
		}

		function addApiEvents (eventNames) {
			for (var name in eventNames) {
				addApiEvent(name);
			}
		}

		function addApiEvent (name) {
			var eventName = camelize('on', name);
			// add function stub to api
			if (!eventsApi[eventName]) {
				eventsApi[eventName] = function (data) {};
			}
			// add beforeXXX stub, too
			eventName = camelize('before', name);
			if (!eventsApi[eventName]) {
				eventsApi[eventName] = function (data) {};
			}
		}

		function callEventsApi (data, name) {
			try {
				return eventsApi[name](data);
			}
			catch (ex) {
				return false;
			}
		}

		function destroy () {
			var adapter;
			while ((adapter = adapters.pop())) {
				if (typeof adapter.destroy == 'function') {
					adapter.destroy();
				}
			}
		}

	}

	// TODO: get rid of this mess
	resolver.register(require('./ArrayAdapter'), 'collection');
	resolver.register(require('./dom/NodeListAdapter'), 'collection');
	resolver.register(require('./ResultSetAdapter'), 'collection');
	resolver.register(require('./QueryAdapter'), 'collection');
	resolver.register(require('./dom/NodeAdapter'), 'object');
	resolver.register(require('./ObjectAdapter'), 'object');
	resolver.register(require('./ResultAdapter'), 'object');

	return Hub;

	/**
	 * Signature for all network strategy functions.
	 * @param source {Object} the adapter that sourced the event
	 * @param dest {Object} the adapter receiving the event
	 * @param data {Object} any data associated with the event
	 * @param type {String} the type of event
	 * @param api {Object} helpful functions for strategies
	 * @returns {Boolean} whether event is allowed.
	 */
	function strategyFunction (source, dest, data, type, api) {};

	function configureEventFinder (option) {
		if (typeof option == 'function') return option;

		return function (name) { return eventNames.hasOwnProperty(name); };
	}

	function convertFromDomEvent (itemOrEvent, adapters) {
		var item, i, adapter;

		// loop through adapters that have the getItemForEvent() method
		// to try to find out which adapter and which data item
		i = 0;
		while (!item && (adapter = adapters[i++])) {
			if (adapter.getItemForEvent) {
				item = adapter.getItemForEvent(itemOrEvent);
			}
		}

		// TODO: make this an option?
//		if (!item) {
//			var err = new Error('Hub: could not find data item for dom event.');
//			err.event = itemOrEvent; // TODO: is this helpful?
//			throw err;
//		}

		return { item: item, source: adapter };
	}

	function isDomEvent (e) {
		// using feature sniffing to detect if this is an event object
		return e && e.target && e.stopPropagation && e.preventDefault;
	}

	function findAdapterForSource (source, adapters) {
		var i, adapter, found;

		// loop through adapters and find which one was created for this source
		i = 0;
		while (!found && (adapter = adapters[i++])) {
			if (adapter.origSource == source) {
				found = adapter;
			}
		}

		return found;
	}

	function collectPropertyTransforms (bindings) {
		var name, propertyTransforms, transform;

		propertyTransforms = {};
		for (name in bindings) {
			transform = bindings[name].transform;
			if (transform) {
				propertyTransforms[name] = transform;
			}
		}

		return propertyTransforms;
	}

	function Begetter () {}
	function beget (proto) {
		var obj;
		Begetter.prototype = proto;
		obj = new Begetter();
		Begetter.prototype = undef;
		return obj;
	}

	function noop () {}

	function camelize (prefix, name) {
		return prefix + name.charAt(0).toUpperCase() + name.substr(1);
	}

});
}(
	typeof define == 'function' && define.amd
		? define
		: function (factory) { module.exports = factory(require); }
));
<|endoftext|>


<commit_before>/*
    InK: InCopy/K4 automation suite
    By Nader Heidari
*/
if (typeof app === "undefined"){
app = {
    activeDocument: {
        stories: [{
            notes: [{
                name: "Note",
                texts: [{
                    contents: "{keywords: herp~derp}"
                }]
            }]
        }, {
            notes: [{
                name: "Note",
                texts: [{
                    contents: "{keywords: herp~derp}"
                }]
            }]
        }]
    }
};
}
var InK = {
    commands: {
        keywords: {

        }
    },
    interp: {
        getAllNotes: function() {
            var i, results = [],
                notes,
                noteQueue = [],
                item;
            for (i = 0; i < app.activeDocument.stories.length; i++) {
                notes = app.activeDocument.stories[i].notes;
                noteQueue = (function (e) {var i, arr=[];for (i=0;i<notes.length;i++){arr.push(notes[i])}return arr;})(notes);
                item = noteQueue.shift();
                while (item !== undefined) {
                    results.push(item);
                    item = noteQueue.shift();
                }
            }
            return results;
        },
        extractCommands: function(notes) {
            var commands = [],
                commandRegex = /\{.*?\}/;
            for (var i = 0; i < notes.length; i++) {
                if (commandRegex.test(notes[i].texts[0].contents)) {
                    commands.push({
                        note: notes[i],
                        contents: notes[i].texts[0].contents
                    });
                }
            }
            return commands;
        },
        parseCommands: function() {
            var derp = this.getAllNotes();
            var herp = this.extractCommands(derp);
            for (var i = 0; i < herp.length; i++) {
                herp[i].command = this.parseCommand(herp[i].contents);
            }
            return herp;
        },
        parseCommand: function(string) {
            var i, commandArray;
            var command = string.match(/\{(.*?)\}/)[1];
            commandArray = command.replace(/^\s+|\s+$/g, "").replace(/(^\{|\}$)/g, "").split(":");
            if (commandArray.length > 1) {
                for (i = 0; i < commandArray.length; i++) {
                    commandArray[i] = commandArray[i].replace(/^\s+|\s+$/g, "");
                }
                return commandArray;
            }
        }
    }
};
InK.interp.parseCommands();<commit_msg>building metadata map<commit_after>/*
    InK: InCopy/K4 automation suite
    By Nader Heidari
*/
if (typeof app === "undefined") {
    app = {
        activeDocument: {
            stories: [{
                notes: [{
                    name: "Note",
                    texts: [{
                        contents: "{keywords: herp~derp}"
                    }]
                }]
            }, {
                notes: [{
                    name: "Note",
                    texts: [{
                        contents: "{keywords: herp~derp}"
                    }]
                }]
            }]
        }
    };
}
var InK = {
    commands: {
        keywords: {

        }
    },
    interp: {
        getAllNotes: function() {
            var i, results = [],
                notes,
                noteQueue = [],
                item;
            for (i = 0; i < app.activeDocument.stories.length; i++) {
                notes = app.activeDocument.stories[i].notes;
                noteQueue = (function(e) {
                    var i, arr = [];
                    for (i = 0; i < notes.length; i++) {
                        arr.push(notes[i])
                    }
                    return arr;
                })(notes);
                item = noteQueue.shift();
                while (item !== undefined) {
                    results.push(item);
                    item = noteQueue.shift();
                }
            }
            return results;
        },
        extractCommands: function(notes) {
            var commands = [],
                commandRegex = /\{.*?\}/;
            for (var i = 0; i < notes.length; i++) {
                if (commandRegex.test(notes[i].texts[0].contents)) {
                    commands.push({
                        note: notes[i],
                        contents: notes[i].texts[0].contents
                    });
                }
            }
            return commands;
        },
        parseCommands: function() {
            var derp = this.getAllNotes();
            var herp = this.extractCommands(derp);
            for (var i = 0; i < herp.length; i++) {
                herp[i].command = this.parseCommand(herp[i].contents);
            }
            return herp;
        },
        parseCommand: function(string) {
            var i, commandArray;
            var command = string.match(/\{(.*?)\}/)[1];
            commandArray = command.replace(/^\s+|\s+$/g, "").replace(/(^\{|\}$)/g, "").split(":");
            if (commandArray.length > 1) {
                for (i = 0; i < commandArray.length; i++) {
                    commandArray[i] = commandArray[i].replace(/^\s+|\s+$/g, "");
                }
                return commandArray;
            }
        }
    }
};
InK.interp.parseCommands();

function getMetadataMap() {
    var defs = app.k4Publications[0].k4MetaDataDefs;
    var obj = {};
    for (var i = 0; i < defs.length; i++) {

        switch (defs[i].k4MetaDataDataType.toString()) {
            case "K4_STRING":
                obj[defs[i].k4Name] = "string";
                break;
            case "K4_VALUE_LIST":
                obj[defs[i].k4Name] = [];
                for (var j = 0; j < defs[i].k4MetaDataDefValueLists.length; j++) {
                    obj[defs[i].k4Name].pushdefs[i].k4MetaDataDefValueLists[j].k4Name);
        }
        break;
        case "K4_INTEGER":
            obj[defs[i].k4Name] = [true, false];
            break;
    }
}
return obj;
}<|endoftext|>


<commit_before>/*
 * ace.js
 * Copyright (C) 2015 tox <tox@rootkit>
 *
 * Distributed under terms of the MIT license.
 */

var RevealAce = window.RevealAce || (function() {
	function extend(o) {
		for(var i = 1; i < arguments.length; i++)
			for(var key in arguments[i])
				o[key] = arguments[i][key];
		return o;
	};
	function mkEditor(iframe) {
		var w = iframe.contentWindow, d = w.document;
		d.write("<!DOCTYPE html>"+
			""+
			""+
			""+
			""+
			"

"+
			""+
			"");
		iframe.onload = function() {
			function slidechanged( event ) {
				var e = iframe;
				for(var e = iframe; e && e != event.currentSlide; e = e.parentNode);
				if(!e)
					return;
				iframe.focus();
				editor.focus();
			}
			var editor = w.ace.edit(d.getElementById('editor'));
			var aceConf = extend({}, options, iframe.dataset);
			editor.setOptions({
				fontSize: "16pt"
			});
			editor.setValue(iframe.textContent);
			if(aceConf.theme)
				editor.setTheme(aceConf.theme);
			if(aceConf.mode)
				editor.getSession().setMode(aceConf.mode);
			if(options.oninit)
				options.oninit.call(editor, editor);
			if(iframe.dataset.oninit)
				(new Function("editor", iframe.dataset.oninit)).call(editor, editor);
			if(options.onchange)
				editor.getSession().on('change', options.onchange);
			if(iframe.dataset.onchange)
				editor.getSession().on('change', new Function("value", "editor", iframe.dataset.onchange));
			if(options.autoFocus) {
				Reveal.addEventListener('slidechanged', slidechanged);
				slidechanged({ currentSlide: Reveal.getCurrentSlide() })
			}
		};
		d.close();
	}

	var module = {};
	var config = Reveal.getConfig();
	var options = extend({
		className: "ace",
	}, config.ace || {});

	var aces = document.getElementsByClassName(options.className);
	for(var i = 0; i < aces.length; i++) {
		if(!aces[i].contentWindow) {
			console.warn("ACE Editors must be embedded in an IFrame");
			continue;
		}
		mkEditor(aces[i]);
	}

	return module;
})();
<commit_msg>wrap event listener; define default options<commit_after>/*
 * ace.js
 * Copyright (C) 2015 tox <tox@rootkit>
 *
 * Distributed under terms of the MIT license.
 */

var RevealAce = window.RevealAce || (function() {
	function extend(o) {
		for(var i = 1; i < arguments.length; i++)
			for(var key in arguments[i])
				o[key] = arguments[i][key];
		return o;
	};
	function mkEditor(iframe) {
		var w = iframe.contentWindow, d = w.document;
		d.write("<!DOCTYPE html>"+
			""+
			""+
			""+
			""+
			"

"+
			""+
			"");
		iframe.onload = function() {
			function slidechanged( event ) {
				var e = iframe;
				for(var e = iframe; e && e != event.currentSlide; e = e.parentNode);
				if(!e)
					return;
				iframe.focus();
				editor.focus();
			}
			var editor = w.ace.edit(d.getElementById('editor'));
			var aceConf = extend({}, options, iframe.dataset);
			editor.setOptions({
				fontSize: "16pt"
			});
			editor.setValue(iframe.textContent);
			if(aceConf.theme)
				editor.setTheme(aceConf.theme);
			if(aceConf.mode)
				editor.getSession().setMode(aceConf.mode);
			if(options.oninit)
				options.oninit.call(editor, editor);
			if(iframe.dataset.oninit)
				(new Function("editor", iframe.dataset.oninit)).call(editor, editor);
			if(options.onchange)
				editor.getSession().on('change', options.onchange);
			if(iframe.dataset.onchange) {
				onchange = new Function("value", "editor", onchange);
				editor.getSession().on('change', function() {
					return onchange(editor.getValue(), editor);
				});
			}
			if(options.autoFocus) {
				Reveal.addEventListener('slidechanged', slidechanged);
				slidechanged({ currentSlide: Reveal.getCurrentSlide() })
			}
		};
		d.close();
	}

	var module = {};
	var config = Reveal.getConfig();
	var options = extend({
		className: "ace",
		autoFocus: false,
		onchange: null,
		oninit: null
	}, config.ace || {});

	var aces = document.getElementsByClassName(options.className);
	for(var i = 0; i < aces.length; i++) {
		if(!aces[i].contentWindow) {
			console.warn("ACE Editors must be embedded in an IFrame");
			continue;
		}
		mkEditor(aces[i]);
	}

	return module;
})();
<|endoftext|>


<commit_before>"use strict";

var Promise = require('bluebird');
var extend = require('extend');
var request = Promise.promisify(require('request'));
var tokenUpdateTime = 12 * 60 * 1000;

/**
 * SignIn to Nfield API
 */
function signIn (defOptions, credentials, callback) {
  var options = {
    method : 'POST',
    uri : 'v1/SignIn',
    json : credentials
  };

  extend(true, options, defOptions);
  return request(options).nodeify(callback);
}

/**
 * Wrapper function for all Nfield API requests that checks if API token is not outdated, and refreshes it otherwise
 */
function requestWithTokenCheck (defOptions, credentials, token, options, callback) {
  var returnedPromise;
  var chainedPromise;

  extend(true, options, defOptions);
  chainedPromise = request(options);

  if (Date.now() - token.Timestamp > tokenUpdateTime) {
    returnedPromise = signIn(defOptions, credentials).then(function (data) {
      if (data[0].statusCode !== 200) {
        throw new Error(`${data[0].statusCode}: ${data[0].body.Message}`);
      } else {
        token.AuthenticationToken = data[0].body.AuthenticationToken;
        token.Timestamp = Date.now();
        return chainedPromise;
      }
    });
  } else {
    returnedPromise = chainedPromise;
  }

  returnedPromise.nodeify(callback);
  return returnedPromise;
}

/**
 * Returns Nfield survey status
 *
 * {@link https://api.nfieldmr.com/help/api/get-v1-surveys-surveyid-fieldwork-status}
 */
function getSurveyStatus (defOptions, credentials, token, surveyId, callback) {
  var options = {
    method : 'GET',
    uri : `v1/Surveys/${surveyId}/Fieldwork/Status`,
    headers : {
      'Authorization' : `Basic ${token.AuthenticationToken}`
    }
  };

  return requestWithTokenCheck(defOptions, credentials, token, options, callback);
}

/**
 * Starts Nfield survey
 *
 * {@link https://api.nfieldmr.com/help/api/put-v1-surveys-surveyid-fieldwork-start}
 */
function startSurvey (defOptions, credentials, token, surveyId, callback) {
  var options = {
    method : 'PUT',
    uri : `v1/Surveys/${surveyId}/Fieldwork/Start`,
    headers : {
      'Authorization' : `Basic ${token.AuthenticationToken}`
    }
  };

  return requestWithTokenCheck(defOptions, credentials, token, options, callback);
}

/**
 * Stops Nfield survey
 *
 * Has a 'magical' parameter called TerminateRunningInterviews, that never explained in any part of Nfield API documentation
 * and doesn't seem to affect anything when changed, but present here for the sake of making requests as close to API docs as possible (defaults to 'false')
 *
 * {@link https://api.nfieldmr.com/help/api/put-v1-surveys-surveyid-fieldwork-stop}
 */
function stopSurvey (defOptions, credentials, token, requestParams, callback) {
  var options = {
    method : 'PUT',
    uri : `v1/Surveys/${requestParams.SurveyId}/Fieldwork/Stop`,
    json : {
      'TerminateRunningInterviews' : requestParams.TerminateRunningInterviews || false
    },
    headers : {
      'Authorization' : `Basic ${token.AuthenticationToken}`
    }
  };

  return requestWithTokenCheck(defOptions, credentials, token, options, callback);
}

module.exports = {
  signIn : signIn,
  getSurveyStatus : getSurveyStatus,
  startSurvey : startSurvey,
  stopSurvey : stopSurvey
};<commit_msg>Move Auth header creation to requestWithTokenCheck<commit_after>"use strict";

var Promise = require('bluebird');
var extend = require('extend');
var request = Promise.promisify(require('request'));
var tokenUpdateTime = 12 * 60 * 1000;

/**
 * SignIn to Nfield API
 */
function signIn (defOptions, credentials, callback) {
  var options = {
    method : 'POST',
    uri : 'v1/SignIn',
    json : credentials
  };

  extend(true, options, defOptions);
  return request(options).nodeify(callback);
}

/**
 * Wrapper function for all Nfield API requests that checks if API token is not outdated, and refreshes it otherwise
 */
function requestWithTokenCheck (defOptions, credentials, token, options, callback) {
  var returnedPromise;
  var chainedPromise;

  options.headers = options.headers || {};
  options.headers.Authorization = `Basic ${token.AuthenticationToken}`;

  extend(true, options, defOptions);
  chainedPromise = request(options);

  if (Date.now() - token.Timestamp > tokenUpdateTime) {
    returnedPromise = signIn(defOptions, credentials).then(function (data) {
      if (data[0].statusCode !== 200) {
        throw new Error(`${data[0].statusCode}: ${data[0].body.Message}`);
      } else {
        token.AuthenticationToken = data[0].body.AuthenticationToken;
        token.Timestamp = Date.now();
        return chainedPromise;
      }
    });
  } else {
    returnedPromise = chainedPromise;
  }

  returnedPromise.nodeify(callback);
  return returnedPromise;
}

/**
 * Returns Nfield survey status
 *
 * {@link https://api.nfieldmr.com/help/api/get-v1-surveys-surveyid-fieldwork-status}
 */
function getSurveyStatus (defOptions, credentials, token, surveyId, callback) {
  var options = {
    method : 'GET',
    uri : `v1/Surveys/${surveyId}/Fieldwork/Status`
  };

  return requestWithTokenCheck(defOptions, credentials, token, options, callback);
}

/**
 * Starts Nfield survey
 *
 * {@link https://api.nfieldmr.com/help/api/put-v1-surveys-surveyid-fieldwork-start}
 */
function startSurvey (defOptions, credentials, token, surveyId, callback) {
  var options = {
    method : 'PUT',
    uri : `v1/Surveys/${surveyId}/Fieldwork/Start`
  };

  return requestWithTokenCheck(defOptions, credentials, token, options, callback);
}

/**
 * Stops Nfield survey
 *
 * Has a 'magical' parameter called TerminateRunningInterviews, that never explained in any part of Nfield API documentation
 * and doesn't seem to affect anything when changed, but present here for the sake of making requests as close to API docs as possible (defaults to 'false')
 *
 * {@link https://api.nfieldmr.com/help/api/put-v1-surveys-surveyid-fieldwork-stop}
 */
function stopSurvey (defOptions, credentials, token, requestParams, callback) {
  var options = {
    method : 'PUT',
    uri : `v1/Surveys/${requestParams.SurveyId}/Fieldwork/Stop`,
    json : {
      'TerminateRunningInterviews' : requestParams.TerminateRunningInterviews || false
    }
  };

  return requestWithTokenCheck(defOptions, credentials, token, options, callback);
}

module.exports = {
  signIn : signIn,
  getSurveyStatus : getSurveyStatus,
  startSurvey : startSurvey,
  stopSurvey : stopSurvey
};<|endoftext|>


<commit_before>var rest = require('rest-sugar');
var sugar = require('object-sugar');

var Library = require('./schemas').Library;

module.exports = function(app) {
    var root = '/api/v1';

    app.get(root + '/libraries/:name/:version', function(req, res) {
        var version = req.params.version;

        sugar.getOne(Library, {
            name: req.params.name
        }, function(err, library) {
            if(err) {
                return res.send(404);
            }

            var d = library.assets.filter(function(asset) {
                return asset.version === version;
            })[0];

            if(d && d.files) {
                return res.json(d.files);
            }

            return res.send(404);
        });
    });

    app.get(root + '/libraries/:name', function(req, res) {
        sugar.getOne(Library, {
            name: req.params.name
        }, function(err, library) {
            if(err) {
                return res.send(404);
            }

            res.json([library]);
        });
    });

    var api = rest(app, root, {
        libraries: Library
    }, sugar);

    api.pre(function() {
        api.use(rest.only('GET'));
    });
};
<commit_msg>Alias `/packages.php` to `/api/v1/libraries`<commit_after>var rest = require('rest-sugar');
var sugar = require('object-sugar');

var Library = require('./schemas').Library;

module.exports = function(app) {
    var root = '/api/v1';

    app.get(root + '/libraries/:name/:version', function(req, res) {
        var version = req.params.version;

        sugar.getOne(Library, {
            name: req.params.name
        }, function(err, library) {
            if(err) {
                return res.send(404);
            }

            var d = library.assets.filter(function(asset) {
                return asset.version === version;
            })[0];

            if(d && d.files) {
                return res.json(d.files);
            }

            return res.send(404);
        });
    });

    app.get(root + '/libraries/:name', function(req, res) {
        sugar.getOne(Library, {
            name: req.params.name
        }, function(err, library) {
            if(err) {
                return res.send(404);
            }

            res.json([library]);
        });
    });

    var api = rest(app, root, {
        libraries: Library
    }, sugar);

    api.pre(function() {
        api.use(rest.only('GET'));
    });

    var librariesRoute = app.routes.get.filter(function(v) {
        return v.path === '/api/v1/libraries';
    })[0];

    app.get('/packages.php', librariesRoute.callbacks[0]);
};
<|endoftext|>


<commit_before>// api:

function create_api_error(err_code, err_data, err_message) {
    return {
        error: err_code || 'internal:unknown',
        data: err_data || '',
        message: err_message || ''
    };
}

var api = {
    invalid_param: function(paramName) {
        return create_api_error('param:invalid', paramName, 'Invalid parameter: ' + paramName);
    },
    not_allowed: function(err_message) {
        return create_api_error('permission:notallowed', '', err_message);
    },
    not_found: function(err_data, err_message) {
        return create_api_error('resource:notfound', err_data, err_message);
    },
    server_error: function(err_code, err_data, err_message) {
        if (err_code instanceof Error) {
            return create_api_error('', err_code.message, err_code.stack);
        }
        if (err_code && err_code.error) {
            return err_code;
        }
        return create_api_error(err_code, err_data, err_message);
    },
    error: function(err_code, err_data, err_message) {
        if (err_code instanceof Error) {
            return create_api_error('', err_code.message, err_code.stack);
        }
        if (err_code && err_code.error) {
            return err_code;
        }
        return create_api_error(err_code, err_data, err_message);
    }
}

exports = module.exports = api;
<commit_msg>add APIError type<commit_after>// api:

function APIError(err_code, err_data, err_message) {
    this.error = err_code;
    this.data = err_data;
    this.message = err_message;
}

var api = {
    invalid_param: function(paramName) {
        return new APIError('parameter:invalid', paramName, 'Invalid parameter: ' + paramName);
    },
    not_allowed: function(err_message) {
        return new APIError('permission:notallowed', '', err_message);
    },
    not_found: function(err_data, err_message) {
        return new APIError('resource:notfound', err_data, err_message);
    },
    server_error: function(err_code, err_data, err_message) {
        if (err_code instanceof Error) {
            return new APIError('500', err_code.message, err_code.stack);
        }
        if (err_code && err_code.error) {
            return err_code;
        }
        return new APIError(err_code, err_data, err_message);
    },
    error: function(err_code, err_data, err_message) {
        console.log('create APIError: ' + err_code + ',' + err_data + ',' + err_message);
        return new APIError(err_code, err_data, err_message);
    },
    APIError: APIError
}

exports = module.exports = api;
<|endoftext|>


<commit_before>// The defaultable API
//
// Copyright 2011 Iris Couch
//
//    Licensed under the Apache License, Version 2.0 (the "License");
//    you may not use this file except in compliance with the License.
//    You may obtain a copy of the License at
//
//        http://www.apache.org/licenses/LICENSE-2.0
//
//    Unless required by applicable law or agreed to in writing, software
//    distributed under the License is distributed on an "AS IS" BASIS,
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//    See the License for the specific language governing permissions and
//    limitations under the License.

module.exports = {
                 }
<commit_msg>Remove unused file<commit_after><|endoftext|>


<commit_before>var assert = require('assert');
var fs = require('fs');
var path = require('path');
var util = require('util');
var mkdirp = require('mkdirp');
var optimist = require('optimist');
var config = require('./lib/config.js');
var index = require('./index');
var Migration = require('./lib/migration.js');
var Seeder = require('./lib/seeder.js');
var Migrator = require('./lib/migrator.js');
var log = require('./lib/log');
var pkginfo = require('pkginfo')(module, 'version');
var dotenv = require('dotenv');

//global declaration for detection like it's done in umigrate
dbm = require( './' );
async = require( 'async' );

var internals = {};

function dbmigrate(callback) {

  if(typeof(callback) === 'function')
    internals.onComplete = callback;

  dotenv.load();
  registerEvents();
  setDefaultArgv();
  loadConfig();
}

function registerEvents() {

  process.on('uncaughtException', function(err) {
    log.error(err.stack);
    process.exit(1);
  });
}

dbmigrate.prototype = {

  /**
    * Add a global defined variable to db-migrate, to enable access from
    * local migrations without configuring pathes.
    *
    * @return boolean
    */
  addGlobal: function(library) {

    try {
      require(library);
    } catch(e) {
      return false;
    }

    return true;
  },

  /**
    * Add a configuration option to dbmigrate.
    *
    * @return boolean
    */
  addConfiguration: function(description, args, type) {

    var name = args.shift();
    internals.argv.describe(name, description);

    for(var i = 0; i < args.length; ++i) {

      internals.argv.alias(args[i], name);
    }

    switch(type) {

      case 'string':
        internals.argv.string(name);
        break;

      case 'boolean':
        internals.argv.boolean(name);
        break;

      default:
        return false;
    }

    return true;
  },

  /**
    * Resets and sets argv to a specified new argv.
    */
  resetConfiguration: function(argv) {
    internals.argv = argv;
  },

  /**
    * Executes up a given number of migrations or a specific one.
    *
    * Defaults to up all migrations if no count is given.
    */
  up: function() {

    if(arguments.length > 0)
    {
      if(typeof(arguments[0]) === 'string') {
        argv.destination = argument[0];
      }
      else if(typeof(arguments[0]) === 'number') {
        argv.count = arguments[0];
      }

      executeUp();
    }
  },

  /**
    * Executes up a given number of migrations or a specific one.
    *
    * Defaults to up all migrations if no count is given.
    */
  down: function() {

    if(arguments.length > 0)
    {
      if(typeof(arguments[0]) === 'number') {
        argv.count = arguments[0];
      }

      executeDown();
    }
  },

  /**
    * Executes down for all currently migrated migrations.
    */
  reset: function() {

    argv.count = Number.MAX_VALUE;
    executeDown();
  },

  /**
    * Creates a correctly formatted migration
    */
  create: function(migrationName) {

    argv._.push(migrationName);
    executeCreate();
  },

  /**
    * Creates a database of the given dbname.
    */
  createDatabase: function(dbname) {

    argv._.push(dbname);
    global.mode = 'create';
  },

  /**
    * Drops a database of the given dbname.
    */
  dropDatabase: function(dbname) {

    argv._.push(dbname);
    global.mode = 'drop';
  },

  /**
    * Sets a config variable to the given value.
    *
    * @return value
    */
  setConfigParam: function(param, value) {

    return (argv[param] = value);
  },

  /**
    * Sets the callback to the default onComplete
    */
  setDefaultCallback: function() {

    internals.onComplete = onComplete;
  },

  /**
    * Let's the user customize the callback, which gets called after all
    * migrations have been done.
    */
  setCustomCallback: function(callback) {

    internals.onComplete = callback;
  },

  /**
    * Executes the default routine.
    */
  run: function() {

    run();

    if (argv['force-exit']) {
      log.verbose('Forcing exit');
      process.exit(0);
    }
  }

};

function setDefaultArgv() {

  internals.argv = optimist
      .default({
        verbose: false,
        table: 'migrations',
        'seeds-table': 'seeds',
        'force-exit': false,
        'sql-file': false,
        'no-transactions': false,
        config: process.cwd() + '/database.json',
        'migrations-dir': process.cwd() + '/migrations',
        'vcseeder-dir': process.cwd() + '/VCSeeder',
        'staticseeder-dir': process.cwd() + '/Seeder'})
      .usage('Usage: db-migrate [up|down|reset|create|db] [[dbname/]migrationName|all] [options]')

      .describe('env', 'The environment to run the migrations under (dev, test, prod).')
      .alias('e', 'env')
      .string('e')

      .describe('migrations-dir', 'The directory containing your migration files.')
      .alias('m', 'migrations-dir')
      .string('m')

      .describe('count', 'Max number of migrations to run.')
      .alias('c', 'count')
      .string('c')

      .describe('dry-run', 'Prints the SQL but doesn\'t run it.')
      .boolean('dry-run')

      .describe('force-exit', 'Forcibly exit the migration process on completion.')
      .boolean('force-exit')

      .describe('verbose', 'Verbose mode.')
      .alias('v', 'verbose')
      .boolean('v')

      .alias('h', 'help')
      .alias('h', '?')
      .boolean('h')

      .describe('version', 'Print version info.')
      .alias('i', 'version')
      .boolean('version')

      .describe('config', 'Location of the database.json file.')
      .string('config')

      .describe('sql-file', 'Automatically create two sql files for up and down statements in /sqls and generate the javascript code that loads them.')
      .boolean('sql-file')

      .describe('coffee-file', 'Create a coffeescript migration file')
      .boolean('coffee-file')

      .describe('migration-table', 'Set the name of the migration table, which stores the migration history.')
      .alias('table', 'migration-table')
      .alias('t', 'table')
      .string('t')

      .describe('seeds-table', 'Set the name of the seeds table, which stores the seed history.')
      .string('seeds-table')

      .describe('vcseeder-dir', 'Set the path to the Version Controlled Seeder directory.')
      .string('vcseeder-dir')

      .describe('staticseeder-dir', 'Set the path to the Seeder directory.')
      .string('staticseeder-dir')

      .describe('no-transactions', 'Explicitly disable transactions')
      .boolean('no-transactions')

      .argv;

  if (argv.version) {
    console.log(module.exports.version);
    process.exit(0);
  }

  if (argv.help || argv._.length === 0) {
    optimist.showHelp();
    process.exit(1);
  }

  global.migrationTable = argv.table;
  global.seedsTable = argv['seeds-table'];
  global.dbm = dbm;
  global.matching = '';
  global.mode;
  global.verbose = argv.verbose;
  global.notransactions = argv['no-transactions']
  global.dryRun = argv['dry-run'];
  if(global.dryRun) {
    log.info('dry run');
  }

}

function createMigrationDir(dir, callback) {
  fs.stat(dir, function(err, stat) {
    if (err) {
      mkdirp(dir, callback);
    } else {
      callback();
    }
  });
}

function loadConfig() {
  if (process.env.DATABASE_URL) {
    config.loadUrl(process.env.DATABASE_URL, argv.env);
  } else {
    config.load(argv.config, argv.env);
  }
  if(verbose) {
    var current = config.getCurrent();
    var s = JSON.parse(JSON.stringify(current.settings));

    if (s["password"])
      s["password"] = "******";

    log.info('Using', current.env, 'settings:', s);
  }
}

function executeCreate() {
  var folder, path;

  if(argv._.length === 0) {
    log.error('\'migrationName\' is required.');
    optimist.showHelp();
    process.exit(1);
  }

  createMigrationDir(argv['migrations-dir'], function(err) {
    if (err) {
      log.error('Failed to create migration directory at ', argv['migrations-dir'], err);
      process.exit(1);
    }

    argv.title = argv._.shift();
    folder = argv.title.split('/');

    argv.title = folder[folder.length - 2] || folder[0];
    path = argv['migrations-dir'];

    if(folder.length > 1) {

      path += '/';

      for(var i = 0; i < folder.length - 1; ++i) {

        path += folder[i] + '/';
      }
    }

    var templateType = Migration.TemplateType.DEFAULT_JS;
    if (shouldCreateSqlFiles()) {
      templateType = Migration.TemplateType.SQL_FILE_LOADER;
    } else if (shouldCreateCoffeeFile()) {
      templateType = Migration.TemplateType.DEFAULT_COFFEE;
    }
    var migration = new Migration(argv.title + (shouldCreateCoffeeFile() ? '.coffee' : '.js'), path, new Date(), templateType);
    index.createMigration(migration, function(err, migration) {
      assert.ifError(err);
      log.info(util.format('Created migration at %s', migration.path));
    });
  });

  if (shouldCreateSqlFiles()) {
    createSqlFiles();
  }
}

function shouldCreateSqlFiles() {
  return argv['sql-file'] || config['sql-file'];
}

function shouldCreateCoffeeFile() {
  return argv['coffee-file'] || config['coffee-file'];
}

function createSqlFiles() {
  var sqlDir = argv['migrations-dir'] + '/sqls';
  createMigrationDir(sqlDir, function(err) {
    if (err) {
      log.error('Failed to create migration directory at ', sqlDir, err);
      process.exit(1);
    }

    var templateTypeDefaultSQL = Migration.TemplateType.DEFAULT_SQL;
    var migrationUpSQL = new Migration(argv.title + '-up.sql', sqlDir, new Date(), templateTypeDefaultSQL);
    index.createMigration(migrationUpSQL, function(err, migration) {
      assert.ifError(err);
      log.info(util.format('Created migration up sql file at %s', migration.path));
    });
    var migrationDownSQL = new Migration(argv.title + '-down.sql', sqlDir, new Date(), templateTypeDefaultSQL);
    index.createMigration(migrationDownSQL, function(err, migration) {
      assert.ifError(err);
      log.info(util.format('Created migration down sql file at %s', migration.path));
    });
  });
}

function executeUp() {

  if(!argv.count) {
    argv.count = Number.MAX_VALUE;
  }

  index.connect(config.getCurrent().settings, Migrator, function(err, migrator) {
    assert.ifError(err);

    if(global.locTitle)
        migrator.migrationsDir = path.resolve(argv['migrations-dir'], global.locTitle);
    else
      migrator.migrationsDir = path.resolve(argv['migrations-dir']);

    migrator.driver.createMigrationsTable(function(err) {
      assert.ifError(err);
      log.verbose('migration table created');
      migrator.up(argv, internals.onComplete.bind(this, migrator));
    });
  });
}

function executeDown() {

  if(!argv.count) {
    log.info('Defaulting to running 1 down migration.');
    argv.count = 1;
  }

  index.connect(config.getCurrent().settings, Migrator, function(err, migrator) {
    assert.ifError(err);

    migrator.migrationsDir = path.resolve(argv['migrations-dir']);

    migrator.driver.createMigrationsTable(function(err) {
      assert.ifError(err);
      migrator.down(argv, internals.onComplete.bind(this, migrator));
    });
  });
}

function executeDB() {

  if(argv._.length > 0) {
    argv.dbname = argv._.shift().toString();
  }
  else {

    log.info('Error: You must enter a database name!');
    return;
  }

  index.driver(config.getCurrent().settings, function(err, db)
  {
    if(global.mode === 'create')
    {
      db.createDatabase(argv.dbname, { ifNotExists: true }, function()
      {
        if(err) {
          log.info('Error: Failed to create database!');
        }
        else {
          log.info('Created database "' + argv.dbname + '"');
        }

        db.close();
      });
    }
    else if(global.mode === 'drop')
    {
      db.dropDatabase(argv.dbname, { ifExists: true }, function()
      {
        if(err) {
          log.info('Error: Failed to drop database!');
        }
        else {
          log.info('Deleted database "' + argv.dbname + '"');
        }

        db.close();
      });
    }
    else
      return;
  });

}

function executeSeed() {

  if(argv._.length > 0) {
    argv.destination = argv._.shift().toString();
  }

  index.connect(config.getCurrent().settings, Seeder, function(err, seeder)
  {
    assert.ifError(err);

    seeder.seedDir = path.resolve(argv[(global.mode !== 'static') ? 'vcseeder-dir': 'staticseeder-dir']);
    seeder.seed(argv, internals.onComplete.bind(this, seeder));
  });
}

internals.onComplete = onComplete;

function onComplete(migrator, originalErr) {
  migrator.driver.close(function(err) {
    assert.ifError(originalErr);
    assert.ifError(err);
    log.info('Done');
  });
};

function run() {
  var action = argv._.shift(),
      folder = action.split(':');

  action = folder[0];

  loadConfig();

  switch(action) {
    case 'create':
      executeCreate();
      break;
    case 'up':
    case 'down':
    case 'reset':

      if(action === 'reset')
        argv.count = Number.MAX_VALUE;

      if(argv._.length > 0) {
        if (action === 'down') {
          log.info('Ignoring migration name for down migrations.  Use --count to control how many down migrations are run.');
          argv.destination = null;
        } else {
          argv.destination = argv._.shift().toString();
        }
      }

      if(folder[1])
      {
        global.matching = folder[1];
        global.migrationMode = folder[1];
      }

      if(action == 'up') {
        executeUp();
      } else {
        executeDown();
      }
      break;

    case 'db':

      if(folder.length < 1) {

        log.info('Please enter a valid command, i.e. db:create|db:drop');
      }
      else {

        global.mode = folder[1];
        executeDB();
      }
      break;
    case 'seed':

      global.mode = folder[1] || 'vc';
      global.migrationMode = folder[2];
      executeSeed();
      break;

    default:
      log.error('Invalid Action: Must be [up|down|create|reset|seed|db].');
      optimist.showHelp();
      process.exit(1);
      break;
  }
}

module.exports = dbmigrate;<commit_msg>add seeder method to api<commit_after>var assert = require('assert');
var fs = require('fs');
var path = require('path');
var util = require('util');
var mkdirp = require('mkdirp');
var optimist = require('optimist');
var config = require('./lib/config.js');
var index = require('./index');
var Migration = require('./lib/migration.js');
var Seeder = require('./lib/seeder.js');
var Migrator = require('./lib/migrator.js');
var log = require('./lib/log');
var pkginfo = require('pkginfo')(module, 'version');
var dotenv = require('dotenv');

//global declaration for detection like it's done in umigrate
dbm = require( './' );
async = require( 'async' );

var internals = {};

function dbmigrate(callback) {

  if(typeof(callback) === 'function')
    internals.onComplete = callback;

  dotenv.load();
  registerEvents();
  setDefaultArgv();
  loadConfig();
}

function registerEvents() {

  process.on('uncaughtException', function(err) {
    log.error(err.stack);
    process.exit(1);
  });
}

dbmigrate.prototype = {

  /**
    * Add a global defined variable to db-migrate, to enable access from
    * local migrations without configuring pathes.
    *
    * @return boolean
    */
  addGlobal: function(library) {

    try {
      require(library);
    } catch(e) {
      return false;
    }

    return true;
  },

  /**
    * Add a configuration option to dbmigrate.
    *
    * @return boolean
    */
  addConfiguration: function(description, args, type) {

    var name = args.shift();
    internals.argv.describe(name, description);

    for(var i = 0; i < args.length; ++i) {

      internals.argv.alias(args[i], name);
    }

    switch(type) {

      case 'string':
        internals.argv.string(name);
        break;

      case 'boolean':
        internals.argv.boolean(name);
        break;

      default:
        return false;
    }

    return true;
  },

  /**
    * Resets and sets argv to a specified new argv.
    */
  resetConfiguration: function(argv) {
    internals.argv = argv;
  },

  /**
    * Executes up a given number of migrations or a specific one.
    *
    * Defaults to up all migrations if no count is given.
    */
  up: function() {

    if(arguments.length > 0)
    {
      if(typeof(arguments[0]) === 'string') {
        argv.destination = argument[0];
      }
      else if(typeof(arguments[0]) === 'number') {
        argv.count = arguments[0];
      }

      executeUp();
    }
  },

  /**
    * Executes up a given number of migrations or a specific one.
    *
    * Defaults to up all migrations if no count is given.
    */
  down: function() {

    if(arguments.length > 0)
    {
      if(typeof(arguments[0]) === 'number') {
        argv.count = arguments[0];
      }

      executeDown();
    }
  },

  /**
    * Executes down for all currently migrated migrations.
    */
  reset: function() {

    argv.count = Number.MAX_VALUE;
    executeDown();
  },

  /**
    * Creates a correctly formatted migration
    */
  create: function(migrationName) {

    argv._.push(migrationName);
    executeCreate();
  },

  /**
    * Creates a database of the given dbname.
    */
  createDatabase: function(dbname) {

    argv._.push(dbname);
    global.mode = 'create';
  },

  /**
    * Drops a database of the given dbname.
    */
  dropDatabase: function(dbname) {

    argv._.push(dbname);
    global.mode = 'drop';
  },

  /**
    * Sets a config variable to the given value.
    *
    * @return value
    */
  setConfigParam: function(param, value) {

    return (argv[param] = value);
  },

  /**
    * Sets the callback to the default onComplete
    */
  setDefaultCallback: function() {

    internals.onComplete = onComplete;
  },

  /**
    * Let's the user customize the callback, which gets called after all
    * migrations have been done.
    */
  setCustomCallback: function(callback) {

    internals.onComplete = callback;
  },

  /**
    * Seeds either the static or version controlled seeders, controlled by
    * the passed mode.
    */
  seed: function(mode, scope) {

    global.mode = mode || 'vc';
    global.migrationMode = scope;
    executeSeed();
  },

  /**
    * Executes the default routine.
    */
  run: function() {

    run();

    if (argv['force-exit']) {
      log.verbose('Forcing exit');
      process.exit(0);
    }
  }

};

function setDefaultArgv() {

  internals.argv = optimist
      .default({
        verbose: false,
        table: 'migrations',
        'seeds-table': 'seeds',
        'force-exit': false,
        'sql-file': false,
        'no-transactions': false,
        config: process.cwd() + '/database.json',
        'migrations-dir': process.cwd() + '/migrations',
        'vcseeder-dir': process.cwd() + '/VCSeeder',
        'staticseeder-dir': process.cwd() + '/Seeder'})
      .usage('Usage: db-migrate [up|down|reset|create|db] [[dbname/]migrationName|all] [options]')

      .describe('env', 'The environment to run the migrations under (dev, test, prod).')
      .alias('e', 'env')
      .string('e')

      .describe('migrations-dir', 'The directory containing your migration files.')
      .alias('m', 'migrations-dir')
      .string('m')

      .describe('count', 'Max number of migrations to run.')
      .alias('c', 'count')
      .string('c')

      .describe('dry-run', 'Prints the SQL but doesn\'t run it.')
      .boolean('dry-run')

      .describe('force-exit', 'Forcibly exit the migration process on completion.')
      .boolean('force-exit')

      .describe('verbose', 'Verbose mode.')
      .alias('v', 'verbose')
      .boolean('v')

      .alias('h', 'help')
      .alias('h', '?')
      .boolean('h')

      .describe('version', 'Print version info.')
      .alias('i', 'version')
      .boolean('version')

      .describe('config', 'Location of the database.json file.')
      .string('config')

      .describe('sql-file', 'Automatically create two sql files for up and down statements in /sqls and generate the javascript code that loads them.')
      .boolean('sql-file')

      .describe('coffee-file', 'Create a coffeescript migration file')
      .boolean('coffee-file')

      .describe('migration-table', 'Set the name of the migration table, which stores the migration history.')
      .alias('table', 'migration-table')
      .alias('t', 'table')
      .string('t')

      .describe('seeds-table', 'Set the name of the seeds table, which stores the seed history.')
      .string('seeds-table')

      .describe('vcseeder-dir', 'Set the path to the Version Controlled Seeder directory.')
      .string('vcseeder-dir')

      .describe('staticseeder-dir', 'Set the path to the Seeder directory.')
      .string('staticseeder-dir')

      .describe('no-transactions', 'Explicitly disable transactions')
      .boolean('no-transactions')

      .argv;

  if (argv.version) {
    console.log(module.exports.version);
    process.exit(0);
  }

  if (argv.help || argv._.length === 0) {
    optimist.showHelp();
    process.exit(1);
  }

  global.migrationTable = argv.table;
  global.seedsTable = argv['seeds-table'];
  global.dbm = dbm;
  global.matching = '';
  global.mode;
  global.verbose = argv.verbose;
  global.notransactions = argv['no-transactions']
  global.dryRun = argv['dry-run'];
  if(global.dryRun) {
    log.info('dry run');
  }

}

function createMigrationDir(dir, callback) {
  fs.stat(dir, function(err, stat) {
    if (err) {
      mkdirp(dir, callback);
    } else {
      callback();
    }
  });
}

function loadConfig() {
  if (process.env.DATABASE_URL) {
    config.loadUrl(process.env.DATABASE_URL, argv.env);
  } else {
    config.load(argv.config, argv.env);
  }
  if(verbose) {
    var current = config.getCurrent();
    var s = JSON.parse(JSON.stringify(current.settings));

    if (s["password"])
      s["password"] = "******";

    log.info('Using', current.env, 'settings:', s);
  }
}

function executeCreate() {
  var folder, path;

  if(argv._.length === 0) {
    log.error('\'migrationName\' is required.');
    optimist.showHelp();
    process.exit(1);
  }

  createMigrationDir(argv['migrations-dir'], function(err) {
    if (err) {
      log.error('Failed to create migration directory at ', argv['migrations-dir'], err);
      process.exit(1);
    }

    argv.title = argv._.shift();
    folder = argv.title.split('/');

    argv.title = folder[folder.length - 2] || folder[0];
    path = argv['migrations-dir'];

    if(folder.length > 1) {

      path += '/';

      for(var i = 0; i < folder.length - 1; ++i) {

        path += folder[i] + '/';
      }
    }

    var templateType = Migration.TemplateType.DEFAULT_JS;
    if (shouldCreateSqlFiles()) {
      templateType = Migration.TemplateType.SQL_FILE_LOADER;
    } else if (shouldCreateCoffeeFile()) {
      templateType = Migration.TemplateType.DEFAULT_COFFEE;
    }
    var migration = new Migration(argv.title + (shouldCreateCoffeeFile() ? '.coffee' : '.js'), path, new Date(), templateType);
    index.createMigration(migration, function(err, migration) {
      assert.ifError(err);
      log.info(util.format('Created migration at %s', migration.path));
    });
  });

  if (shouldCreateSqlFiles()) {
    createSqlFiles();
  }
}

function shouldCreateSqlFiles() {
  return argv['sql-file'] || config['sql-file'];
}

function shouldCreateCoffeeFile() {
  return argv['coffee-file'] || config['coffee-file'];
}

function createSqlFiles() {
  var sqlDir = argv['migrations-dir'] + '/sqls';
  createMigrationDir(sqlDir, function(err) {
    if (err) {
      log.error('Failed to create migration directory at ', sqlDir, err);
      process.exit(1);
    }

    var templateTypeDefaultSQL = Migration.TemplateType.DEFAULT_SQL;
    var migrationUpSQL = new Migration(argv.title + '-up.sql', sqlDir, new Date(), templateTypeDefaultSQL);
    index.createMigration(migrationUpSQL, function(err, migration) {
      assert.ifError(err);
      log.info(util.format('Created migration up sql file at %s', migration.path));
    });
    var migrationDownSQL = new Migration(argv.title + '-down.sql', sqlDir, new Date(), templateTypeDefaultSQL);
    index.createMigration(migrationDownSQL, function(err, migration) {
      assert.ifError(err);
      log.info(util.format('Created migration down sql file at %s', migration.path));
    });
  });
}

function executeUp() {

  if(!argv.count) {
    argv.count = Number.MAX_VALUE;
  }

  index.connect(config.getCurrent().settings, Migrator, function(err, migrator) {
    assert.ifError(err);

    if(global.locTitle)
        migrator.migrationsDir = path.resolve(argv['migrations-dir'], global.locTitle);
    else
      migrator.migrationsDir = path.resolve(argv['migrations-dir']);

    migrator.driver.createMigrationsTable(function(err) {
      assert.ifError(err);
      log.verbose('migration table created');
      migrator.up(argv, internals.onComplete.bind(this, migrator));
    });
  });
}

function executeDown() {

  if(!argv.count) {
    log.info('Defaulting to running 1 down migration.');
    argv.count = 1;
  }

  index.connect(config.getCurrent().settings, Migrator, function(err, migrator) {
    assert.ifError(err);

    migrator.migrationsDir = path.resolve(argv['migrations-dir']);

    migrator.driver.createMigrationsTable(function(err) {
      assert.ifError(err);
      migrator.down(argv, internals.onComplete.bind(this, migrator));
    });
  });
}

function executeDB() {

  if(argv._.length > 0) {
    argv.dbname = argv._.shift().toString();
  }
  else {

    log.info('Error: You must enter a database name!');
    return;
  }

  index.driver(config.getCurrent().settings, function(err, db)
  {
    if(global.mode === 'create')
    {
      db.createDatabase(argv.dbname, { ifNotExists: true }, function()
      {
        if(err) {
          log.info('Error: Failed to create database!');
        }
        else {
          log.info('Created database "' + argv.dbname + '"');
        }

        db.close();
      });
    }
    else if(global.mode === 'drop')
    {
      db.dropDatabase(argv.dbname, { ifExists: true }, function()
      {
        if(err) {
          log.info('Error: Failed to drop database!');
        }
        else {
          log.info('Deleted database "' + argv.dbname + '"');
        }

        db.close();
      });
    }
    else
      return;
  });

}

function executeSeed() {

  if(argv._.length > 0) {
    argv.destination = argv._.shift().toString();
  }

  index.connect(config.getCurrent().settings, Seeder, function(err, seeder)
  {
    assert.ifError(err);

    seeder.seedDir = path.resolve(argv[(global.mode !== 'static') ? 'vcseeder-dir': 'staticseeder-dir']);
    seeder.seed(argv, internals.onComplete.bind(this, seeder));
  });
}

internals.onComplete = onComplete;

function onComplete(migrator, originalErr) {
  migrator.driver.close(function(err) {
    assert.ifError(originalErr);
    assert.ifError(err);
    log.info('Done');
  });
};

function run() {
  var action = argv._.shift(),
      folder = action.split(':');

  action = folder[0];

  loadConfig();

  switch(action) {
    case 'create':
      executeCreate();
      break;
    case 'up':
    case 'down':
    case 'reset':

      if(action === 'reset')
        argv.count = Number.MAX_VALUE;

      if(argv._.length > 0) {
        if (action === 'down') {
          log.info('Ignoring migration name for down migrations.  Use --count to control how many down migrations are run.');
          argv.destination = null;
        } else {
          argv.destination = argv._.shift().toString();
        }
      }

      if(folder[1])
      {
        global.matching = folder[1];
        global.migrationMode = folder[1];
      }

      if(action == 'up') {
        executeUp();
      } else {
        executeDown();
      }
      break;

    case 'db':

      if(folder.length < 1) {

        log.info('Please enter a valid command, i.e. db:create|db:drop');
      }
      else {

        global.mode = folder[1];
        executeDB();
      }
      break;
    case 'seed':

      global.mode = folder[1] || 'vc';
      global.migrationMode = folder[2];
      executeSeed();
      break;

    default:
      log.error('Invalid Action: Must be [up|down|create|reset|seed|db].');
      optimist.showHelp();
      process.exit(1);
      break;
  }
}

module.exports = dbmigrate;<|endoftext|>


<commit_before>const fs = require('fs');
const qs = require('qs');
const https = require('https');
const url = require('url');
const request = require('request');
const passport = require('passport');
const config = require('./config');
const bodyParser = require('body-parser');
const sgMail = require('@sendgrid/mail');
const requestUtils = require('./client/src/shared/requestUtils');
const notifications = require('./notifications');
const crypto = require('crypto');
const compare = require('secure-compare');

module.exports = function(app) {
  const GITHUB_API_ROOT = 'https://api.github.com';

  const getlocalAppRootUrl = request => {
    return process.env.NODE_ENV == 'production'
      ? url.format({
          protocol: 'https', // request.protocol returns http for now since the node server itself is only using http. However the api is used over https thanks to azure / IIS
          host: request.hostname,
          // port: request.port,
          pathname: ''
        })
      : 'https://localhost:3000'; // hard coded value in development because the request came through the webpack dev server on a different port and via https.
  };

  function loadAppData() {
    return JSON.parse(fs.readFileSync(`${__dirname}/${config.app.groupConfigPath}`, 'utf-8'));
  }

  const appData = loadAppData();

  // Utility functions
  const sendMail = (to, title, body) => {
    sgMail.setApiKey(process.env.SENDGRID_API_KEY);
    const msg = {
      to: to,
      from: config.app.emailSender,
      subject: title,
      html: body,
    };
    return sgMail.send(msg);
  };

  const getProxyRequestOptions = url => ({
    url: GITHUB_API_ROOT + url.replace('/api', ''),
    headers: {
      Authorization: `token ${config.github.botToken}`
      //,"user-agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36"
      ,"accept-encoding": "identity",
    }
  });

  const rewriteResponseHeaders = (request, response) => {
    if (response.headers.link) {
      response.headers.link = response.headers.link.replace(GITHUB_API_ROOT, getlocalAppRootUrl(request) + '/api');
      console.log(`Request App: Rewritten response.headers.link: ${JSON.stringify(response.headers.link)}`);
    } else {
      console.log(`Request App: response.headers.link falsy, nothing to rewrite`);
    }
  };

  const genericErrorHandler = (error, response, body) => {
    if (error) {
      if (error.code === 'ECONNREFUSED') {
        console.error('Refused connection');
      } else if (error.code === 'ETIMEDOUT') {
        console.error('Connection timed out');
      } else {
        throw error;
      }
    }
  };

  const validateRepository = (organisation, repo, projects) => {
    if (projects.filter(p => p.organisation == organisation && p.repository == repo).length == 0) {
      console.error(`Request App: Repository not found : ${organisation}/${repo}`);
      return false;
    } else {
      console.log(`Request App: Valid repository: ${organisation}/${repo}`);
      return true;
    }
  };

  // App end points
  app.get('/api/projects', passport.authenticate('oauth-bearer', { session: false }), function(req, res) {
    res.json(appData);
  });

  app.get('/api/authenticate/github/:code', passport.authenticate('oauth-bearer', { session: false }), function(req, res) {
    const r = request.post(
      {
        url: 'https://github.com/login/oauth/access_token',
        json: {
          client_id: config.github.clientID,
          client_secret: config.github.clientSecret,
          code: req.params.code
        }
      },
      genericErrorHandler
    );
    r.pipe(res);
  });

  // proxy to github api end points
  app.get('/api/repos/:organisation/:repo/issues', passport.authenticate('oauth-bearer', { session: false }), function(req, res) {
    console.log('Request App received request to: ', req.url);

    if (!validateRepository(req.params.organisation, req.params.repo, appData.projects))
      res.status(403).send({ error: 'Invalid repository name' });

    console.log(`Request App: Listing issues on repository ${req.params.organisation}/${req.params.repo}`);

    // this actually does a request to github, which is not required
    const gitHubRequest = request(getProxyRequestOptions(req.url)); // add the authorization that github wants

    console.log('Request App: Proxying request to: ', getProxyRequestOptions(req.url).url);

    req
    .pipe(gitHubRequest, genericErrorHandler) // send to git hub
    .on('response', response => rewriteResponseHeaders(req, response)) // response from github, monkey with the headers for some reason. Looking at the logs it doesn't seem to do anything
    .pipe(res); // pipe response from github back to response from this route / function
  });

  app.get('/api/repositories/:repoId/issues', passport.authenticate('oauth-bearer', { session: false }), function(req, res) {
    console.log(`going through pages on repository ${req.params.repoId}`);
    const r = request(getProxyRequestOptions(req.url));
    console.log('Proxied request to: ', getProxyRequestOptions(req.url).url);
    req.pipe(r, genericErrorHandler).on('response', response => rewriteResponseHeaders(req, response)).pipe(res);
  });

  app.get('/api/repos/:organisation/:repo/issues/:issueId', passport.authenticate('oauth-bearer', { session: false }), function(req, res) {
    if (!validateRepository(req.params.organisation, req.params.repo, appData.projects))
      res.status(403).send({ error: 'Invalid repository name' });

    const r = request(getProxyRequestOptions(req.url));
    console.log('Proxied request to: ', getProxyRequestOptions(req.url).url);
    req.pipe(r, genericErrorHandler).pipe(res);
  });

  app.post('/api/repos/:organisation/:repo/issues', passport.authenticate('oauth-bearer', { session: false }), function(req, res) {
    if (!validateRepository(req.params.organisation, req.params.repo, appData.projects))
      res.status(403).send({ error: 'Invalid repository name' });

    console.log(`creating issue on repository ${req.params.organisation}/${req.params.repo}`);
    const r = request.post(getProxyRequestOptions(req.url));
    req.pipe(r, genericErrorHandler).pipe(res);
  });

  app.get('/api/repos/:organisation/:repo/issues/:issueId/comments', passport.authenticate('oauth-bearer', { session: false }), function(req, res) {
    if (!validateRepository(req.params.organisation, req.params.repo, appData.projects))
      res.status(403).send({ error: 'Invalid repository name' });

    const r = request(getProxyRequestOptions(req.url));
    console.log('Proxied request to: ', getProxyRequestOptions(req.url).url);
    req.pipe(r, genericErrorHandler).pipe(res);
  });

  app.post('/api/repos/:organisation/:repo/issues/:issueId/comments', passport.authenticate('oauth-bearer', { session: false }), function(req, res) {
    if (!validateRepository(req.params.organisation, req.params.repo, appData.projects))
      res.status(403).send({ error: 'Invalid repository name' });

    console.log(`adding comment on issue in repository ${req.params.organisation}/${req.params.repo}`);
    const r = request.post(getProxyRequestOptions(req.url));
    req.pipe(r, genericErrorHandler).pipe(res);
  });

  // Calculate the X-Hub-Signature header value.
  function getSignature(buf) {
    var hmac = crypto.createHmac("sha1", config.github.webHookSecret);
    hmac.update(buf, "utf-8");
    return "sha1=" + hmac.digest("hex");
  }

  // Verify function compatible with body-parser to retrieve the request payload.
  // Read more: https://github.com/expressjs/body-parser#verify
  function verifyRequest(req, res, buf, encoding) {
    var expected = req.headers['x-hub-signature'];
    var calculated = getSignature(buf);
    console.log("X-Hub-Signature:", expected, "Content:", "-" + buf.toString('utf8') + "-");
    if (!compare(expected,calculated)) {
      throw new Error("Invalid signature.");
    } else {
      console.log("Valid signature!");
    }
  }

  function userLoginFromGitHubRequestBody(body){
    return
      req.body &&
      req.body.issue &&
      req.body.issue.user.login;
  }

  function isGitHubWebHookRequestValid(req)  {
    const eventType = req.headers['x-github-event'];

    return
      eventType &&
      ['issues', 'issue_comment'].includes(eventType);// &&
      //userLoginFromGitHubRequestBody(req.body) === config.github.botLogin;
  }

  function handleInvalidGitHubWebHookRequest(req, res)  {
    console.log('RequestApp: The following github webhook was not valid:');
    console.log(req.headers['x-github-event']);
    console.log(config.github.botLogin);
    console.log(userLoginFromGitHubRequestBody(req.body));
    req.body.issue && console.log(`issue ${JSON.stringify(req.body.issue)}`);
    req.body.issue && req.body.issue.user && console.log(`issue.user: ${JSON.stringify(req.body.issue.user)}`);
    req.body.issue && req.body.issue.user && req.body.issue.user.login && console.log(`issue.user.login: ${JSON.stringify(req.body.issue.user.login)}`);
    console.log(JSON.stringify(req.headers));
    console.log(JSON.stringify(req.body));

    res.status(422).send({
      message: 'The github webhook request is not valid'
    });
  }

  function sendEmailsForGitHubWebHook(req, res)  {
    try {
      trySendEmailsForGitHubWebHook(req, res);
    } catch (error) {
      handleErrorForEmailsForGitHubWebHook(req, res, error);
    }
  }

  function trySendEmailsForGitHubWebHook(req, res)  {
    const { email } = requestUtils.getCreator(req.body.issue);
    const project = notifications.findProject(req.body, appData.projects);
    const requestUrl = notifications.getRequestUrl(req.body.issue.number, project, getlocalAppRootUrl(req));
    const eventType = req.headers['x-github-event'];
    const { subject, content } = notifications.getNotificationText(eventType, req.body, project.name, requestUrl);

    console.log(`Request app: Email notification starting`);

    if (email && subject && content) {
      sendMail(email, subject, content)
        .then(response => {
          console.log(response.statusCode, `Email notification sent successfully to ${email}`);
        })
        .catch(error => {
          //error is an instance of SendGridError
          //The full response is attached to error.response
          console.log(error.response);
        });
    }

    console.log(`Request app: Email notification returning response`);
    res.json({ message: 'web hook processed' });
  }

  function handleErrorForEmailsForGitHubWebHook(req, res, error)  {
    console.log('RequestApp: An error occcured while attempting to process the following github webhook:');
    console.log(error);
    console.log('RequestApp: webhook data:');
    console.log(JSON.stringify(req.headers));
    console.log(JSON.stringify(req.body));
    res.status(500).json({
      message: 'An error occcured while attempting to process a github webhook',
    });
  }

  // handle github web hooks
  app.post('/api/github-webhooks', [bodyParser.json({verify:verifyRequest})], function(req, res) {

    // this message is unsecured because github doesn't have a token to call us
    // instead we check that this message was generated by our github instance
    // by checking that the signature of the body was created with the correct secret
    console.log(`RequestApp: received web hook:`);
    console.log(JSON.stringify(req.headers));
    console.log(JSON.stringify(req.body));
    console.log(`RequestApp: received web hook action: ${req.headers['x-github-event']}`);

    //if (isGitHubWebHookRequestValid(req)) {
      sendEmailsForGitHubWebHook(req, res)
    //} else {
    // handleInvalidGitHubWebHookRequest(req, res);
    //}
  });
};
<commit_msg>Fix up validation go github webhook requests<commit_after>const fs = require('fs');
const qs = require('qs');
const https = require('https');
const url = require('url');
const request = require('request');
const passport = require('passport');
const config = require('./config');
const bodyParser = require('body-parser');
const sgMail = require('@sendgrid/mail');
const requestUtils = require('./client/src/shared/requestUtils');
const notifications = require('./notifications');
const crypto = require('crypto');
const compare = require('secure-compare');

module.exports = function(app) {
  const GITHUB_API_ROOT = 'https://api.github.com';

  const getlocalAppRootUrl = request => {
    return process.env.NODE_ENV == 'production'
      ? url.format({
          protocol: 'https', // request.protocol returns http for now since the node server itself is only using http. However the api is used over https thanks to azure / IIS
          host: request.hostname,
          // port: request.port,
          pathname: ''
        })
      : 'https://localhost:3000'; // hard coded value in development because the request came through the webpack dev server on a different port and via https.
  };

  function loadAppData() {
    return JSON.parse(fs.readFileSync(`${__dirname}/${config.app.groupConfigPath}`, 'utf-8'));
  }

  const appData = loadAppData();

  // Utility functions
  const sendMail = (to, title, body) => {
    sgMail.setApiKey(process.env.SENDGRID_API_KEY);
    const msg = {
      to: to,
      from: config.app.emailSender,
      subject: title,
      html: body,
    };
    return sgMail.send(msg);
  };

  const getProxyRequestOptions = url => ({
    url: GITHUB_API_ROOT + url.replace('/api', ''),
    headers: {
      Authorization: `token ${config.github.botToken}`
      //,"user-agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36"
      ,"accept-encoding": "identity",
    }
  });

  const rewriteResponseHeaders = (request, response) => {
    if (response.headers.link) {
      response.headers.link = response.headers.link.replace(GITHUB_API_ROOT, getlocalAppRootUrl(request) + '/api');
      console.log(`Request App: Rewritten response.headers.link: ${JSON.stringify(response.headers.link)}`);
    } else {
      console.log(`Request App: response.headers.link falsy, nothing to rewrite`);
    }
  };

  const genericErrorHandler = (error, response, body) => {
    if (error) {
      if (error.code === 'ECONNREFUSED') {
        console.error('Refused connection');
      } else if (error.code === 'ETIMEDOUT') {
        console.error('Connection timed out');
      } else {
        throw error;
      }
    }
  };

  const validateRepository = (organisation, repo, projects) => {
    if (projects.filter(p => p.organisation == organisation && p.repository == repo).length == 0) {
      console.error(`Request App: Repository not found : ${organisation}/${repo}`);
      return false;
    } else {
      console.log(`Request App: Valid repository: ${organisation}/${repo}`);
      return true;
    }
  };

  // App end points
  app.get('/api/projects', passport.authenticate('oauth-bearer', { session: false }), function(req, res) {
    res.json(appData);
  });

  app.get('/api/authenticate/github/:code', passport.authenticate('oauth-bearer', { session: false }), function(req, res) {
    const r = request.post(
      {
        url: 'https://github.com/login/oauth/access_token',
        json: {
          client_id: config.github.clientID,
          client_secret: config.github.clientSecret,
          code: req.params.code
        }
      },
      genericErrorHandler
    );
    r.pipe(res);
  });

  // proxy to github api end points
  app.get('/api/repos/:organisation/:repo/issues', passport.authenticate('oauth-bearer', { session: false }), function(req, res) {
    console.log('Request App received request to: ', req.url);

    if (!validateRepository(req.params.organisation, req.params.repo, appData.projects))
      res.status(403).send({ error: 'Invalid repository name' });

    console.log(`Request App: Listing issues on repository ${req.params.organisation}/${req.params.repo}`);

    // this actually does a request to github, which is not required
    const gitHubRequest = request(getProxyRequestOptions(req.url)); // add the authorization that github wants

    console.log('Request App: Proxying request to: ', getProxyRequestOptions(req.url).url);

    req
    .pipe(gitHubRequest, genericErrorHandler) // send to git hub
    .on('response', response => rewriteResponseHeaders(req, response)) // response from github, monkey with the headers for some reason. Looking at the logs it doesn't seem to do anything
    .pipe(res); // pipe response from github back to response from this route / function
  });

  app.get('/api/repositories/:repoId/issues', passport.authenticate('oauth-bearer', { session: false }), function(req, res) {
    console.log(`going through pages on repository ${req.params.repoId}`);
    const r = request(getProxyRequestOptions(req.url));
    console.log('Proxied request to: ', getProxyRequestOptions(req.url).url);
    req.pipe(r, genericErrorHandler).on('response', response => rewriteResponseHeaders(req, response)).pipe(res);
  });

  app.get('/api/repos/:organisation/:repo/issues/:issueId', passport.authenticate('oauth-bearer', { session: false }), function(req, res) {
    if (!validateRepository(req.params.organisation, req.params.repo, appData.projects))
      res.status(403).send({ error: 'Invalid repository name' });

    const r = request(getProxyRequestOptions(req.url));
    console.log('Proxied request to: ', getProxyRequestOptions(req.url).url);
    req.pipe(r, genericErrorHandler).pipe(res);
  });

  app.post('/api/repos/:organisation/:repo/issues', passport.authenticate('oauth-bearer', { session: false }), function(req, res) {
    if (!validateRepository(req.params.organisation, req.params.repo, appData.projects))
      res.status(403).send({ error: 'Invalid repository name' });

    console.log(`creating issue on repository ${req.params.organisation}/${req.params.repo}`);
    const r = request.post(getProxyRequestOptions(req.url));
    req.pipe(r, genericErrorHandler).pipe(res);
  });

  app.get('/api/repos/:organisation/:repo/issues/:issueId/comments', passport.authenticate('oauth-bearer', { session: false }), function(req, res) {
    if (!validateRepository(req.params.organisation, req.params.repo, appData.projects))
      res.status(403).send({ error: 'Invalid repository name' });

    const r = request(getProxyRequestOptions(req.url));
    console.log('Proxied request to: ', getProxyRequestOptions(req.url).url);
    req.pipe(r, genericErrorHandler).pipe(res);
  });

  app.post('/api/repos/:organisation/:repo/issues/:issueId/comments', passport.authenticate('oauth-bearer', { session: false }), function(req, res) {
    if (!validateRepository(req.params.organisation, req.params.repo, appData.projects))
      res.status(403).send({ error: 'Invalid repository name' });

    console.log(`adding comment on issue in repository ${req.params.organisation}/${req.params.repo}`);
    const r = request.post(getProxyRequestOptions(req.url));
    req.pipe(r, genericErrorHandler).pipe(res);
  });

  // Calculate the X-Hub-Signature header value.
  function getSignature(buf) {
    var hmac = crypto.createHmac("sha1", config.github.webHookSecret);
    hmac.update(buf, "utf-8");
    return "sha1=" + hmac.digest("hex");
  }

  // Verify function compatible with body-parser to retrieve the request payload.
  // Read more: https://github.com/expressjs/body-parser#verify
  function verifyRequest(req, res, buf, encoding) {
    var expected = req.headers['x-hub-signature'];
    var calculated = getSignature(buf);
    console.log("X-Hub-Signature:", expected, "Content:", "-" + buf.toString('utf8') + "-");
    if (!compare(expected,calculated)) {
      throw new Error("Invalid signature.");
    } else {
      console.log("Valid signature!");
    }
  }

  function userLoginFromGitHubRequestBody(body){
    return (body && body.issue.user) ? body.issue.user.login : "";
  }

  function isGitHubWebHookRequestValid(req)  {
    return ['issues', 'issue_comment'].includes(req.headers['x-github-event']) && userLoginFromGitHubRequestBody(req.body) === config.github.botLogin;
  }

  function handleInvalidGitHubWebHookRequest(req, res)  {
    console.log('RequestApp: The following github webhook was not valid:');
    console.log(req.headers['x-github-event']);
    console.log(config.github.botLogin);
    console.log(userLoginFromGitHubRequestBody(req.body));

    res.status(422).send({
      message: 'The github webhook request is not valid'
    });
  }

  function sendEmailsForGitHubWebHook(req, res)  {
    try {
      trySendEmailsForGitHubWebHook(req, res);
    } catch (error) {
      handleErrorForEmailsForGitHubWebHook(req, res, error);
    }
  }

  function trySendEmailsForGitHubWebHook(req, res)  {
    const { email } = requestUtils.getCreator(req.body.issue);
    const project = notifications.findProject(req.body, appData.projects);
    const requestUrl = notifications.getRequestUrl(req.body.issue.number, project, getlocalAppRootUrl(req));
    const eventType = req.headers['x-github-event'];
    const { subject, content } = notifications.getNotificationText(eventType, req.body, project.name, requestUrl);

    console.log(`Request app: Email notification starting`);

    if (email && subject && content) {
      sendMail(email, subject, content)
        .then(response => {
          console.log(response.statusCode, `Email notification sent successfully to ${email}`);
        })
        .catch(error => {
          //error is an instance of SendGridError
          //The full response is attached to error.response
          console.log(error.response);
        });
    }

    console.log(`Request app: Email notification returning response`);
    res.json({ message: 'web hook processed' });
  }

  function handleErrorForEmailsForGitHubWebHook(req, res, error)  {
    console.log('RequestApp: An error occcured while attempting to process the following github webhook:');
    console.log(error);

    res.status(500).json({
      message: 'An error occcured while attempting to process a github webhook',
    });
  }

  // handle github web hooks
  app.post('/api/github-webhooks', [bodyParser.json({verify:verifyRequest})], function(req, res) {

    // this message is unsecured because github doesn't have a token to call us
    // instead we check that this message was generated by our github instance
    // by checking that the signature of the body was created with the correct secret
    console.log(`RequestApp: received web hook action: ${req.headers['x-github-event']}`);
    console.log(JSON.stringify(req.headers));
    console.log(JSON.stringify(req.body));

    //const valid = isGitHubWebHookRequestValid(req);
    console.log(isGitHubWebHookRequestValid(req));

    if (isGitHubWebHookRequestValid(req)) {
      sendEmailsForGitHubWebHook(req, res)
    } else {
      handleInvalidGitHubWebHookRequest(req, res);
    }
  });
};
<|endoftext|>