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

var Utils = require('./utils'),
	Strings = Utils.Strings,
	Log = new Utils.Log(),
	Files = require('./files');

var queryMaxAttempts = Math.max(Math.min(+process.env.QUERY_MAX_ATTEMPTS, 10), 2);
var queryDelaySeconds = Math.max(Math.min(+process.env.QUERY_DELAY_SECONDS, 300), 30);

module.exports = function(details, id, forced){
	Log.setID(id);
	Log.info(Strings.WORK_ORDER_STARTED.replace('%s', id));

	if(forced)
		queryCurse(details);
	else {
		var numPolls = 0;
		var interval = setInterval(function(){
			++numPolls;
			if(numPolls > queryMaxAttempts){
				Log.error(Strings.LOOP_EXCEEDED_ATTEMPTS.replace('%s', queryMaxAttempts));
				clearInterval(interval);
			} else {
				Log.info(Strings.LOOP_ATTEMPT.replace('%s', numPolls));
				queryCurse(details, interval);
			}
		}, queryDelaySeconds * 1000);
	}
}

function handleErrors(err, res, body){
	if(err)
		Log.error(Strings.CONNECTION_ERROR.replace('%s', res.request.uri.href));
	else if(res && res.statusCode != 200)
		Log.error(Strings.RESPONSE_INCORRECT.replace('%s', res.request.uri.href).replace('%s', res.statusCode).replace('%s', body || 'null'));
	else
		return true;
}

var curseURL = 'https://wow.curseforge.com';
var curseCDN = 'https://addons.cursecdn.com';

function queryCurse(details, interval){
	request(curseURL + '/addons/' + details.curse + '/files', function(err, res, body){
		if(!handleErrors(err, res))
			return;

		var tagPath = body.match('(/addons/' + details.curse + '/files/.+/)">' + details.tag + '');
		if(!tagPath)
			return Log.error(Strings.CURSE_TAG_NOT_FOUND);

		Log.info(Strings.CURSE_TAG_FOUND);

		request(curseURL + tagPath[1], function(err, res, body){
			if(!handleErrors(err, res))
				return;

			var filePath = body.match('https://.+/media(/files/.+/.+/(.+\-' + details.tag + '.zip))');
			if(!filePath)
				return Log.error(Strings.CURSE_FILE_NOT_FOUND);

			Log.info(Strings.CURSE_FILE_FOUND);

			request(curseCDN + filePath[1]).on('response', function(res){
				if(!handleErrors(null, res))
					return;

				Log.info(Strings.CURSE_FILE_DOWNLOADED);

				if(interval)
					clearInterval(interval);

				queryWowi(details, filePath[2]);
			}).on('error', function(err){
				handleErrors(err);
			}).pipe(fs.createWriteStream(filePath[2]));
		});
	});
}

var wowiAPI = 'https://api.wowinterface.com';

function queryWowi(details, filePath){
	var headers = {'X-API-Token': process.env.WOWI_API_TOKEN}

	request({
		url: wowiAPI + '/addons/details/' + details.wowi + '.json',
		headers: headers,
		json: true
	}, function(err, res, data){
		if(!handleErrors(err, res, data))
			return;

		var currentVersion = data[0].version;
		if(details.tag == currentVersion)
			return Log.info(Strings.ADDON_EXISTS.replace('%s', currentVersion));

		Log.info(Strings.ADDON_DETAILS.replace('%s', currentVersion));

		var postData = {
			url: wowiAPI + '/addons/update',
			headers: headers,
			formData: {
				id: +details.wowi,
				version: details.tag,
				updatefile: fs.createReadStream(filePath)
			}
		}

		if(details.changelog){
			if(!details.changelogPath)
				details.changelogPath = 'CHANGELOG.md';

			Log.info(Strings.CHANGELOG_FETCH.replace('%s', details.changelogPath));

			Files.fetchChangelog(details, function(err, data){
				if(err)
					return Log.error(err);

				Log.info(Strings.CHANGELOG_FETCHED.replace('%s', details.changelogPath));

				postData.formData.changelog = Files.formatChangelog(data);

				updateWowi(details, postData);
			});
		} else
			updateWowi(details, postData);
	});
}

function updateWowi(details, postData){
	Files.getInterfaceVersion(details, function(err, version){
		if(err)
			return Log.error(err);

		postData.formData.compatible = version.charAt(0) + '.' + version.charAt(2);

		Log.info(Strings.ADDON_UPLOADING);

		request.post(postData, function(err, res, body){
			if(!handleErrors(err, res, body))
				return;

			Log.info(Strings.ADDON_UPLOADED.replace('%s', details.path).replace('%s', details.tag));
		});
	});
}
<commit_msg>Any status code in the 200-299 range is positive<commit_after>var fs = require('fs'),
	request = require('request');

var Utils = require('./utils'),
	Strings = Utils.Strings,
	Log = new Utils.Log(),
	Files = require('./files');

var queryMaxAttempts = Math.max(Math.min(+process.env.QUERY_MAX_ATTEMPTS, 10), 2);
var queryDelaySeconds = Math.max(Math.min(+process.env.QUERY_DELAY_SECONDS, 300), 30);

module.exports = function(details, id, forced){
	Log.setID(id);
	Log.info(Strings.WORK_ORDER_STARTED.replace('%s', id));

	if(forced)
		queryCurse(details);
	else {
		var numPolls = 0;
		var interval = setInterval(function(){
			++numPolls;
			if(numPolls > queryMaxAttempts){
				Log.error(Strings.LOOP_EXCEEDED_ATTEMPTS.replace('%s', queryMaxAttempts));
				clearInterval(interval);
			} else {
				Log.info(Strings.LOOP_ATTEMPT.replace('%s', numPolls));
				queryCurse(details, interval);
			}
		}, queryDelaySeconds * 1000);
	}
}

function handleErrors(err, res, body){
	if(err)
		Log.error(Strings.CONNECTION_ERROR.replace('%s', res.request.uri.href));
	else if(res && res.statusCode.toString()[0] != 2)
		Log.error(Strings.RESPONSE_INCORRECT.replace('%s', res.request.uri.href).replace('%s', res.statusCode).replace('%s', body || 'null'));
	else
		return true;
}

var curseURL = 'https://wow.curseforge.com';
var curseCDN = 'https://addons.cursecdn.com';

function queryCurse(details, interval){
	request(curseURL + '/addons/' + details.curse + '/files', function(err, res, body){
		if(!handleErrors(err, res))
			return;

		var tagPath = body.match('(/addons/' + details.curse + '/files/.+/)">' + details.tag + '');
		if(!tagPath)
			return Log.error(Strings.CURSE_TAG_NOT_FOUND);

		Log.info(Strings.CURSE_TAG_FOUND);

		request(curseURL + tagPath[1], function(err, res, body){
			if(!handleErrors(err, res))
				return;

			var filePath = body.match('https://.+/media(/files/.+/.+/(.+\-' + details.tag + '.zip))');
			if(!filePath)
				return Log.error(Strings.CURSE_FILE_NOT_FOUND);

			Log.info(Strings.CURSE_FILE_FOUND);

			request(curseCDN + filePath[1]).on('response', function(res){
				if(!handleErrors(null, res))
					return;

				Log.info(Strings.CURSE_FILE_DOWNLOADED);

				if(interval)
					clearInterval(interval);

				queryWowi(details, filePath[2]);
			}).on('error', function(err){
				handleErrors(err);
			}).pipe(fs.createWriteStream(filePath[2]));
		});
	});
}

var wowiAPI = 'https://api.wowinterface.com';

function queryWowi(details, filePath){
	var headers = {'X-API-Token': process.env.WOWI_API_TOKEN}

	request({
		url: wowiAPI + '/addons/details/' + details.wowi + '.json',
		headers: headers,
		json: true
	}, function(err, res, data){
		if(!handleErrors(err, res, data))
			return;

		var currentVersion = data[0].version;
		if(details.tag == currentVersion)
			return Log.info(Strings.ADDON_EXISTS.replace('%s', currentVersion));

		Log.info(Strings.ADDON_DETAILS.replace('%s', currentVersion));

		var postData = {
			url: wowiAPI + '/addons/update',
			headers: headers,
			formData: {
				id: +details.wowi,
				version: details.tag,
				updatefile: fs.createReadStream(filePath)
			}
		}

		if(details.changelog){
			if(!details.changelogPath)
				details.changelogPath = 'CHANGELOG.md';

			Log.info(Strings.CHANGELOG_FETCH.replace('%s', details.changelogPath));

			Files.fetchChangelog(details, function(err, data){
				if(err)
					return Log.error(err);

				Log.info(Strings.CHANGELOG_FETCHED.replace('%s', details.changelogPath));

				postData.formData.changelog = Files.formatChangelog(data);

				updateWowi(details, postData);
			});
		} else
			updateWowi(details, postData);
	});
}

function updateWowi(details, postData){
	Files.getInterfaceVersion(details, function(err, version){
		if(err)
			return Log.error(err);

		postData.formData.compatible = version.charAt(0) + '.' + version.charAt(2);

		Log.info(Strings.ADDON_UPLOADING);

		request.post(postData, function(err, res, body){
			if(!handleErrors(err, res, body))
				return;

			Log.info(Strings.ADDON_UPLOADED.replace('%s', details.path).replace('%s', details.tag));
		});
	});
}
<|endoftext|>


<commit_before>import compact from 'lodash/compact'
import every from 'lodash/every'
import includes from 'lodash/includes'
import map from 'lodash/map'
import size from 'lodash/size'
import some from 'lodash/some'
import takeWhile from 'lodash/takeWhile'
import toPairs from 'lodash/toPairs'

const SUBSTRING_KEYS = new Set([
	'title',
	'name',
	'description',
	'notes',
	'instructors',
	'times',
	'locations',
])

function checkCourseAgainstQueryBit(course, [key, values]) {
	if (!course.hasOwnProperty(key)) {
		return false
	}

	let substring = SUBSTRING_KEYS.has(key)

	// values is either:
	// - a 1-long array
	// - an $AND, $OR, $NOT, $NOR, or $XOR query
	// - one of the above, but substring

	let hasBool = typeof values[0] === 'string' && values[0].startsWith('$')
	let OR = values[0] === '$OR'
	let NOR = values[0] === '$NOR'
	let AND = values[0] === '$AND'
	let NOT = values[0] === '$NOT'
	let XOR = values[0] === '$XOR'

	if (hasBool) {
		// remove the first value from the array
		// by returning all but the first element
		values = values.slice(1)
	}

	let internalMatches = map(values, val => {
		// dept, gereqs, etc.
		if (Array.isArray(course[key]) && !substring) {
			return includes(course[key], val)
		} else if (Array.isArray(course[key]) && substring) {
			return some(
				map(course[key], item =>
					includes(item.toLowerCase(), val.toLowerCase()),
				),
			)
		} else if (substring) {
			return includes(course[key].toLowerCase(), val.toLowerCase())
		}
		return course[key] === val
	})

	if (!hasBool) {
		return every(internalMatches)
	}

	let result = false

	if (OR) result = some(internalMatches)
	if (NOR) result = !some(internalMatches)
	if (AND) result = every(internalMatches)
	if (NOT) result = !every(internalMatches)
	if (XOR) result = compact(internalMatches).length === 1

	return result
}

// Checks if a course passes a query check.
// query: Object | the query object that comes out of buildQueryFromString
// course: Course | the course to check
// returns: Boolean | did all query bits pass the check?
export function checkCourseAgainstQuery(query, course) {
	let kvPairs = toPairs(query)
	let matches = takeWhile(kvPairs, pair =>
		checkCourseAgainstQueryBit(course, pair),
	)

	return size(kvPairs) === size(matches) && every(matches)
}
<commit_msg>replace _.includes with Array#includes and String#includes<commit_after>import compact from 'lodash/compact'
import every from 'lodash/every'
import map from 'lodash/map'
import size from 'lodash/size'
import some from 'lodash/some'
import takeWhile from 'lodash/takeWhile'
import toPairs from 'lodash/toPairs'

const SUBSTRING_KEYS = new Set([
	'title',
	'name',
	'description',
	'notes',
	'instructors',
	'times',
	'locations',
])

function checkCourseAgainstQueryBit(course, [key, values]) {
	if (!course.hasOwnProperty(key)) {
		return false
	}

	let substring = SUBSTRING_KEYS.has(key)

	// values is either:
	// - a 1-long array
	// - an $AND, $OR, $NOT, $NOR, or $XOR query
	// - one of the above, but substring

	let hasBool = typeof values[0] === 'string' && values[0].startsWith('$')
	let OR = values[0] === '$OR'
	let NOR = values[0] === '$NOR'
	let AND = values[0] === '$AND'
	let NOT = values[0] === '$NOT'
	let XOR = values[0] === '$XOR'

	if (hasBool) {
		// remove the first value from the array
		// by returning all but the first element
		values = values.slice(1)
	}

	let internalMatches = map(values, val => {
		// dept, gereqs, etc.
		if (Array.isArray(course[key]) && !substring) {
			return course[key].includes(val)
		} else if (Array.isArray(course[key]) && substring) {
			return some(
				map(course[key], item =>
					item.toLowerCase().includes(val.toLowerCase()),
				),
			)
		} else if (substring) {
			return course[key].toLowerCase().includes(val.toLowerCase())
		}
		return course[key] === val
	})

	if (!hasBool) {
		return every(internalMatches)
	}

	let result = false

	if (OR) result = some(internalMatches)
	if (NOR) result = !some(internalMatches)
	if (AND) result = every(internalMatches)
	if (NOT) result = !every(internalMatches)
	if (XOR) result = compact(internalMatches).length === 1

	return result
}

// Checks if a course passes a query check.
// query: Object | the query object that comes out of buildQueryFromString
// course: Course | the course to check
// returns: Boolean | did all query bits pass the check?
export function checkCourseAgainstQuery(query, course) {
	let kvPairs = toPairs(query)
	let matches = takeWhile(kvPairs, pair =>
		checkCourseAgainstQueryBit(course, pair),
	)

	return size(kvPairs) === size(matches) && every(matches)
}
<|endoftext|>


<commit_before>/*
Copyright 2016 First People's Cultural Council

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.
*/
import IntlService from "views/services/intl";

export default {
    clean: function (str, mode = 'NXQL') {
        if (mode == 'NXQL') {
            // Escape single quotes and URL decode
            str = decodeURIComponent(str.replace(/'/g, "\\'"));

            // Escape '&' operator
            str = str.replace(/&/g, "%26")
        }

        return str;
    },
    extractErrorMessage: function (jsonError) {
        let errorMessage = jsonError.message;

        if (jsonError.message != null && jsonError.message.indexOf(": ") !== -1) {
            errorMessage = jsonError.message.split(": ")[1];
            errorMessage = IntlService.instance.translate({
                key: 'error',
                default: "Error",
                append: ': '
            }) + IntlService.instance.searchAndReplace(errorMessage);
        }

        return errorMessage;
    },
    getReadableFileSize: function (size) {
        var e = (Math.log(size) / Math.log(1e3)) | 0;
        return +(size / Math.pow(1e3, e)).toFixed(2) + ' ' + ('kMGTPEZY'[e - 1] || '') + 'B';
    },
    toTitleCase: function (string) {
        return string[0].toUpperCase() + string.substring(1);
    },
    randomIntBetween: function (min, max) {
        return Math.floor(Math.random() * (Math.floor(max) - Math.ceil(min))) + Math.ceil(min);
    },
    formatUTCDateString: function (dateString) {
        let dayDesc;
        let d = new Date(dateString);
        // let m = [
        //     intl.trans('jan', 'Jan', 'first'),
        //     intl.trans('feb', 'Feb', 'first'),
        //     intl.trans('mar', 'Mar', 'first'),
        //     intl.trans('apr', 'Apr', 'first'),
        //     intl.trans('may', 'May', 'first'),
        //     intl.trans('jun', 'Jun', 'first'),
        //     intl.trans('jul', 'Jul', 'first'),
        //     intl.trans('aug', 'Aug', 'first'),
        //     intl.trans('sept', 'Sep', 'first'),
        //     intl.trans('oct', 'Oct', 'first'),
        //     intl.trans('nov', 'Nov', 'first'),
        //     intl.trans('dec', 'Dec', 'first')
        // ];
        // @todo translate using the above?
        let m = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'June', 'July', 'Aug', 'Sept', 'Oct', 'Nov', 'Dec'];

        switch (d.getDate().toString()[d.getDate().toString().length - 1]) {
            case '1':
                dayDesc = 'st';
                break;

            case '2':
                dayDesc = 'nd';
                break;

            case '3':
                dayDesc = 'rd';
                break;

            default:
                dayDesc = 'th'
        }
        ;

        return (m[d.getMonth()]) + ' ' + d.getDate() + dayDesc + ', ' + d.getFullYear() + ' @ ' + ("0" + d.getUTCHours()).slice(-2) + ":" + ("0" + d.getUTCMinutes()).slice(-2);
    }
}<commit_msg>TRELLO-223 - Escape colons in search<commit_after>/*
Copyright 2016 First People's Cultural Council

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.
*/
import IntlService from "views/services/intl";

export default {
    clean: function (str, mode = 'NXQL') {
        if (mode == 'NXQL') {
            // Escape single quotes and URL decode
            str = decodeURIComponent(str.replace(/'/g, "\\'"));

            // Escape colon
            str = decodeURIComponent(str.replace(/:/g, "\\:"));

            // Escape '&' operator
            str = str.replace(/&/g, "%26")
        }

        return str;
    },
    extractErrorMessage: function (jsonError) {
        let errorMessage = jsonError.message;

        if (jsonError.message != null && jsonError.message.indexOf(": ") !== -1) {
            errorMessage = jsonError.message.split(": ")[1];
            errorMessage = IntlService.instance.translate({
                key: 'error',
                default: "Error",
                append: ': '
            }) + IntlService.instance.searchAndReplace(errorMessage);
        }

        return errorMessage;
    },
    getReadableFileSize: function (size) {
        var e = (Math.log(size) / Math.log(1e3)) | 0;
        return +(size / Math.pow(1e3, e)).toFixed(2) + ' ' + ('kMGTPEZY'[e - 1] || '') + 'B';
    },
    toTitleCase: function (string) {
        return string[0].toUpperCase() + string.substring(1);
    },
    randomIntBetween: function (min, max) {
        return Math.floor(Math.random() * (Math.floor(max) - Math.ceil(min))) + Math.ceil(min);
    },
    formatUTCDateString: function (dateString) {
        let dayDesc;
        let d = new Date(dateString);
        // let m = [
        //     intl.trans('jan', 'Jan', 'first'),
        //     intl.trans('feb', 'Feb', 'first'),
        //     intl.trans('mar', 'Mar', 'first'),
        //     intl.trans('apr', 'Apr', 'first'),
        //     intl.trans('may', 'May', 'first'),
        //     intl.trans('jun', 'Jun', 'first'),
        //     intl.trans('jul', 'Jul', 'first'),
        //     intl.trans('aug', 'Aug', 'first'),
        //     intl.trans('sept', 'Sep', 'first'),
        //     intl.trans('oct', 'Oct', 'first'),
        //     intl.trans('nov', 'Nov', 'first'),
        //     intl.trans('dec', 'Dec', 'first')
        // ];
        // @todo translate using the above?
        let m = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'June', 'July', 'Aug', 'Sept', 'Oct', 'Nov', 'Dec'];

        switch (d.getDate().toString()[d.getDate().toString().length - 1]) {
            case '1':
                dayDesc = 'st';
                break;

            case '2':
                dayDesc = 'nd';
                break;

            case '3':
                dayDesc = 'rd';
                break;

            default:
                dayDesc = 'th'
        }
        ;

        return (m[d.getMonth()]) + ' ' + d.getDate() + dayDesc + ', ' + d.getFullYear() + ' @ ' + ("0" + d.getUTCHours()).slice(-2) + ":" + ("0" + d.getUTCMinutes()).slice(-2);
    }
}<|endoftext|>


<commit_before>//= require ./attributes
//= require ./model_changes
//= require ./adapter
//= require ./reloader

/**
  Events:
    - record:saved
 */
Data.Model = Ember.Object.extend(Ember.Evented, {

    id: Data.attr('number', { serialize: false }),
    createdAt: Data.attr('datetime', { serialize: false }),
    updatedAt: Data.attr('datetime', { serialize: false }),

    isNew: true,
    isDirty: false,
    isDeleted: false,

    _container: null,
    _attributeChanges: null,
    _relationChanges: null,
    /** Dereferenciated relations cache. Avoid processing time dereferencing (save, ...) */
    _relationsCache: null,

    init: function () {
        this._super();
        this._resetChanges();
        this.resetCaches();
    },

    _url: function () {
        var
            parent = this.get('_parent'),
            parts = [];

        if (parent) {
            parts.pushObject(parent.get('_url'));
        }
        parts.pushObject(this.constructor.resourceUrl());
        if (!this.get('isNew')) {
            parts.pushObject(this.get('id'));
        }
        return parts.join('/');
    }.property('id'),

    _parent: function () {
        var ownerRelation = this.constructor.ownerRelation(Data.STRICT_OWNER);

        if (ownerRelation) {
            return this.get('_relationsCache')[ownerRelation.name] || this.get(ownerRelation.name);
        }
        return null;
    }.property().cacheable(false),

    _updateData: function (data) {
        this.set('_data', data);
        this._resetChanges();
        this._resetDirtyness();
    },

    _resetDirtyness: function () {
        this.setProperties({
            isNew: false,
            isDirty: false
        });
        var parent = this.get('_parent');
        if (parent) {
            parent._computeDirtyness();
        }
    },

    _computeDirtyness: function () {
        var dirty = false;

        this.constructor.eachRelation(function (name, meta) {
            if (!meta.options.owner) {
                var cachedRelation = this.get('_relationsCache')[name];
                if (cachedRelation && cachedRelation.get('isDirty')) {
                    dirty = true;
                }
            }
        }, this);
        this.set('isDirty', dirty);

        var parent = this.get('_parent');
        if (parent) {
            parent._computeDirtyness();
        }
    },

    _resetChanges: function () {
        this.set('_original', Ember.copy(this.get('_data'), true));
        this.set('_attributeChanges', Data.ModelChanges.create());
        this.set('_relationChanges', Data.ModelChanges.create());
    },

    resetCaches: function () {
        this.set('_relationsCache', {});
        this.expireCaches();
    },

    expireCaches: function () {
        this.set('_cacheTimestamp', new Date().getTime());
    },

    _changeAttribute: function (attribute, oldValue, newValue) {
        var
            embeddedContainer = this.get('_embeddedContainer'),
            path = (embeddedContainer ? this.get('_embeddedAttribute') + '.' : '') + attribute,
            changeHolder = embeddedContainer || this,
            attributeChanges = changeHolder.get('_attributeChanges');

        if (this.get('_original.' + path) === newValue) {
            attributeChanges.resetChanges(path);
            //TODO reset dirtyness
        } else {
            if (oldValue !== newValue) {
                attributeChanges.addChange(path, {
                    attribute: path,
                    oldValue: oldValue,
                    newValue: newValue
                });
            }
            changeHolder._dirty();
        }
    },

    _addRelation: function (relation, addedMember) {
        this._changeRelation(relation, 'add', null, addedMember);
    },

    _removeRelation: function (relation, removedMember) {
        this._changeRelation(relation, 'remove', removedMember, null);
    },

    _replaceRelation: function (relation, oldRelated, newRelated) {
        this._changeRelation(relation, 'replace', oldRelated, newRelated);
    },

    _changeRelation: function (relation, action, oldMember, newMember) {
        this.get('_relationChanges').addChange(relation, {
            relation: relation,
            action: action,
            oldValue: oldMember,
            newValue: newMember
        });
        this._dirty();
    },

    _dirty: function () {
        var
            parent = this.get('_parent'),
            embeddedContainer = this.get('_embeddedContainer');

        this.set('isDirty', true);
        if (embeddedContainer) {
            embeddedContainer._dirty();
        }
        if (parent) {
            parent._dirty();
        }
    },

    hasChanges: function () {
        return this.get('_attributeChanges.hasChanges') || this.get('_relationChanges.hasChanges');
    }.property('_attributeChanges.hasChanges', '_relationChanges.hasChanges'),

    deleteRecord: function () {
        var container = this.get('_container');

        if (container) {
            var removed = container.get('_removed');
            removed && removed.pushObject(this);
            container.removeObject(this);
        }

        this.set('isDeleted', true);
        if (!this.get('isNew')) {
            this._dirty();
        } else {
            this._resetDirtyness();
        }
    },

    unload: function () {
        var container = this.get('_container');

        if (container) {
            container.removeObject(this);
        }
    },

    save: function (extraParams) {
        var self = this,
            promise = new Ember.RSVP.Promise(function (resolve, reject) {

            function saveChildren (record, resolve, reject) {
                var
                    relationCaches = record.get('_relationsCache'),
                    savingTracker = Ember.Object.create({
                        relationsToSave: [],

                        save: function (relation) {
                            this.get('relationsToSave').pushObject(relation);
                        },
                        saved: function (relation) {
                            var relationsToSave = this.get('relationsToSave');
                            // relationsToSave.removeObject(relation);
                            relationsToSave.popObject();
                            if (Ember.isEmpty(relationsToSave)) {
                                resolve(record);
                            }
                        }
                    });

                record.constructor.eachRelation(function (relation, meta) {
                    if (!meta.options.owner) {
                        var relationCache = relationCaches[relation];
                        if (relationCache) {
                            savingTracker.save(relationCache);
                            relationCache.save().then(function () {
                                Ember.run(function () {
                                    savingTracker.saved(relationCache)
                                });
                            });
                        }
                    }
                });
                savingTracker.saved(); // in case of no relation to save...
            }

            // Ember.run(function () {
                if (self.get('isNew') || self.get('hasChanges') || self.get('isDeleted')) {
                    self.getAdapter().save(self, extraParams).then(function (record) {
                        Ember.run(function () {
                            saveChildren(record, resolve, reject);
                        });
                    });
                } else {
                    saveChildren(self, resolve, reject);
                }
            // });
        });

        promise.on('promise:resolved', function(event) {
            self.trigger('record:saved', self);
        });
        promise.on('promise:failed', function(event) {
            self.trigger('record:failed', self);
        });
        return promise;
    },

    cancelChanges: function () {
        var relationCaches = this.get('_relationsCache');

        this.constructor.eachRelation(function (relation, meta) {
            if (meta.options.nested) {
                var relationCache = relationCaches[relation];
                if (relationCache) {
                    relationCache.cancelChanges();
                }
            }
        });

        this._updateData(Ember.copy(this.get('_original'), true));
    },

    toJSON: function () {
        var
            json = {},
            processedKeys = [];

        this.constructor.eachAttribute(function (attribute, meta) {
            if (meta.options.serialize !== false) {
                json[meta.codec.key(attribute)] = meta.codec.encode(this, attribute);
            }
            processedKeys.pushObject(meta.codec.key(attribute));
        }, this);

        this.constructor.eachRelation(function (relation, meta) {
            if (meta.options.serialize !== false) {
                json[meta.codec.key(relation)] = meta.codec.encode(this, relation);
            }
            processedKeys.pushObject(meta.codec.key(relation));
        }, this);

        Ember.keys(this._data).removeObjects(processedKeys).forEach(function (dynamicKey) {
            json[dynamicKey] = this._data[dynamicKey];
        }, this);

        return json;
    },

    getAdapter: function () {
        return this.constructor.getAdapter();
    }
});

Data.Model.reopenClass({

    instanciate: function (data, extraData) {
        extraData = extraData || {};
        extraData['isNew'] = true;

        var record = this.createRecord(data, extraData);
        Ember.run.next(record, function () {
            this._dirty();
        });
        return record;
    },

    load: function (data, extraData) {
        var
            useCache = extraData ? !extraData._embeddedContainer : true,
            cachedRecord = useCache ? this.cachedRecord(data.id) : null,
            record,
            reloader;

        if (cachedRecord) {
            cachedRecord._updateData(data);
            record = cachedRecord;
        } else {
            extraData = extraData || {};
            extraData['isNew'] = false;
            record = this.createRecord(data, extraData);
        }
        record.resetCaches();

        reloader = record.get('_reloader');
        if (reloader) {
            reloader.checkTriggers();
        }

        return record;
    },

    loadMany: function (data, extraData) {
        data = data || [];
        return data.map(function (itemData) {
            return this.load(itemData, extraData);
        }, this);
    },

    createRecord: function (data, extraData) {
        data = data || {};
        extraData = extraData || {};
        var
            useCache = !extraData._embeddedContainer,
            record = this.create({
                _data: data,
                _createdAt: new Date(),
            });

        record.setProperties(extraData || {});
        if (this.hasReloading()) {
            record.set('_reloader', Data.Reloader.create({
                record: record
            }));
        }
        if (useCache) {
            this.cacheRecord(data.id, record);
        }
        return record;
    },

    hasReloading: function () {
        var hasReload = false;

        this.eachAttribute(function (name, meta) {
            if (meta.options.reload) {
                hasReload = true;
            }
        });
        return hasReload;
    },

    /**
     * Iterate through model relations, invoking callback with relation's name & meta.
     */
    eachAttribute: function (callback, binding) {
        this.eachComputedProperty(function (name, meta) {
            if (meta.isAttribute) {
                callback.call(binding || this, name, meta);
            }
        }, this);
    },

    /**
     * Iterate through model relations, invoking callback with relation's name & meta.
     */
    eachRelation: function (callback, binding) {
        this.eachComputedProperty(function (name, meta) {
            if (meta.isRelation) {
                callback.call(binding || this, name, meta);
            }
        }, this);
    },

    sideLoad: function (data) {
        var
            orderedKeys = [],
            types = {};

        orderedKeys.addKey = function (key, type) {
            key = key.decamelize();
            if (!this.contains(key)) {
                types[key] = type;
                this.pushObject(key);
            }
        };

        this.eachRelation(function (relationName, meta) {
            var dependsOn = meta.options.dependsOn || [];

            dependsOn.forEach(function (key) {
                var
                    typeName = key.singularize().camelize().classify(),
                    namespace = this._splittedSymbols()[1];

                orderedKeys.addKey(key, '%@.%@'.fmt(namespace, typeName));
            }, this);
            orderedKeys.addKey(relationName, meta.type);
        });

        orderedKeys.forEach(function (key) {
            var
                sideLoad = data[key],
                type;

            if (sideLoad) {
                type = types[key];
                Ember.Logger.debug('DATA - Sideload', sideLoad.length, type, "instances")
                sideLoad.forEach(function (sideItemData) {
                    Data.getType(type).load(sideItemData);
                });
            }
        });
    },

    find: function (id, parent, options) {
        var adapter = this.getAdapter();

        switch (Ember.typeOf(id)) {
        case 'string':
        case 'number':
            return adapter.findOne(this, id, parent, options);
        case 'array':
            return adapter.findMany(this, id, parent, options);
        case 'object':
            return adapter.findMany(this, [], parent, { params: id });
        default:
            return adapter.findMany(this, [], parent, options);
        }
    },

    cacheRecord: function (id, value) {
        this._cache = this._cache || {};
        this._cache[id] = value;
    },
    cachedRecord: function (id) {
        if (this._cache) {
            return this._cache[id];
        }
    },

    resourceKey: function () {
        var typeName = this._splittedSymbols()[2];

        return typeName.decamelize();
    },

    resourceUrl: function () {
        return this.resourceKey().pluralize();
    },

    ownerRelation: function (checking) {
        var ownerRelation;

        this.eachRelation(function (relation, meta) {
            if (meta.options.owner && !((checking === Data.STRICT_OWNER) && (meta.options.follow === false))) {
                ownerRelation = {
                    name: relation,
                    meta: meta
                };
            }
        }, this);
        return ownerRelation;
    },

    getAdapter: function () {
        var namespace = Ember.get(this._splittedSymbols()[1]);

        return namespace.adapter
            || namespace.__container__.lookup('adapter:default'); //TODO improve injection management...
    },

    _splittedSymbols: function () {
        return /(.+)\.(\w+)/.exec(this.toString());
    }
});
<commit_msg>Refactor model's class info retrieval<commit_after>//= require ./attributes
//= require ./model_changes
//= require ./adapter
//= require ./reloader

/**
  Events:
    - record:saved
 */
Data.Model = Ember.Object.extend(Ember.Evented, {

    id: Data.attr('number', { serialize: false }),
    createdAt: Data.attr('datetime', { serialize: false }),
    updatedAt: Data.attr('datetime', { serialize: false }),

    isNew: true,
    isDirty: false,
    isDeleted: false,

    _container: null,
    _attributeChanges: null,
    _relationChanges: null,
    /** Dereferenciated relations cache. Avoid processing time dereferencing (save, ...) */
    _relationsCache: null,

    init: function () {
        this._super();
        this._resetChanges();
        this.resetCaches();
    },

    _url: function () {
        var parent = this.get('_parent'),
            parts = [];

        if (parent) {
            parts.pushObject(parent.get('_url'));
        }
        parts.pushObject(this.constructor.resourceUrl());
        if (!this.get('isNew')) {
            parts.pushObject(this.get('id'));
        }
        return parts.join('/');
    }.property('id'),

    _parent: function () {
        var ownerRelation = this.constructor.ownerRelation(Data.STRICT_OWNER);

        if (ownerRelation) {
            return this.get('_relationsCache')[ownerRelation.name] || this.get(ownerRelation.name);
        }
        return null;
    }.property().cacheable(false),

    _updateData: function (data) {
        this.set('_data', data);
        this._resetChanges();
        this._resetDirtyness();
    },

    _resetDirtyness: function () {
        this.setProperties({
            isNew: false,
            isDirty: false
        });
        var parent = this.get('_parent');
        if (parent) {
            parent._computeDirtyness();
        }
    },

    _computeDirtyness: function () {
        var dirty = false;

        this.constructor.eachRelation(function (name, meta) {
            if (!meta.options.owner) {
                var cachedRelation = this.get('_relationsCache')[name];
                if (cachedRelation && cachedRelation.get('isDirty')) {
                    dirty = true;
                }
            }
        }, this);
        this.set('isDirty', dirty);

        var parent = this.get('_parent');
        if (parent) {
            parent._computeDirtyness();
        }
    },

    _resetChanges: function () {
        this.set('_original', Ember.copy(this.get('_data'), true));
        this.set('_attributeChanges', Data.ModelChanges.create());
        this.set('_relationChanges', Data.ModelChanges.create());
    },

    resetCaches: function () {
        this.set('_relationsCache', {});
        this.expireCaches();
    },

    expireCaches: function () {
        this.set('_cacheTimestamp', new Date().getTime());
    },

    _changeAttribute: function (attribute, oldValue, newValue) {
        var embeddedContainer = this.get('_embeddedContainer'),
            path = (embeddedContainer ? this.get('_embeddedAttribute') + '.' : '') + attribute,
            changeHolder = embeddedContainer || this,
            attributeChanges = changeHolder.get('_attributeChanges');

        if (this.get('_original.' + path) === newValue) {
            attributeChanges.resetChanges(path);
            //TODO reset dirtyness
        } else {
            if (oldValue !== newValue) {
                attributeChanges.addChange(path, {
                    attribute: path,
                    oldValue: oldValue,
                    newValue: newValue
                });
            }
            changeHolder._dirty();
        }
    },

    _addRelation: function (relation, addedMember) {
        this._changeRelation(relation, 'add', null, addedMember);
    },

    _removeRelation: function (relation, removedMember) {
        this._changeRelation(relation, 'remove', removedMember, null);
    },

    _replaceRelation: function (relation, oldRelated, newRelated) {
        this._changeRelation(relation, 'replace', oldRelated, newRelated);
    },

    _changeRelation: function (relation, action, oldMember, newMember) {
        this.get('_relationChanges').addChange(relation, {
            relation: relation,
            action: action,
            oldValue: oldMember,
            newValue: newMember
        });
        this._dirty();
    },

    _dirty: function () {
        var parent = this.get('_parent'),
            embeddedContainer = this.get('_embeddedContainer');

        this.set('isDirty', true);
        if (embeddedContainer) {
            embeddedContainer._dirty();
        }
        if (parent) {
            parent._dirty();
        }
    },

    hasChanges: function () {
        return this.get('_attributeChanges.hasChanges') || this.get('_relationChanges.hasChanges');
    }.property('_attributeChanges.hasChanges', '_relationChanges.hasChanges'),

    deleteRecord: function () {
        var container = this.get('_container');

        if (container) {
            var removed = container.get('_removed');
            removed && removed.pushObject(this);
            container.removeObject(this);
        }

        this.set('isDeleted', true);
        if (!this.get('isNew')) {
            this._dirty();
        } else {
            this._resetDirtyness();
        }
    },

    unload: function () {
        var container = this.get('_container');

        if (container) {
            container.removeObject(this);
        }
    },

    save: function (extraParams) {
        var self = this,
            promise = new Ember.RSVP.Promise(function (resolve, reject) {

            function saveChildren (record, resolve, reject) {
                var relationCaches = record.get('_relationsCache'),
                    savingTracker = Ember.Object.create({
                        relationsToSave: [],

                        save: function (relation) {
                            this.get('relationsToSave').pushObject(relation);
                        },
                        saved: function (relation) {
                            var relationsToSave = this.get('relationsToSave');
                            // relationsToSave.removeObject(relation);
                            relationsToSave.popObject();
                            if (Ember.isEmpty(relationsToSave)) {
                                resolve(record);
                            }
                        }
                    });

                record.constructor.eachRelation(function (relation, meta) {
                    if (!meta.options.owner) {
                        var relationCache = relationCaches[relation];
                        if (relationCache) {
                            savingTracker.save(relationCache);
                            relationCache.save().then(function () {
                                Ember.run(function () {
                                    savingTracker.saved(relationCache)
                                });
                            });
                        }
                    }
                });
                savingTracker.saved(); // in case of no relation to save...
            }

            // Ember.run(function () {
                if (self.get('isNew') || self.get('hasChanges') || self.get('isDeleted')) {
                    self.getAdapter().save(self, extraParams).then(function (record) {
                        Ember.run(function () {
                            saveChildren(record, resolve, reject);
                        });
                    });
                } else {
                    saveChildren(self, resolve, reject);
                }
            // });
        });

        promise.on('promise:resolved', function(event) {
            self.trigger('record:saved', self);
        });
        promise.on('promise:failed', function(event) {
            self.trigger('record:failed', self);
        });
        return promise;
    },

    cancelChanges: function () {
        var relationCaches = this.get('_relationsCache');

        this.constructor.eachRelation(function (relation, meta) {
            if (meta.options.nested) {
                var relationCache = relationCaches[relation];
                if (relationCache) {
                    relationCache.cancelChanges();
                }
            }
        });

        this._updateData(Ember.copy(this.get('_original'), true));
    },

    toJSON: function () {
        var json = {},
            processedKeys = [];

        this.constructor.eachAttribute(function (attribute, meta) {
            if (meta.options.serialize !== false) {
                json[meta.codec.key(attribute)] = meta.codec.encode(this, attribute);
            }
            processedKeys.pushObject(meta.codec.key(attribute));
        }, this);

        this.constructor.eachRelation(function (relation, meta) {
            if (meta.options.serialize !== false) {
                json[meta.codec.key(relation)] = meta.codec.encode(this, relation);
            }
            processedKeys.pushObject(meta.codec.key(relation));
        }, this);

        Ember.keys(this._data).removeObjects(processedKeys).forEach(function (dynamicKey) {
            json[dynamicKey] = this._data[dynamicKey];
        }, this);

        return json;
    },

    getAdapter: function () {
        return this.constructor.getAdapter();
    }
});

Data.Model.reopenClass({

    instanciate: function (data, extraData) {
        extraData = extraData || {};
        extraData['isNew'] = true;

        var record = this.createRecord(data, extraData);
        Ember.run.next(record, function () {
            this._dirty();
        });
        return record;
    },

    load: function (data, extraData) {
        var useCache = extraData ? !extraData._embeddedContainer : true,
            cachedRecord = useCache ? this.cachedRecord(data.id) : null,
            record,
            reloader;

        if (cachedRecord) {
            cachedRecord._updateData(data);
            record = cachedRecord;
        } else {
            extraData = extraData || {};
            extraData['isNew'] = false;
            record = this.createRecord(data, extraData);
        }
        record.resetCaches();

        reloader = record.get('_reloader');
        if (reloader) {
            reloader.checkTriggers();
        }

        return record;
    },

    loadMany: function (data, extraData) {
        data = data || [];
        return data.map(function (itemData) {
            return this.load(itemData, extraData);
        }, this);
    },

    createRecord: function (data, extraData) {
        data = data || {};
        extraData = extraData || {};
        var useCache = !extraData._embeddedContainer,
            record = this.create({
                _data: data,
                _createdAt: new Date(),
            });

        record.setProperties(extraData || {});
        if (this.hasReloading()) {
            record.set('_reloader', Data.Reloader.create({
                record: record
            }));
        }
        if (useCache) {
            this.cacheRecord(data.id, record);
        }
        return record;
    },

    hasReloading: function () {
        var hasReload = false;

        this.eachAttribute(function (name, meta) {
            if (meta.options.reload) {
                hasReload = true;
            }
        });
        return hasReload;
    },

    /**
     * Iterate through model relations, invoking callback with relation's name & meta.
     */
    eachAttribute: function (callback, binding) {
        this.eachComputedProperty(function (name, meta) {
            if (meta.isAttribute) {
                callback.call(binding || this, name, meta);
            }
        }, this);
    },

    /**
     * Iterate through model relations, invoking callback with relation's name & meta.
     */
    eachRelation: function (callback, binding) {
        this.eachComputedProperty(function (name, meta) {
            if (meta.isRelation) {
                callback.call(binding || this, name, meta);
            }
        }, this);
    },

    sideLoad: function (data) {
        var orderedKeys = [],
            types = {};

        orderedKeys.addKey = function (key, type) {
            key = key.decamelize();
            if (!this.contains(key)) {
                types[key] = type;
                this.pushObject(key);
            }
        };

        this.eachRelation(function (relationName, meta) {
            var dependsOn = meta.options.dependsOn || [];

            dependsOn.forEach(function (key) {
                var typeName = key.singularize().camelize().classify();

                orderedKeys.addKey(key, '%@.%@'.fmt(this._classInfo().namespace, typeName));
            }, this);
            orderedKeys.addKey(relationName, meta.type);
        });

        orderedKeys.forEach(function (key) {
            var sideLoad = data[key],
                type;

            if (sideLoad) {
                type = types[key];
                Ember.Logger.debug('DATA - Sideload', sideLoad.length, type, "instances")
                sideLoad.forEach(function (sideItemData) {
                    Data.getType(type).load(sideItemData);
                });
            }
        });
    },

    find: function (id, parent, options) {
        var adapter = this.getAdapter();

        switch (Ember.typeOf(id)) {
        case 'string':
        case 'number':
            return adapter.findOne(this, id, parent, options);
        case 'array':
            return adapter.findMany(this, id, parent, options);
        case 'object':
            return adapter.findMany(this, [], parent, { params: id });
        default:
            return adapter.findMany(this, [], parent, options);
        }
    },

    cacheRecord: function (id, value) {
        this._cache = this._cache || {};
        this._cache[id] = value;
    },
    cachedRecord: function (id) {
        if (this._cache) {
            return this._cache[id];
        }
    },

    resourceKey: function () {
        return this._classInfo().className.decamelize();
    },

    resourceUrl: function () {
        return this.resourceKey().pluralize();
    },

    ownerRelation: function (checking) {
        var ownerRelation;

        this.eachRelation(function (relation, meta) {
            if (meta.options.owner && !((checking === Data.STRICT_OWNER) && (meta.options.follow === false))) {
                ownerRelation = {
                    name: relation,
                    meta: meta
                };
            }
        }, this);
        return ownerRelation;
    },

    getAdapter: function () {
        var namespace = Ember.get(this._classInfo().namespace);

        return namespace.adapter
            || namespace.__container__.lookup('adapter:default'); //TODO improve injection management...
    },

    _classInfo: function () {
        var info = /(.+)\.(\w+)/.exec(this.toString());
        return {
            namespace: info[1],
            className: info[2]
        };
    }
});
<|endoftext|>


<commit_before>"use strict";

export const BUTTONS = {
  one: 1
};

export class Tool {
  setCanvas(canvas) {
    this.canvas = canvas;
    this.state = canvas.state;
    this.primary = canvas.primary;
    this.draw = canvas.draw;
    this.loc = this.state.location;
  }

  onPenDown(state, loc) {
    console.log("pen down");
  }

  onPenUp(state, loc) {
    this.saveDraw(state);
    this.clearLocation(state);
  }

  onPenMove(state, loc) {
    console.log("pen down");
  }

  onPenCancel(state) {
    this.clearDraw(state);
    this.clearLocation(state);
  }

  prepare(state) {
    let ctx = this.draw.getContext(),
        palette = state.palette;
    ctx.beginPath();
    ctx.strokeStyle = palette.strokeStyle;
    ctx.lineWidth = palette.lineWidth;
    return ctx;
  }

  clearLocation(state) {
    let loc = state.location,
        curr = loc.current,
        prev = loc.previous,
        orig = loc.original;
    orig.x = null;
    orig.y = null;
    prev.x = null;
    prev.y = null;
    curr.x = null;
    curr.y = null;
  }

  /**
   * Save drawing layer and clear it
   */
  saveDraw(state) {
    let draw = this.draw,
        primary = this.primary,
        c = draw.getCanvas();

    primary.getContext().drawImage(c, 0, 0);
    draw.getContext().clearRect(0, 0, c.width, c.height);
    console.log("saved");
  }

  cleardraw(state) {
    let draw = this.draw,
        c = draw.getCanvas();

    draw.getContext().clearRect(0, 0, c.width, c.height);
    console.log("cleared");
  }

  /**
   * Clears drawing context optimally
   */
  clearContext(ctx, orig, prev) {
    let clearX = prev.x < orig.x ? prev.x : orig.x,
        clearY = prev.y < orig.y ? prev.y : orig.y;
    ctx.clearRect(
      clearX - 1,
      clearY - 1,
      Math.abs(prev.x - orig.x) + 2,
      Math.abs(prev.y - orig.y) + 2);
  }
}
<commit_msg>Reduce noise<commit_after>"use strict";

export const BUTTONS = {
  one: 1
};

export class Tool {
  setCanvas(canvas) {
    this.canvas = canvas;
    this.state = canvas.state;
    this.primary = canvas.primary;
    this.draw = canvas.draw;
    this.loc = this.state.location;
  }

  onPenDown(state, loc) {
  }

  onPenUp(state, loc) {
    this.saveDraw(state);
    this.clearLocation(state);
  }

  onPenMove(state, loc) {
  }

  onPenCancel(state) {
    this.clearDraw(state);
    this.clearLocation(state);
  }

  prepare(state) {
    let ctx = this.draw.getContext(),
        palette = state.palette;
    ctx.beginPath();
    ctx.strokeStyle = palette.strokeStyle;
    ctx.lineWidth = palette.lineWidth;
    return ctx;
  }

  clearLocation(state) {
    let loc = state.location,
        curr = loc.current,
        prev = loc.previous,
        orig = loc.original;
    orig.x = null;
    orig.y = null;
    prev.x = null;
    prev.y = null;
    curr.x = null;
    curr.y = null;
  }

  /**
   * Save drawing layer and clear it
   */
  saveDraw(state) {
    let draw = this.draw,
        primary = this.primary,
        c = draw.getCanvas();

    primary.getContext().drawImage(c, 0, 0);
    draw.getContext().clearRect(0, 0, c.width, c.height);
  }

  cleardraw(state) {
    let draw = this.draw,
        c = draw.getCanvas();

    draw.getContext().clearRect(0, 0, c.width, c.height);
  }

  /**
   * Clears drawing context optimally
   */
  clearContext(ctx, orig, prev) {
    let clearX = prev.x < orig.x ? prev.x : orig.x,
        clearY = prev.y < orig.y ? prev.y : orig.y;
    ctx.clearRect(
      clearX - 1,
      clearY - 1,
      Math.abs(prev.x - orig.x) + 2,
      Math.abs(prev.y - orig.y) + 2);
  }
}
<|endoftext|>


<commit_before>//
// Copyright (c) 2013-2016 SKYARCH NETWORKS INC.
//
// This software is released under the MIT License.
//
// http://opensource.org/licenses/mit-license.php
//

module.exports = function(data){
  return {
    style: 'tableExample',
    color: '#444',
    table: {
        headerRows: 2,
        dontBreakRows: true,
        widths: [ 120, 90, 'auto', 'auto', 'auto', 100,  'auto', 'auto', 'auto', 100],
        body: buildTableBody(data)
    },
  };

};

function eval_port(from_port, to_port){
  if(from_port && to_port){
    return from_port === to_port ? from_port.toString(): from_port.toString()+"-"+to_port.toString();
  }
  else{
    return 'All';
  }
}

function eval_protocol(ip_protocol){
  return ip_protocol === '-1' ? 'All' : ip_protocol;
}

function buildTableBody(data) {
    var body = [];

    body.push([{ rowSpan: 2, text: t('security_groups.description'),  style: 'tableHeader' },
            { rowSpan: 2, text: t('security_groups.group_id'),style: 'tableHeader' },
            { colSpan: 4, text: t('security_groups.inbound'), style: 'tableHeader' },
            'x','x','xx',
            { colSpan: 4, text:  t('security_groups.outbound'), style: 'tableHeader' },
            'x', 'x', 'x',], ['x' , 'x',
          {text: t('security_groups.type'), style: 'tableHeader' },
          {text: t('security_groups.protocol'), style: 'tableHeader' },
          {text: t('security_groups.port_range'),  style: 'tableHeader' },
          {text: t('security_groups.source'),  style: 'tableHeader' },
          {text: t('security_groups.type'),  style: 'tableHeader' },
          {text: t('security_groups.protocol'), style: 'tableHeader' },
          {text: t('security_groups.port_range'), style: 'tableHeader' },
          {text: t('security_groups.source'),  style: 'tableHeader' },
      ]);

    data.forEach(function(v,index) {
      var inbound =  v.ip_permissions;
      var outbound = v.ip_permissions_egress;
      body.push([{text: v.description, style: 'tableHeader', rowSpan: inbound.length},
               {text: v.group_id, style: 'tableHeader', rowSpan: inbound.length},
               inbound[0].user_id_group_pairs,
               eval_protocol(inbound[0].ip_protocol),
               eval_port(inbound[0].from_port, inbound[0].to_port),
               inbound[0].ip_ranges[0].cidr_ip,
               outbound[0].user_id_group_pairs,
               eval_protocol(outbound[0].ip_protocol),
               eval_port(outbound[0].from_port, outbound[0].from_port),
               outbound[0].ip_ranges[0].cidr_ip
      ]);

      inbound.shift(); // Remove first index
      outbound.shift();  // Remove first index
      extract_next(inbound, outbound, body);

  });
  return body;
}

function extract_next(inbound, outbound, body){
  if(inbound.length > outbound.length){
    inbound.forEach(function(v,index){
        if(outbound.length > index){
          body.push(['','',
                v.user_id_group_pairs,
                eval_protocol(v.ip_protocol),
                eval_port(v.from_port, v.to_port),
                v.ip_ranges[0].cidr_ip,
                outbound[index].user_id_group_pairs,
                eval_protocol(outbound[index].ip_protocol),
                eval_port(outbound[index].from_port, outbound[index].to_port),
                outbound[index].ip_ranges[0].cidr_ip
              ]);
        }else{
          body.push(['','',
                v.user_id_group_pairs,
                eval_protocol(v.ip_protocol),
                eval_port(v.from_port, v.tp_port),
                v.ip_ranges[0].cidr_ip,
                '-',
                '-',
                '-',
                '-'
              ]);
        }

    });
  }else{
    outbound.forEach(function(v,index){
        if(inboound.length > index){
          body.push(['','',
                v.user_id_group_pairs,
                eval_protocol(v.ip_protocol),
                eval_port(v.from_port.toString()),
                v.ip_ranges[0].cidr_ip,
                inboound[index].user_id_group_pairs,
                eval_protocol(inboound[index].ip_protocol),
                eval_port(inboound[index].from_port,inboound[index].to_port),
                inboound[index].ip_ranges[0].cidr_ip
              ]);
        }else{
          body.push(['','',
                v.user_id_group_pairs,
                eval_protocol(v.ip_protocol),
                eval_port(v.from_port,v.to_port),
                v.ip_ranges[0].cidr_ip,
                '-',
                '-',
                '-',
                '-'
              ]);
        }

    });
  }

}
<commit_msg>Added push initializer for inbound and outbound rules for setting the table<commit_after>//
// Copyright (c) 2013-2016 SKYARCH NETWORKS INC.
//
// This software is released under the MIT License.
//
// http://opensource.org/licenses/mit-license.php
//

module.exports = function(data){
  return {
    style: 'tableExample',
    color: '#444',
    table: {
        headerRows: 2,
        dontBreakRows: true,
        widths: [ 120, 90, 'auto', 'auto', 'auto', 100,  'auto', 'auto', 'auto', 100],
        body: buildTableBody(data)
    },
  };

};

function eval_port(from_port, to_port){
  if(from_port && to_port){
    return from_port === to_port ? from_port.toString(): from_port.toString()+"-"+to_port.toString();
  }
  else{
    return 'All';
  }
}

function eval_protocol(ip_protocol){
  return ip_protocol === '-1' ? 'All' : ip_protocol;
}

function buildTableBody(data) {
    var body = [];

    body.push([{ rowSpan: 2, text: t('security_groups.description'),  style: 'tableHeader' },
            { rowSpan: 2, text: t('security_groups.group_id'),style: 'tableHeader' },
            { colSpan: 4, text: t('security_groups.inbound'), style: 'tableHeader' },
            'x','x','xx',
            { colSpan: 4, text:  t('security_groups.outbound'), style: 'tableHeader' },
            'x', 'x', 'x',], ['x' , 'x',
          {text: t('security_groups.type'), style: 'tableHeader' },
          {text: t('security_groups.protocol'), style: 'tableHeader' },
          {text: t('security_groups.port_range'),  style: 'tableHeader' },
          {text: t('security_groups.source'),  style: 'tableHeader' },
          {text: t('security_groups.type'),  style: 'tableHeader' },
          {text: t('security_groups.protocol'), style: 'tableHeader' },
          {text: t('security_groups.port_range'), style: 'tableHeader' },
          {text: t('security_groups.source'),  style: 'tableHeader' },
      ]);

    data.forEach(function(v,index) {
      var inbound =  v.ip_permissions;
      var outbound = v.ip_permissions_egress;

      push_initial(v,inbound, outbound, body);
      inbound.shift(); // Remove first index
      outbound.shift();  // Remove first index
      extract_next(inbound, outbound, body);

  });
  return body;
}

function push_initial(v, inbound, outbound, body){
  //Check if inbound lenght is greater than outbound lenght
  if(inbound.length > outbound.length){
    // If outbound has contents
    if(outbound.length > 0){
      body.push([{text: v.description, style: 'tableHeader', rowSpan: inbound.length},
               {text: v.group_id, style: 'tableHeader', rowSpan: inbound.length},
               inbound[0].user_id_group_pairs,
               eval_protocol(inbound[0].ip_protocol),
               eval_port(inbound[0].from_port, inbound[0].to_port),
               inbound[0].ip_ranges[0].cidr_ip,
               outbound[0].user_id_group_pairs,
               eval_protocol(outbound[0].ip_protocol),
               eval_port(outbound[0].from_port, outbound[0].from_port),
               outbound[0].ip_ranges[0].cidr_ip
      ]);
      // Insert (-) if outbound doesn't exists
    }else{
        body.push([{text: v.description, style: 'tableHeader', rowSpan: inbound.length},
                 {text: v.group_id, style: 'tableHeader', rowSpan: inbound.length},
                 inbound[0].user_id_group_pairs,
                 eval_protocol(inbound[0].ip_protocol),
                 eval_port(inbound[0].from_port, inbound[0].to_port),
                 inbound[0].ip_ranges[0].cidr_ip,
                 '-',  '-',   '-', '-'
        ]);
      }
      // IF outbound length is greated than inbound
    }else if (inbound.length < outbound.length) {
      // Check if inbound has contents
      if(inbound.length > 0){
        body.push([{text: v.description, style: 'tableHeader', rowSpan: outbound.length},
                 {text: v.group_id, style: 'tableHeader', rowSpan: outbound.length},
                 inbound[0].user_id_group_pairs,
                 eval_protocol(inbound[0].ip_protocol),
                 eval_port(inbound[0].from_port, inbound[0].to_port),
                 inbound[0].ip_ranges[0].cidr_ip,
                 outbound[0].user_id_group_pairs,
                 eval_protocol(outbound[0].ip_protocol),
                 eval_port(outbound[0].from_port, outbound[0].from_port),
                 outbound[0].ip_ranges[0].cidr_ip
        ]);
      }else {
        body.push([{text: v.description, style: 'tableHeader', rowSpan: outbound.length},
                 {text: v.group_id, style: 'tableHeader', rowSpan: outbound.length},
                 '-', '-', '-', '-',
                 outbound[0].user_id_group_pairs,
                 eval_protocol(outbound[0].ip_protocol),
                 eval_port(outbound[0].from_port, outbound[0].from_port),
                 outbound[0].ip_ranges[0].cidr_ip
        ]);
      }
      //Push blank
    }else {
      body.push([{text: v.description, style: 'tableHeader', rowSpan: outbound.length},
               {text: v.group_id, style: 'tableHeader', rowSpan: outbound.length},
               '-', '-', '-', '-','-','-','-','-'
      ]);
    }
}

function extract_next(inbound, outbound, body){
  if(inbound.length > outbound.length){
    inbound.forEach(function(v,index){
        if(outbound.length > index){
          body.push(['','',
                v.user_id_group_pairs,
                eval_protocol(v.ip_protocol),
                eval_port(v.from_port, v.to_port),
                v.ip_ranges[0].cidr_ip,
                outbound[index].user_id_group_pairs,
                eval_protocol(outbound[index].ip_protocol),
                eval_port(outbound[index].from_port, outbound[index].to_port),
                outbound[index].ip_ranges[0].cidr_ip
              ]);
        }else{
          body.push(['','',
                v.user_id_group_pairs,
                eval_protocol(v.ip_protocol),
                eval_port(v.from_port, v.tp_port),
                v.ip_ranges[0].cidr_ip,
                '-',
                '-',
                '-',
                '-'
              ]);
        }

    });
  }else{
    outbound.forEach(function(v,index){
        if(inboound.length > index){
          body.push(['','',
                v.user_id_group_pairs,
                eval_protocol(v.ip_protocol),
                eval_port(v.from_port.toString()),
                v.ip_ranges[0].cidr_ip,
                inboound[index].user_id_group_pairs,
                eval_protocol(inboound[index].ip_protocol),
                eval_port(inboound[index].from_port,inboound[index].to_port),
                inboound[index].ip_ranges[0].cidr_ip
              ]);
        }else{
          body.push(['','',
                v.user_id_group_pairs,
                eval_protocol(v.ip_protocol),
                eval_port(v.from_port,v.to_port),
                v.ip_ranges[0].cidr_ip,
                '-',
                '-',
                '-',
                '-'
              ]);
        }

    });
  }

}
<|endoftext|>


<commit_before>import { returnTime, updateCurrentPhraseScroll } from 'js/utils';

/**
 * KeyboardController
 * @export
 * @class KeyboardController
 */
export default class KeyboardController {
  /**
   * Creates an instance of KeyboardController.
   *
   * @param {any} $scope
   * @param {any} $global
   * @param {any} $timeout
   * @param {TTSManager} TTSManager
   *
   * @memberOf RecentController
   */
  constructor($scope, $global, $timeout, EventManager, TTSManager) {
    this.$scope = $scope;
    this.$scope.global = $global;
    this.$timeout = $timeout;
    this.tts = TTSManager;
    this.event = EventManager;
    this.fastTypeTimer = null;
    this.suggestions = false;
    // Initilize variables for controller.

    // Call controllerInit
    this.controllerInit();
  }

  /**
   * Create or facilitate new functions for $scope or $global service.
   */
  controllerInit() {
    this.phraseIndex = 0;
    this.suggestionList = [];
  }

  /**
   * Shows the keyboard
   */
  showKeyboard() {
    let typeInput = document.getElementById("typeInput");
    if (typeInput) {
      typeInput.focus();
      document.body.scrollTop = document.documentElement.scrollTop = 0;
    }
    this.showKeyboardFocused();
  }

  /**
   * Removes readonly from typeInput
   * Triggered when type input is
   * Walkaround for ios autocorrect
   */
  showKeyboardFocused() {
    let typeInput = document.getElementById("typeInput");
    if (typeInput) {
      document.body.style.height = (parseInt(window.innerHeight) / 2) + 'px';
      document.body.scrollTop = document.documentElement.scrollTop = 0;
      this.$timeout(() => {
        document.body.style.height = parseInt(window.innerHeight) + 'px';
        document.body.scrollTop = document.documentElement.scrollTop = 0;
        console.log("virtual keyboard opened!");
      }, 300);
    }
  }

  /**
   * Hides the keyboard
   */
  hideKeyboard() {
    let typeInput = document.getElementById("typeInput");
    if (typeInput) {
      document.body.style.height = "100%";
    }
  }

  /**
   * Submits (handles) the current typeInput string content.
   */
  submitCurrentInput() {
    let typeInput = document.getElementById("typeInput");
    if (typeInput.value) {
      let inputWord = {};
      inputWord.title = typeInput.value.toLocaleLowerCase();
      inputWord.slug = typeInput.value.toLocaleLowerCase().replace(" ", "-");
      let checkExist = this.checkWordInDB(inputWord.slug);
      inputWord.slugExist = !checkExist;

      if (inputWord.title.contains(" ") && !checkExist) {
        this.recognizeWord(inputWord.title);
      } else {
        inputWord.phraseIndex = this.phraseIndex++;
        this.$scope.global.pushToCurrentPhrase(inputWord);
      }
      updateCurrentPhraseScroll();
      this.tts.speak(inputWord.title);
      let gs = this.$scope.global.gridSize;
      this.event.appWord(inputWord.title, gs[0], gs[1]);
      typeInput.value = "";
    }
  }

  /**
   * Handles the submission of the input via "enter" from the device
   */
  enterSubmit($event) {
    if ($event.keyCode === 13) {
      this.submitCurrentInput();
      this.suggestionList = [];
    } else {
      let typeInput = document.getElementById("typeInput");
      if (typeInput) {
        // If there is a timeout, clear it.
        if (this.fastTypeTimer) {
          this.$timeout.cancel(this.fastTypeTimer);
        }
        this.fastTypeTimer = this.$timeout(() => {
          if (typeInput.value.length > 1) {
            this.suggestWordsByInput(typeInput.value);
          } else {
            this.suggestionList = [];
            this.suggestions = false;
          }
        }, 300);
      }
    }
  }

  /**
   * Handles, creates the data to set suggestions by,
   * current keyboard input.
   */
  suggestWordsByInput(searchLetter) {
    this.suggestions = true;
    searchLetter = searchLetter.toLocaleLowerCase();
    searchLetter = searchLetter.replace(" ", "-");
    this.suggestionList = this.$scope.global.mainSlugArray.filter((word) => {
      return word.substring(0, searchLetter.length) == searchLetter;
    });
    if (this.suggestionList.length > 0) {
      this.suggestionList.sort(this.sortByLength);
    }
  }

  /**
   * Handles actions when a suggestion symbol is clicked
   */
  clickSuggestion(wordSlug) {
    let wordObj2Push = {};
    wordObj2Push.title = wordSlug.replace("-", " ");
    wordObj2Push.slug = wordSlug;
    if (this.isVerb(wordSlug)) {
      wordObj2Push.type = "verb";
    }
    wordObj2Push.phraseIndex = this.phraseIndex++;
    this.$scope.global.pushToCurrentPhrase(wordObj2Push);

    let typeInput = document.getElementById("typeInput");
    if (typeInput) {
      document.getElementById("typeInput").value = "";
    }
    this.suggestionList = [];
    this.suggestions = false;
  }

  /**
   * Recognizes the string given by the current word package before pushing.
   * Then pushes the pieces indivicually.
   */
  recognizeWord(word) {
    // this function can be better algorithmicly!
    let splittedUnrecognizedWord = word.split(" ");
    splittedUnrecognizedWord.forEach((wordPiece) => {
      if (wordPiece) {
        let wordObj2Push = {};
        wordObj2Push.title = wordPiece;
        wordObj2Push.slug = wordPiece;
        wordObj2Push.slugExist = !this.checkWordInDB(wordObj2Push.slug);
        wordObj2Push.phraseIndex = this.phraseIndex++;
        this.$scope.global.pushToCurrentPhrase(wordObj2Push);
      }
    });
  }

  /**
   * Checks if the given word in the mainSlugArray
   */
  checkWordInDB(word) {
    return this.$scope.global.mainSlugArray.contains(word);
  }

  /**
   * Checks if the given word is a verb.
   */
  isVerb(word) {
    let words = this.$scope.global.mainArray.filter((w) => {
      return w.slug == word;
    });
    if (words.length > 0) {
      if (words[0].type == "verb") {
        return true;
      } else {
        return false;
      }
    } else {
      return false;
    }
  }

  /**
   * Comparission function for .sort() prototype.
   */
  sortByLength(a, b) {
    if (a.length > b.length) {
      return 1;
    }
    if (a.length < b.length) {
      return -1;
    }
    return 0;
  }

}

// Service Dependency Injection
KeyboardController.$inject = ['$scope', '$global', '$timeout', 'EventManager', 'TTSManager'];
<commit_msg>show symbols of conjuncted verbs in keyboard<commit_after>import { returnTime, updateCurrentPhraseScroll } from 'js/utils';

/**
 * KeyboardController
 * @export
 * @class KeyboardController
 */
export default class KeyboardController {
  /**
   * Creates an instance of KeyboardController.
   *
   * @param {any} $scope
   * @param {any} $global
   * @param {any} $timeout
   * @param {TTSManager} TTSManager
   *
   * @memberOf RecentController
   */
  constructor($scope, $global, $timeout, EventManager, TTSManager) {
    this.$scope = $scope;
    this.$scope.global = $global;
    this.$timeout = $timeout;
    this.tts = TTSManager;
    this.event = EventManager;
    this.fastTypeTimer = null;
    this.suggestions = false;
    // Initilize variables for controller.

    // Call controllerInit
    this.controllerInit();
  }

  /**
   * Create or facilitate new functions for $scope or $global service.
   */
  controllerInit() {
    this.phraseIndex = 0;
    this.suggestionList = [];
  }

  /**
   * Shows the keyboard
   */
  showKeyboard() {
    let typeInput = document.getElementById("typeInput");
    if (typeInput) {
      typeInput.focus();
      document.body.scrollTop = document.documentElement.scrollTop = 0;
    }
    this.showKeyboardFocused();
  }

  /**
   * Removes readonly from typeInput
   * Triggered when type input is
   * Walkaround for ios autocorrect
   */
  showKeyboardFocused() {
    let typeInput = document.getElementById("typeInput");
    if (typeInput) {
      document.body.style.height = (parseInt(window.innerHeight) / 2) + 'px';
      document.body.scrollTop = document.documentElement.scrollTop = 0;
      this.$timeout(() => {
        document.body.style.height = parseInt(window.innerHeight) + 'px';
        document.body.scrollTop = document.documentElement.scrollTop = 0;
        console.log("virtual keyboard opened!");
      }, 300);
    }
  }

  /**
   * Hides the keyboard
   */
  hideKeyboard() {
    let typeInput = document.getElementById("typeInput");
    if (typeInput) {
      document.body.style.height = "100%";
    }
  }

  /**
   * Submits (handles) the current typeInput string content.
   */
  submitCurrentInput() {
    let typeInput = document.getElementById("typeInput");
    if (typeInput.value) {
      let inputWord = {};
      inputWord.title = typeInput.value.toLocaleLowerCase();
      inputWord.slug = typeInput.value.toLocaleLowerCase().replace(" ", "-");
      let checkExist = this.checkWordInDB(inputWord.slug);
      inputWord.slugExist = !checkExist;

      if (inputWord.title.contains(" ") && !checkExist) {
        this.recognizeWord(inputWord.title);
      } else {
        inputWord.phraseIndex = this.phraseIndex++;
        this.$scope.global.pushToCurrentPhrase(inputWord);
      }
      updateCurrentPhraseScroll();
      this.tts.speak(inputWord.title);
      let gs = this.$scope.global.gridSize;
      this.event.appWord(inputWord.title, gs[0], gs[1]);
      typeInput.value = "";
    }
  }

  /**
   * Handles the submission of the input via "enter" from the device
   */
  enterSubmit($event) {
    if ($event.keyCode === 13) {
      this.submitCurrentInput();
      this.suggestionList = [];
    } else {
      let typeInput = document.getElementById("typeInput");
      if (typeInput) {
        // If there is a timeout, clear it.
        if (this.fastTypeTimer) {
          this.$timeout.cancel(this.fastTypeTimer);
        }
        this.fastTypeTimer = this.$timeout(() => {
          if (typeInput.value.length > 1) {
            this.suggestWordsByInput(typeInput.value);
          } else {
            this.suggestionList = [];
            this.suggestions = false;
          }
        }, 300);
      }
    }
  }

  /**
   * Handles, creates the data to set suggestions by,
   * current keyboard input.
   */
  suggestWordsByInput(searchLetter) {
    this.suggestions = true;
    searchLetter = searchLetter.toLocaleLowerCase();
    searchLetter = searchLetter.replace(" ", "-");
    this.suggestionList = this.$scope.global.mainSlugArray.filter((word) => {
      return word.substring(0, searchLetter.length) == searchLetter;
    });
    if (this.suggestionList.length > 0) {
      this.suggestionList.sort(this.sortByLength);
    }
  }

  /**
   * Handles actions when a suggestion symbol is clicked
   */
  clickSuggestion(wordSlug) {
    let wordObj2Push = {};
    wordObj2Push.title = wordSlug.replace("-", " ");
    wordObj2Push.slug = wordSlug;
    if (this.isVerb(wordSlug)) {
      wordObj2Push.type = "verb";
    }
    wordObj2Push.phraseIndex = this.phraseIndex++;
    this.$scope.global.pushToCurrentPhrase(wordObj2Push);

    let typeInput = document.getElementById("typeInput");
    if (typeInput) {
      document.getElementById("typeInput").value = "";
    }
    this.suggestionList = [];
    this.suggestions = false;
  }

  /**
   * Recognizes the string given by the current word package before pushing.
   * Then pushes the pieces indivicually.
   */
  recognizeWord(word) {
    // this function can be better algorithmicly!
    let splittedUnrecognizedWord = word.split(" ");
    splittedUnrecognizedWord.forEach((wordPiece) => {
      if (wordPiece) {
        let wordObj2Push = {};
        wordObj2Push.title = wordPiece;
        wordObj2Push.slug = wordPiece;
        wordObj2Push.slugExist = !this.checkWordInDB(wordObj2Push.slug);
        wordObj2Push.phraseIndex = this.phraseIndex++;
        this.$scope.global.pushToCurrentPhrase(wordObj2Push);
      }
    });
  }

  /**
   * Checks if the given word in the mainSlugArray
   */
  checkWordInDB(word) {
    let possibleSlug = this.$scope.global.mainSlugMap[word];
    return this.$scope.global.mainSlugArray.contains(possibleSlug);
  }

  /**
   * Checks if the given word is a verb.
   */
  isVerb(word) {
    let words = this.$scope.global.mainArray.filter((w) => {
      return w.slug == word;
    });
    if (words.length > 0) {
      if (words[0].type == "verb") {
        return true;
      } else {
        return false;
      }
    } else {
      return false;
    }
  }

  /**
   * Comparission function for .sort() prototype.
   */
  sortByLength(a, b) {
    if (a.length > b.length) {
      return 1;
    }
    if (a.length < b.length) {
      return -1;
    }
    return 0;
  }

}

// Service Dependency Injection
KeyboardController.$inject = ['$scope', '$global', '$timeout', 'EventManager', 'TTSManager'];
<|endoftext|>


<commit_before>import { Meteor } from 'meteor/meteor';
import { Match } from 'meteor/check';
import { Accounts } from 'meteor/accounts-base';
import { OAuth } from 'meteor/oauth';
import { HTTP } from 'meteor/http';
import { ServiceConfiguration } from 'meteor/service-configuration';
import { Logger } from '../../logger';
import { Users } from '../../models';
import _ from 'underscore';
import { isURL } from '../../utils/lib/isURL';

const logger = new Logger('CustomOAuth');

const Services = {};
const BeforeUpdateOrCreateUserFromExternalService = [];

export class CustomOAuth {
	constructor(name, options) {
		logger.debug('Init CustomOAuth', name, options);

		this.name = name;
		if (!Match.test(this.name, String)) {
			throw new Meteor.Error('CustomOAuth: Name is required and must be String');
		}

		if (Services[this.name]) {
			Services[this.name].configure(options);
			return;
		}

		Services[this.name] = this;

		this.configure(options);

		this.userAgent = 'Meteor';
		if (Meteor.release) {
			this.userAgent += `/${ Meteor.release }`;
		}

		Accounts.oauth.registerService(this.name);
		this.registerService();
		this.addHookToProcessUser();
	}

	configure(options) {
		if (!Match.test(options, Object)) {
			throw new Meteor.Error('CustomOAuth: Options is required and must be Object');
		}

		if (!Match.test(options.serverURL, String)) {
			throw new Meteor.Error('CustomOAuth: Options.serverURL is required and must be String');
		}

		if (!Match.test(options.tokenPath, String)) {
			options.tokenPath = '/oauth/token';
		}

		if (!Match.test(options.identityPath, String)) {
			options.identityPath = '/me';
		}

		this.serverURL = options.serverURL;
		this.tokenPath = options.tokenPath;
		this.identityPath = options.identityPath;
		this.tokenSentVia = options.tokenSentVia;
		this.identityTokenSentVia = options.identityTokenSentVia;
		this.usernameField = (options.usernameField || '').trim();
		this.mergeUsers = options.mergeUsers;

		if (this.identityTokenSentVia == null || this.identityTokenSentVia === 'default') {
			this.identityTokenSentVia = this.tokenSentVia;
		}

		if (!isURL(this.tokenPath)) {
			this.tokenPath = this.serverURL + this.tokenPath;
		}

		if (!isURL(this.identityPath)) {
			this.identityPath = this.serverURL + this.identityPath;
		}

		if (Match.test(options.addAutopublishFields, Object)) {
			Accounts.addAutopublishFields(options.addAutopublishFields);
		}
	}

	getAccessToken(query) {
		const config = ServiceConfiguration.configurations.findOne({ service: this.name });
		if (!config) {
			throw new ServiceConfiguration.ConfigError();
		}

		let response = undefined;

		const allOptions = {
			headers: {
				'User-Agent': this.userAgent, // http://doc.gitlab.com/ce/api/users.html#Current-user
				Accept: 'application/json',
			},
			params: {
				code: query.code,
				redirect_uri: OAuth._redirectUri(this.name, config),
				grant_type: 'authorization_code',
				state: query.state,
			},
		};

		// Only send clientID / secret once on header or payload.
		if (this.tokenSentVia === 'header') {
			allOptions.auth = `${ config.clientId }:${ OAuth.openSecret(config.secret) }`;
		} else {
			allOptions.params.client_secret = OAuth.openSecret(config.secret);
			allOptions.params.client_id = config.clientId;
		}

		try {
			response = HTTP.post(this.tokenPath, allOptions);
		} catch (err) {
			const error = new Error(`Failed to complete OAuth handshake with ${ this.name } at ${ this.tokenPath }. ${ err.message }`);
			throw _.extend(error, { response: err.response });
		}

		let data;
		if (response.data) {
			data = response.data;
		} else {
			data = JSON.parse(response.content);
		}

		if (data.error) { // if the http response was a json object with an error attribute
			throw new Error(`Failed to complete OAuth handshake with ${ this.name } at ${ this.tokenPath }. ${ data.error }`);
		} else {
			return data.access_token;
		}
	}

	getIdentity(accessToken) {
		const params = {};
		const headers = {
			'User-Agent': this.userAgent, // http://doc.gitlab.com/ce/api/users.html#Current-user
		};

		if (this.identityTokenSentVia === 'header') {
			headers.Authorization = `Bearer ${ accessToken }`;
		} else {
			params.access_token = accessToken;
		}

		try {
			const response = HTTP.get(this.identityPath, {
				headers,
				params,
			});

			let data;

			if (response.data) {
				data = response.data;
			} else {
				data = JSON.parse(response.content);
			}

			logger.debug('Identity response', JSON.stringify(data, null, 2));

			return data;
		} catch (err) {
			const error = new Error(`Failed to fetch identity from ${ this.name } at ${ this.identityPath }. ${ err.message }`);
			throw _.extend(error, { response: err.response });
		}
	}

	registerService() {
		const self = this;
		OAuth.registerService(this.name, 2, null, (query) => {
			const accessToken = self.getAccessToken(query);
			// console.log 'at:', accessToken

			let identity = self.getIdentity(accessToken);

			if (identity) {
				// Set 'id' to '_id' for any sources that provide it
				if (identity._id && !identity.id) {
					identity.id = identity._id;
				}

				// Fix for Reddit
				if (identity.result) {
					identity = identity.result;
				}

				// Fix WordPress-like identities having 'ID' instead of 'id'
				if (identity.ID && !identity.id) {
					identity.id = identity.ID;
				}

				// Fix Auth0-like identities having 'user_id' instead of 'id'
				if (identity.user_id && !identity.id) {
					identity.id = identity.user_id;
				}

				if (identity.CharacterID && !identity.id) {
					identity.id = identity.CharacterID;
				}

				// Fix Dataporten having 'user.userid' instead of 'id'
				if (identity.user && identity.user.userid && !identity.id) {
					if (identity.user.userid_sec && identity.user.userid_sec[0]) {
						identity.id = identity.user.userid_sec[0];
					} else {
						identity.id = identity.user.userid;
					}
					identity.email = identity.user.email;
				}
				// Fix for Xenforo [BD]API plugin for 'user.user_id; instead of 'id'
				if (identity.user && identity.user.user_id && !identity.id) {
					identity.id = identity.user.user_id;
					identity.email = identity.user.user_email;
				}
				// Fix general 'phid' instead of 'id' from phabricator
				if (identity.phid && !identity.id) {
					identity.id = identity.phid;
				}

				// Fix Keycloak-like identities having 'sub' instead of 'id'
				if (identity.sub && !identity.id) {
					identity.id = identity.sub;
				}

				// Fix OpenShift identities where id is in 'metadata' object
				if (!identity.id && identity.metadata && identity.metadata.uid) {
					identity.id = identity.metadata.uid;
					identity.name = identity.fullName;
				}

				// Fix general 'userid' instead of 'id' from provider
				if (identity.userid && !identity.id) {
					identity.id = identity.userid;
				}

				// Fix Nextcloud provider
				if (!identity.id && identity.ocs && identity.ocs.data && identity.ocs.data.id) {
					identity.id = identity.ocs.data.id;
					identity.name = identity.ocs.data.displayname;
					identity.email = identity.ocs.data.email;
				}

				// Fix when authenticating from a meteor app with 'emails' field
				if (!identity.email && (identity.emails && Array.isArray(identity.emails) && identity.emails.length >= 1)) {
					identity.email = identity.emails[0].address ? identity.emails[0].address : undefined;
				}
			}

			// console.log 'id:', JSON.stringify identity, null, '  '

			const serviceData = {
				_OAuthCustom: true,
				accessToken,
			};

			_.extend(serviceData, identity);

			const data = {
				serviceData,
				options: {
					profile: {
						name: identity.name || identity.username || identity.nickname || identity.CharacterName || identity.userName || identity.preferred_username || (identity.user && identity.user.name),
					},
				},
			};

			// console.log data

			return data;
		});
	}

	retrieveCredential(credentialToken, credentialSecret) {
		return OAuth.retrieveCredential(credentialToken, credentialSecret);
	}

	getUsername(data) {
		let username = '';

		username = this.usernameField.split('.').reduce(function(prev, curr) {
			return prev ? prev[curr] : undefined;
		}, data);
		if (!username) {
			throw new Meteor.Error('field_not_found', `Username field "${ this.usernameField }" not found in data`, data);
		}
		return username;
	}

	addHookToProcessUser() {
		BeforeUpdateOrCreateUserFromExternalService.push((serviceName, serviceData/* , options*/) => {
			if (serviceName !== this.name) {
				return;
			}

			if (this.usernameField) {
				const username = this.getUsername(serviceData);

				const user = Users.findOneByUsername(username);
				if (!user) {
					return;
				}

				// User already created or merged
				if (user.services && user.services[serviceName] && user.services[serviceName].id === serviceData.id) {
					return;
				}

				if (this.mergeUsers !== true) {
					throw new Meteor.Error('CustomOAuth', `User with username ${ user.username } already exists`);
				}

				const serviceIdKey = `services.${ serviceName }.id`;
				const update = {
					$set: {
						[serviceIdKey]: serviceData.id,
					},
				};

				Users.update({ _id: user._id }, update);
			}
		});

		Accounts.validateNewUser((user) => {
			if (!user.services || !user.services[this.name] || !user.services[this.name].id) {
				return true;
			}

			if (this.usernameField) {
				user.username = this.getUsername(user.services[this.name]);
			}

			return true;
		});

	}
}

const { updateOrCreateUserFromExternalService } = Accounts;
Accounts.updateOrCreateUserFromExternalService = function(...args /* serviceName, serviceData, options*/) {
	for (const hook of BeforeUpdateOrCreateUserFromExternalService) {
		hook.apply(this, args);
	}

	return updateOrCreateUserFromExternalService.apply(this, args);
};
<commit_msg>[NEW] update name of user if changed externally using CustomOAuth<commit_after>import { Meteor } from 'meteor/meteor';
import { Match } from 'meteor/check';
import { Accounts } from 'meteor/accounts-base';
import { OAuth } from 'meteor/oauth';
import { HTTP } from 'meteor/http';
import { ServiceConfiguration } from 'meteor/service-configuration';
import { Logger } from '../../logger';
import { Users } from '../../models';
import _ from 'underscore';
import { isURL } from '../../utils/lib/isURL';

const logger = new Logger('CustomOAuth');

const Services = {};
const BeforeUpdateOrCreateUserFromExternalService = [];

export class CustomOAuth {
	constructor(name, options) {
		logger.debug('Init CustomOAuth', name, options);

		this.name = name;
		if (!Match.test(this.name, String)) {
			throw new Meteor.Error('CustomOAuth: Name is required and must be String');
		}

		if (Services[this.name]) {
			Services[this.name].configure(options);
			return;
		}

		Services[this.name] = this;

		this.configure(options);

		this.userAgent = 'Meteor';
		if (Meteor.release) {
			this.userAgent += `/${ Meteor.release }`;
		}

		Accounts.oauth.registerService(this.name);
		this.registerService();
		this.addHookToProcessUser();
	}

	configure(options) {
		if (!Match.test(options, Object)) {
			throw new Meteor.Error('CustomOAuth: Options is required and must be Object');
		}

		if (!Match.test(options.serverURL, String)) {
			throw new Meteor.Error('CustomOAuth: Options.serverURL is required and must be String');
		}

		if (!Match.test(options.tokenPath, String)) {
			options.tokenPath = '/oauth/token';
		}

		if (!Match.test(options.identityPath, String)) {
			options.identityPath = '/me';
		}

		this.serverURL = options.serverURL;
		this.tokenPath = options.tokenPath;
		this.identityPath = options.identityPath;
		this.tokenSentVia = options.tokenSentVia;
		this.identityTokenSentVia = options.identityTokenSentVia;
		this.usernameField = (options.usernameField || '').trim();
		this.mergeUsers = options.mergeUsers;

		if (this.identityTokenSentVia == null || this.identityTokenSentVia === 'default') {
			this.identityTokenSentVia = this.tokenSentVia;
		}

		if (!isURL(this.tokenPath)) {
			this.tokenPath = this.serverURL + this.tokenPath;
		}

		if (!isURL(this.identityPath)) {
			this.identityPath = this.serverURL + this.identityPath;
		}

		if (Match.test(options.addAutopublishFields, Object)) {
			Accounts.addAutopublishFields(options.addAutopublishFields);
		}
	}

	getAccessToken(query) {
		const config = ServiceConfiguration.configurations.findOne({ service: this.name });
		if (!config) {
			throw new ServiceConfiguration.ConfigError();
		}

		let response = undefined;

		const allOptions = {
			headers: {
				'User-Agent': this.userAgent, // http://doc.gitlab.com/ce/api/users.html#Current-user
				Accept: 'application/json',
			},
			params: {
				code: query.code,
				redirect_uri: OAuth._redirectUri(this.name, config),
				grant_type: 'authorization_code',
				state: query.state,
			},
		};

		// Only send clientID / secret once on header or payload.
		if (this.tokenSentVia === 'header') {
			allOptions.auth = `${ config.clientId }:${ OAuth.openSecret(config.secret) }`;
		} else {
			allOptions.params.client_secret = OAuth.openSecret(config.secret);
			allOptions.params.client_id = config.clientId;
		}

		try {
			response = HTTP.post(this.tokenPath, allOptions);
		} catch (err) {
			const error = new Error(`Failed to complete OAuth handshake with ${ this.name } at ${ this.tokenPath }. ${ err.message }`);
			throw _.extend(error, { response: err.response });
		}

		let data;
		if (response.data) {
			data = response.data;
		} else {
			data = JSON.parse(response.content);
		}

		if (data.error) { // if the http response was a json object with an error attribute
			throw new Error(`Failed to complete OAuth handshake with ${ this.name } at ${ this.tokenPath }. ${ data.error }`);
		} else {
			return data.access_token;
		}
	}

	getIdentity(accessToken) {
		const params = {};
		const headers = {
			'User-Agent': this.userAgent, // http://doc.gitlab.com/ce/api/users.html#Current-user
		};

		if (this.identityTokenSentVia === 'header') {
			headers.Authorization = `Bearer ${ accessToken }`;
		} else {
			params.access_token = accessToken;
		}

		try {
			const response = HTTP.get(this.identityPath, {
				headers,
				params,
			});

			let data;

			if (response.data) {
				data = response.data;
			} else {
				data = JSON.parse(response.content);
			}

			logger.debug('Identity response', JSON.stringify(data, null, 2));

			return data;
		} catch (err) {
			const error = new Error(`Failed to fetch identity from ${ this.name } at ${ this.identityPath }. ${ err.message }`);
			throw _.extend(error, { response: err.response });
		}
	}

	registerService() {
		const self = this;
		OAuth.registerService(this.name, 2, null, (query) => {
			const accessToken = self.getAccessToken(query);
			// console.log 'at:', accessToken

			let identity = self.getIdentity(accessToken);

			if (identity) {
				// Set 'id' to '_id' for any sources that provide it
				if (identity._id && !identity.id) {
					identity.id = identity._id;
				}

				// Fix for Reddit
				if (identity.result) {
					identity = identity.result;
				}

				// Fix WordPress-like identities having 'ID' instead of 'id'
				if (identity.ID && !identity.id) {
					identity.id = identity.ID;
				}

				// Fix Auth0-like identities having 'user_id' instead of 'id'
				if (identity.user_id && !identity.id) {
					identity.id = identity.user_id;
				}

				if (identity.CharacterID && !identity.id) {
					identity.id = identity.CharacterID;
				}

				// Fix Dataporten having 'user.userid' instead of 'id'
				if (identity.user && identity.user.userid && !identity.id) {
					if (identity.user.userid_sec && identity.user.userid_sec[0]) {
						identity.id = identity.user.userid_sec[0];
					} else {
						identity.id = identity.user.userid;
					}
					identity.email = identity.user.email;
				}
				// Fix for Xenforo [BD]API plugin for 'user.user_id; instead of 'id'
				if (identity.user && identity.user.user_id && !identity.id) {
					identity.id = identity.user.user_id;
					identity.email = identity.user.user_email;
				}
				// Fix general 'phid' instead of 'id' from phabricator
				if (identity.phid && !identity.id) {
					identity.id = identity.phid;
				}

				// Fix Keycloak-like identities having 'sub' instead of 'id'
				if (identity.sub && !identity.id) {
					identity.id = identity.sub;
				}

				// Fix OpenShift identities where id is in 'metadata' object
				if (!identity.id && identity.metadata && identity.metadata.uid) {
					identity.id = identity.metadata.uid;
					identity.name = identity.fullName;
				}

				// Fix general 'userid' instead of 'id' from provider
				if (identity.userid && !identity.id) {
					identity.id = identity.userid;
				}

				// Fix Nextcloud provider
				if (!identity.id && identity.ocs && identity.ocs.data && identity.ocs.data.id) {
					identity.id = identity.ocs.data.id;
					identity.name = identity.ocs.data.displayname;
					identity.email = identity.ocs.data.email;
				}

				// Fix when authenticating from a meteor app with 'emails' field
				if (!identity.email && (identity.emails && Array.isArray(identity.emails) && identity.emails.length >= 1)) {
					identity.email = identity.emails[0].address ? identity.emails[0].address : undefined;
				}
			}

			// console.log 'id:', JSON.stringify identity, null, '  '

			const serviceData = {
				_OAuthCustom: true,
				accessToken,
			};

			_.extend(serviceData, identity);

			const data = {
				serviceData,
				options: {
					profile: {
						name: identity.name || identity.username || identity.nickname || identity.CharacterName || identity.userName || identity.preferred_username || (identity.user && identity.user.name),
					},
				},
			};

			// console.log data

			return data;
		});
	}

	retrieveCredential(credentialToken, credentialSecret) {
		return OAuth.retrieveCredential(credentialToken, credentialSecret);
	}

	getUsername(data) {
		let username = '';

		username = this.usernameField.split('.').reduce(function(prev, curr) {
			return prev ? prev[curr] : undefined;
		}, data);
		if (!username) {
			throw new Meteor.Error('field_not_found', `Username field "${ this.usernameField }" not found in data`, data);
		}
		return username;
	}

	addHookToProcessUser() {
		BeforeUpdateOrCreateUserFromExternalService.push((serviceName, serviceData/* , options*/) => {
			if (serviceName !== this.name) {
				return;
			}

			if (this.usernameField) {
				const username = this.getUsername(serviceData);

				const user = Users.findOneByUsername(username);
				if (!user) {
					return;
				}

				// User already created or merged and has identical name as before
				if (user.services && user.services[serviceName] && user.services[serviceName].id === serviceData.id && user.name === serviceData.name) {
					return;
				}

				if (this.mergeUsers !== true) {
					throw new Meteor.Error('CustomOAuth', `User with username ${ user.username } already exists`);
				}

				const serviceIdKey = `services.${ serviceName }.id`;
				const update = {
					$set: {
						name: serviceData.name,
						[serviceIdKey]: serviceData.id,
					},
				};

				Users.update({ _id: user._id }, update);
			}
		});

		Accounts.validateNewUser((user) => {
			if (!user.services || !user.services[this.name] || !user.services[this.name].id) {
				return true;
			}

			if (this.usernameField) {
				user.username = this.getUsername(user.services[this.name]);
			}

			return true;
		});

	}
}

const { updateOrCreateUserFromExternalService } = Accounts;
Accounts.updateOrCreateUserFromExternalService = function(...args /* serviceName, serviceData, options*/) {
	for (const hook of BeforeUpdateOrCreateUserFromExternalService) {
		hook.apply(this, args);
	}

	return updateOrCreateUserFromExternalService.apply(this, args);
};
<|endoftext|>