<commit_before>/**
 * Qlass.js
 *
 * by: Jared Williams
 * A library for performing common classy functions on elements and collections.
 *
 * Simply select an element or elements and add or remove classes to them or check if they have a class.
 * Wrap the selectors with q().
 * ie: q(document.getElementsByTagName).addClass('myClass');
 *
 */
(function() {
	var rClass = /[\n\t]/g,
        rSpace = /\s+/,
        rReturn = /\r/g,
        // Used for trimming whitespace
        rTrim = /^(\s|\u00A0)+|(\s|\u00A0)+$/g,
        rNodeList = /^\[object (HTMLCollection|NodeList|Object)\]$/;

    function isNodeList(nodes) {
		var result = Object.prototype.toString.call(nodes);
			// Check if 'nodes' is a NodeList
		if (typeof nodes === 'object' && rNodeList.test(result) && nodes.hasOwnProperty('length') &&
			(nodes.length === 0 || (typeof nodes[0] === "object" && nodes[0].nodeType > 0))
		) {
			return true;
		}
		return false;
	}

	// Constructor
	function Qlass(selector) {
		// Convert n to real array
		this.selector = selector;

		// if this isn't a null or empty object
		if (this.selector[0] && this.selector[0].length > 0) {

			// Make Array from NodeList or Element
			if (isNodeList(this.selector[0])) {
				this.selector = Array.prototype.slice.call(this.selector[0]);
			} else if (this.selector[0].nodeType === 1) {
				this.selector = Array.prototype.slice.call(this.selector);
			}
		}

		return this;
	}

	// Wrapper
	function q() {
		return new Qlass(arguments);
	}

	// Methods
	Qlass.prototype = {
		log: function() {
            console.log(this.selector);

            return this;
        },
		addClass: function(value) {
			if (value && typeof value === 'string') {
				var classNames = (value || '').split(rSpace);

				for (var i = 0, l = this.selector.length; i < l; i++) {
					var elem = this.selector[i];

					// if this is an element
					if (elem && elem.nodeType === 1) {
						// if no className exists set it and continue on
						if (!elem.className) {
							elem.className = value;
						} else {
							// otherwise check value against each class for a match
							var className = ' '+ elem.className +' ',
								setClass = elem.className;

							for (var c = 0, cl = classNames.length; c < cl; c++) {
								if (className.indexOf(' '+ classNames[c] +' ') < 0) {
									setClass += ' '+ classNames[c];
								}
							}
							elem.className = (setClass || '').replace(rTrim, '');
						}
					}
				}
			}
			return this;
		},
		hasClass: function(value) {
			for (var i = 0; i < this.selector.length; i++) {
				var elem = this.selector[i];

				if (elem && elem.nodeType === 1) {
					// check the elements className to see if the class exists to return true
					if (elem.className.indexOf(value) >= 0) {
						return true;
					}
				}
			}
			return false;
		},
		removeClass: function(value) {
			if (( value && typeof value === 'string') || value === undefined) {
				var classNames = (value || '').split(rSpace);

				for (var i = 0, l = this.selector.length; i < l; i++) {
					var elem = this.selector[i];

					if (elem && elem.nodeType === 1 && elem.className) {
						if (value) {
							var className = (' '+ elem.className +' ').replace(rClass, ' ');

							for (var c = 0, cl = classNames.length; c < cl; c++) {
								className = className.replace(' '+ classNames[c] +' ', ' ');
							}
							elem.className = (className || '').replace(rTrim, '');
						} else {
							elem.className = '';
						}
					}
				}
			}
			return this;
        }
	};

	// Reveal it to global object -> window
	window.q = q;
})();<commit_msg>fixed comment<commit_after>/**
 * Qlass.js
 *
 * by: Jared Williams
 * A library for performing common classy functions on elements and collections.
 *
 * Simply select an element or elements and add or remove classes to them or check if they have a class.
 * Wrap the selectors with q().
 * ie: q(document.getElementsByTagName('div')).addClass('myClass').hasClass('myClass');
 *
 */
(function() {
	var rClass = /[\n\t]/g,
        rSpace = /\s+/,
        rReturn = /\r/g,
        // Used for trimming whitespace
        rTrim = /^(\s|\u00A0)+|(\s|\u00A0)+$/g,
        rNodeList = /^\[object (HTMLCollection|NodeList|Object)\]$/;

    function isNodeList(nodes) {
		var result = Object.prototype.toString.call(nodes);
			// Check if 'nodes' is a NodeList
		if (typeof nodes === 'object' && rNodeList.test(result) && nodes.hasOwnProperty('length') &&
			(nodes.length === 0 || (typeof nodes[0] === "object" && nodes[0].nodeType > 0))
		) {
			return true;
		}
		return false;
	}

	// Constructor
	function Qlass(selector) {
		// Convert n to real array
		this.selector = selector;

		// if this isn't a null or empty object
		if (this.selector[0] && this.selector[0].length > 0) {

			// Make Array from NodeList or Element
			if (isNodeList(this.selector[0])) {
				this.selector = Array.prototype.slice.call(this.selector[0]);
			} else if (this.selector[0].nodeType === 1) {
				this.selector = Array.prototype.slice.call(this.selector);
			}
		}

		return this;
	}

	// Wrapper
	function q() {
		return new Qlass(arguments);
	}

	// Methods
	Qlass.prototype = {
		log: function() {
            console.log(this.selector);

            return this;
        },
		addClass: function(value) {
			if (value && typeof value === 'string') {
				var classNames = (value || '').split(rSpace);

				for (var i = 0, l = this.selector.length; i < l; i++) {
					var elem = this.selector[i];

					// if this is an element
					if (elem && elem.nodeType === 1) {
						// if no className exists set it and continue on
						if (!elem.className) {
							elem.className = value;
						} else {
							// otherwise check value against each class for a match
							var className = ' '+ elem.className +' ',
								setClass = elem.className;

							for (var c = 0, cl = classNames.length; c < cl; c++) {
								if (className.indexOf(' '+ classNames[c] +' ') < 0) {
									setClass += ' '+ classNames[c];
								}
							}
							elem.className = (setClass || '').replace(rTrim, '');
						}
					}
				}
			}
			return this;
		},
		hasClass: function(value) {
			for (var i = 0; i < this.selector.length; i++) {
				var elem = this.selector[i];

				if (elem && elem.nodeType === 1) {
					// check the elements className to see if the class exists to return true
					if (elem.className.indexOf(value) >= 0) {
						return true;
					}
				}
			}
			return false;
		},
		removeClass: function(value) {
			if (( value && typeof value === 'string') || value === undefined) {
				var classNames = (value || '').split(rSpace);

				for (var i = 0, l = this.selector.length; i < l; i++) {
					var elem = this.selector[i];

					if (elem && elem.nodeType === 1 && elem.className) {
						if (value) {
							var className = (' '+ elem.className +' ').replace(rClass, ' ');

							for (var c = 0, cl = classNames.length; c < cl; c++) {
								className = className.replace(' '+ classNames[c] +' ', ' ');
							}
							elem.className = (className || '').replace(rTrim, '');
						} else {
							elem.className = '';
						}
					}
				}
			}
			return this;
        }
	};

	// Reveal it to global object -> window
	window.q = q;
})();<|endoftext|>


<commit_before>/**
*
* simple-quadtree is a minimal quadtree implementation that supports simple put, get,
* remove and clear operations on objects having a x, y position and w, h dimension.
*
* Copyright (c) 2013 Antti Saarinen <antti.p.saarinen@gmail.com>
* https://github.com/asaarinen/qtree
*
*/
function QuadTree(x, y, w, h, options) {

    if( typeof x != 'number' || isNaN(x) )
        x = 0;
    if( typeof y != 'number' || isNaN(y) )
        y = 0;
    if( typeof w != 'number' || isNaN(w) )
        w = 10;
    if( typeof h != 'number' || isNaN(h) )
        h = 10;

    var maxc = 25;
    var leafratio = 0.5;
    if( options ) {
        if( typeof options.maxchildren == 'number' )
            if( options.maxchildren > 0 )
                maxc = options.maxchildren;
        if( typeof options.leafratio == 'number' )
            if( options.leafratio >= 0 )
                leafratio = options.leafratio;
    }

    // validate an input object
    function validate(obj) {
        if( !obj )
            return false;
        if( typeof obj.x != 'number' ||
            typeof obj.y != 'number' ||
            typeof obj.w != 'number' ||
            typeof obj.h != 'number' )
            return false;
        if( isNaN(obj.x) || isNaN(obj.y) ||
            isNaN(obj.w) || isNaN(obj.h) )
            return false;
        if( obj.w < 0 || obj.h < 0 )
            return false;
        return true;
    }

    // test for deep equality for x,y,w,h
    function isequal(o1, o2) {
        if( o1.x == o2.x &&
            o1.y == o2.y &&
            o1.w == o2.w &&
            o1.h == o2.h )
            return true;
        return false;
    }

    // create a new quadtree node
    function createnode(x, y, w, h) {
        return {
            x: x,
            y: y,
            w: w,
            h: h,
            c: [],
            l: [],
            n: []
        }
    }

    // root node used by this quadtree
    var root = createnode(x, y, w, h);

    // calculate distance between two points
    function distance(x1, y1, x2, y2) {
        return Math.sqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1));
    }

    // calculate distance between a point and a line (segment)
    function distancePL(x, y, x1, y1, dx1, dy1, len1 ) {
        if( !len1 ) // in case length is not provided, assume a line
            len1 = -1;

        // x = x1 + s * dx1 + t * dy1
        // y = y1 + s * dy1 - t * dx1
        // x * dy1 - y * dx1 = x1 * dy1 - y1 * dx1 +
        //                     t * ( dy1 * dy1 + dx1 * dx1 )
        var t = dx1 * dx1 + dy1 * dy1;
        if( t == 0 )
            return null;
        else {
            t = ( x * dy1 - y * dx1 - x1 * dy1 + y1 * dx1 ) / t;
            if( Math.abs(dx1) > Math.abs(dy1) )
                var s = ( x - x1 - t * dy1 ) / dx1;
            else
                var s = ( y - y1 + t * dx1 ) / dy1;
            if( ( s >= 0 && s <= len1 ) || len1 < 0 )
                return {
                    s: s,
                    t: t,
                    x: x1 + s * dx1,
                    y: y1 + s * dy1,
                    dist: Math.abs(t)
                };
            else if( s < 0 ) {
                var dist = distance(x, y, x1, y1);
                return {
                    s: s,
                    dist: dist
                };
            } else {
                var dist = distance(x, y,
                                    x1 + len1*dx1,
                                    y1 + len1*dy1);
                return {
                    s: s,
                    dist: dist
                };
            }
        }
    }

    // does a line and a rectangle overlap ?
    function overlap_line(o1, o2, buf) {
        if( !o1 || !o2 )
            return true;
        var dist = distancePL(o2.x + 0.5 * o2.w,
                              o2.y + 0.5 * o2.h,
                              o1.x, o1.y, o1.dx, o1.dy, o1.dist);
        if( dist ) {
            dist.dist -= buf;
            if( dist.dist < 0 )
                return true;
            if( dist.dist * dist.dist <= o2.w * o2.w + o2.h * o2.h )
                return true;
        }
        return false;
    }

    // do two rectangles overlap ?
    function overlap_rect(o1, o2, buf) {
        if( !o1 || !o2 )
            return true;
        if( o1.x + o1.w < o2.x - buf ||
            o1.y + o1.h < o2.y - buf ||
            o1.x - buf > o2.x + o2.w ||
            o1.y - buf > o2.y + o2.h )
            return false;
        return true;
    }

    function isleaf(node, obj) {

        var leaf = false;
        if( obj.w * obj.h > node.w * node.h * leafratio )
            leaf = true;

        if( obj.x < node.x ||
            obj.y < node.y ||
            obj.x + obj.w > node.x + node.w ||
            obj.y + obj.h > node.y + node.h )
            leaf = true;

        var childnode = null;
        for( var ni = 0; ni < node.n.length; ni++ )
            if( overlap_rect(obj, node.n[ni], 0) ) {
                if( childnode ) { // multiple hits
                    leaf = true;
                    break;
                } else
                    childnode = node.n[ni];
            }

        return { leaf: leaf,
                 childnode: childnode };
    }

    // put an object to one of the child nodes of this node
    function put_to_nodes(node, obj) {
        var leaf = isleaf(node, obj);
        if( leaf )
            node.l.push(obj);
        else if( leaf.childnode )
            put(leaf.childnode, obj);
        else
            return;
    }

    function update_coords(obj, updatedcoords) {
        obj.x = ((typeof updatedcoords.x == 'number') ? updatedcoords.x : obj.x);
        obj.y = ((typeof updatedcoords.y == 'number') ? updatedcoords.y : obj.y);
        obj.w = ((typeof updatedcoords.w == 'number') ? updatedcoords.w : obj.w);
        obj.h = ((typeof updatedcoords.h == 'number') ? updatedcoords.h : obj.h);
    }

    function update(node, obj, attr, updatedcoords) {
        if( typeof attr == 'object' && typeof updatedcoords == 'undefined' ) {
            updatedcoords = attr;
            attr = false;
        }

        if( !validate(obj) || typeof updatedcoords == 'undefined' )
            return false;

        if( !attr )
            attr = false;
        else if( typeof attr != 'string' )
            attr = 'id';

        var count = 0;
        for( var ci = 0; ci < node.c.length; ci++ )
            if( ( attr && node.c[ci][attr] == obj[attr] ) ||
                ( !attr && isequal(node.c[ci], obj) ) ) {

                // found the object to be updated
                var orig = node.c[ci];
                update_coords(orig, updatedcoords);

                if( orig.x > node.x + node.w ||
                    orig.y > node.y + node.h ||
                    orig.x + orig.w < node.x ||
                    orig.y + orig.h < node.y ) {

                    // this object needs to be removed and added
                    node.c.splice(ci, 1);
                    put(root, orig);
                } // updated object is still inside same node

                return true;
            }

        for( var ci = 0; ci < node.l.length; ci++ )
            if( ( attr && node.l[ci][attr] == obj[attr] ) ||
                ( !attr && isequal(node.l[ci], obj) ) ) {

                var orig = node.l[ci];
                update_coords(orig, updatedcoords);

                // found the object to be updated
                if( orig.x > node.x + node.w ||
                    orig.y > node.y + node.h ||
                    orig.x + orig.w < node.x ||
                    orig.y + orig.h < node.y ) {

                    // this object needs to be removed and added
                    node.l.splice(ci, 1);
                    put(root, orig);
                } // updated object is still inside same node

                return true;
            }

        var leaf = isleaf(node, obj);
        if( !leaf.leaf && leaf.childnode )
            if( update(leaf.childnode, obj, attr) )
                return true;
        return false;
    }

    // remove an object from this node
    function remove(node, obj, attr) {
        if( !validate(obj) )
            return 0;

        if( !attr )
            attr = false;
        else if( typeof attr != 'string' )
            attr = 'id';

        var count = 0;
        for( var ci = 0; ci < node.c.length; ci++ )
            if( ( attr && node.c[ci][attr] == obj[attr] ) ||
                ( !attr && isequal(node.c[ci], obj) ) ) {
                count++;
                node.c.splice(ci, 1);
                ci--;
            }

        for( var ci = 0; ci < node.l.length; ci++ )
            if( ( attr && node.l[ci][attr] == obj[attr] ) ||
                ( !attr && isequal(node.l[ci], obj) ) ) {
                count++;
                node.l.splice(ci, 1);
                ci--;
            }

        var leaf = isleaf(node, obj);
        if( !leaf.leaf && leaf.childnode )
            return count + remove(leaf.childnode, obj, attr);
        return count;
    }

    // put an object to this node
    function put(node, obj) {

        if( !validate(obj) )
            return;

        if( node.n.length == 0 ) {
            node.c.push(obj);

            // subdivide
            if( node.c.length > maxc ) {
                var w2 = node.w / 2;
                var h2 = node.h / 2;
                node.n.push(createnode(node.x, node.y, w2, h2),
                            createnode(node.x + w2, node.y, w2, h2),
                            createnode(node.x, node.y + h2, w2, h2),
                            createnode(node.x + w2, node.y + h2, w2, h2));
                for( var ci = 0; ci < node.c.length; ci++ )
                    put_to_nodes(node, node.c[ci]);
                node.c = [];
            }
        } else
            put_to_nodes(node, obj);
    }

    // iterate through all objects in this node matching given overlap
    // function
    function getter(overlapfun, node, obj, buf, strict, callbackOrArray) {
        for( var li = 0; li < node.l.length; li++ )
            if( !strict || overlapfun(obj, node.l[li], buf) )
                if( typeof callbackOrArray == 'object' )
                    callbackOrArray.push(node.l[li]);
                else if( !callbackOrArray(node.l[li]) )
                    return false;
        for( var li = 0; li < node.c.length; li++ )
            if( !strict || overlapfun(obj, node.c[li], buf) )
                if( typeof callbackOrArray == 'object' )
                    callbackOrArray.push(node.c[li]);
                else if( !callbackOrArray(node.c[li]) )
                    return false;
        for( var ni = 0; ni < node.n.length; ni++ ) {
            if( overlapfun(obj, node.n[ni], buf) ) {
                if( typeof callbackOrArray == 'object' )
                    callbackOrArray.concat(getter(overlapfun, node.n[ni], obj, buf, strict, callbackOrArray));
                else if( !getter(overlapfun, node.n[ni], obj, buf, strict, callbackOrArray) )
                    return false;
            }
        }
        return true;
    }

    // iterate through all objects in this node matching the given rectangle
    function get_rect(node, obj, buf, callbackOrArray) {
        return getter(overlap_rect, node, obj, buf, true, callbackOrArray);
    }

    // iterate through all objects in this node matching the given
    // line (segment)
    function get_line(node, obj, buf, callbackOrArray) {
        return getter(overlap_line, node, obj, buf, false, callbackOrArray);
    }

    // iterate through all objects in this node matching given
    // geometry, either a rectangle or a line segment
    function get(node, obj, buf, callbackOrArray) {

        if( (typeof buf == 'function' || typeof buf == 'object') && typeof callbackOrArray == 'undefined' ) {
            callbackOrArray = buf;
            buf = 0;
        }
        if( typeof callbackOrArray == 'undefined' ) {
            callbackOrArray = [];
            buf = 0;
        }
        if( obj == null )
            get_rect(node, obj, buf, callbackOrArray);
        else if( typeof obj.x == 'number' &&
                 typeof obj.y == 'number' &&
                 !isNaN(obj.x) && !isNaN(obj.y) ) {
            if( typeof obj.dx == 'number' &&
                typeof obj.dy == 'number' &&
                !isNaN(obj.dx) && !isNaN(obj.dy) )
                get_line(node, obj, buf, callbackOrArray);
            else if( typeof obj.w == 'number' &&
                     typeof obj.h == 'number' &&
                     !isNaN(obj.w) && !isNaN(obj.h) )
                get_rect(node, obj, buf, callbackOrArray);
        }
        if( typeof callbackOrArray == 'object' )
            return callbackOrArray;
    }

    // return the object interface
    return {
        get: function(obj, buf, callbackOrArray) {
            return get(root, obj, buf, callbackOrArray);
        },
        put: function(obj) {
            put(root, obj);
        },
        update: function(obj, attr, updatedcoords) {
            return update(root, obj, attr, updatedcoords);
        },
        remove: function(obj, attr) {
            return remove(root, obj, attr);
        },
        clear: function() {
            root = createnode(x, y, w, h);
        },
        stringify: function() {
            var strobj = {
                x: x, y: y, w: w, h: h,
                maxc: maxc,
                leafratio: leafratio,
                root: root
            };
            try {
                return JSON.stringify(strobj);
            } catch(err) {
                // could not stringify
                // probably due to objects included in qtree being non-stringifiable
                return null;
            }
        },
        parse: function(str) {
            if( typeof str == 'string' )
                str = JSON.parse(str);

            x = str.x;
            y = str.y;
            w = str.w;
            h = str.h;
            maxc = str.maxc;
            leafratio = str.leafratio;
            root = str.root;
        }
    };
}

// for use within node.js
if( typeof module != 'undefined' )
    module.exports = QuadTree;
<commit_msg>fixed bug with leaf nodes<commit_after>/**
*
* simple-quadtree is a minimal quadtree implementation that supports simple put, get,
* remove and clear operations on objects having a x, y position and w, h dimension.
*
* Copyright (c) 2013 Antti Saarinen <antti.p.saarinen@gmail.com>
* https://github.com/asaarinen/qtree
*
*/
function QuadTree(x, y, w, h, options) {

    if( typeof x != 'number' || isNaN(x) )
        x = 0;
    if( typeof y != 'number' || isNaN(y) )
        y = 0;
    if( typeof w != 'number' || isNaN(w) )
        w = 10;
    if( typeof h != 'number' || isNaN(h) )
        h = 10;

    var maxc = 25;
    var leafratio = 0.5;
    if( options ) {
        if( typeof options.maxchildren == 'number' )
            if( options.maxchildren > 0 )
                maxc = options.maxchildren;
        if( typeof options.leafratio == 'number' )
            if( options.leafratio >= 0 )
                leafratio = options.leafratio;
    }

    // validate an input object
    function validate(obj) {
        if( !obj )
            return false;
        if( typeof obj.x != 'number' ||
            typeof obj.y != 'number' ||
            typeof obj.w != 'number' ||
            typeof obj.h != 'number' )
            return false;
        if( isNaN(obj.x) || isNaN(obj.y) ||
            isNaN(obj.w) || isNaN(obj.h) )
            return false;
        if( obj.w < 0 || obj.h < 0 )
            return false;
        return true;
    }

    // test for deep equality for x,y,w,h
    function isequal(o1, o2) {
        if( o1.x == o2.x &&
            o1.y == o2.y &&
            o1.w == o2.w &&
            o1.h == o2.h )
            return true;
        return false;
    }

    // create a new quadtree node
    function createnode(x, y, w, h) {
        return {
            x: x,
            y: y,
            w: w,
            h: h,
            c: [],
            l: [],
            n: []
        }
    }

    // root node used by this quadtree
    var root = createnode(x, y, w, h);

    // calculate distance between two points
    function distance(x1, y1, x2, y2) {
        return Math.sqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1));
    }

    // calculate distance between a point and a line (segment)
    function distancePL(x, y, x1, y1, dx1, dy1, len1 ) {
        if( !len1 ) // in case length is not provided, assume a line
            len1 = -1;

        // x = x1 + s * dx1 + t * dy1
        // y = y1 + s * dy1 - t * dx1
        // x * dy1 - y * dx1 = x1 * dy1 - y1 * dx1 +
        //                     t * ( dy1 * dy1 + dx1 * dx1 )
        var t = dx1 * dx1 + dy1 * dy1;
        if( t == 0 )
            return null;
        else {
            t = ( x * dy1 - y * dx1 - x1 * dy1 + y1 * dx1 ) / t;
            if( Math.abs(dx1) > Math.abs(dy1) )
                var s = ( x - x1 - t * dy1 ) / dx1;
            else
                var s = ( y - y1 + t * dx1 ) / dy1;
            if( ( s >= 0 && s <= len1 ) || len1 < 0 )
                return {
                    s: s,
                    t: t,
                    x: x1 + s * dx1,
                    y: y1 + s * dy1,
                    dist: Math.abs(t)
                };
            else if( s < 0 ) {
                var dist = distance(x, y, x1, y1);
                return {
                    s: s,
                    dist: dist
                };
            } else {
                var dist = distance(x, y,
                                    x1 + len1*dx1,
                                    y1 + len1*dy1);
                return {
                    s: s,
                    dist: dist
                };
            }
        }
    }

    // does a line and a rectangle overlap ?
    function overlap_line(o1, o2, buf) {
        if( !o1 || !o2 )
            return true;
        var dist = distancePL(o2.x + 0.5 * o2.w,
                              o2.y + 0.5 * o2.h,
                              o1.x, o1.y, o1.dx, o1.dy, o1.dist);
        if( dist ) {
            dist.dist -= buf;
            if( dist.dist < 0 )
                return true;
            if( dist.dist * dist.dist <= o2.w * o2.w + o2.h * o2.h )
                return true;
        }
        return false;
    }

    // do two rectangles overlap ?
    function overlap_rect(o1, o2, buf) {
        if( !o1 || !o2 )
            return true;
        if( o1.x + o1.w < o2.x - buf ||
            o1.y + o1.h < o2.y - buf ||
            o1.x - buf > o2.x + o2.w ||
            o1.y - buf > o2.y + o2.h )
            return false;
        return true;
    }

    function isleaf(node, obj) {

        var leaf = false;
        if( obj.w * obj.h > node.w * node.h * leafratio )
            leaf = true;

        if( obj.x < node.x ||
            obj.y < node.y ||
            obj.x + obj.w > node.x + node.w ||
            obj.y + obj.h > node.y + node.h )
            leaf = true;

        var childnode = null;
        for( var ni = 0; ni < node.n.length; ni++ )
            if( overlap_rect(obj, node.n[ni], 0) ) {
                if( childnode ) { // multiple hits
                    leaf = true;
                    break;
                } else
                    childnode = node.n[ni];
            }

        return { leaf: leaf,
                 childnode: childnode };
    }

    // put an object to one of the child nodes of this node
    function put_to_nodes(node, obj) {
        var leaf = isleaf(node, obj);
        if( leaf.leaf )
            node.l.push(obj);
        else if( leaf.childnode )
            put(leaf.childnode, obj);
        else
            return;
    }

    function update_coords(obj, updatedcoords) {
        obj.x = ((typeof updatedcoords.x == 'number') ? updatedcoords.x : obj.x);
        obj.y = ((typeof updatedcoords.y == 'number') ? updatedcoords.y : obj.y);
        obj.w = ((typeof updatedcoords.w == 'number') ? updatedcoords.w : obj.w);
        obj.h = ((typeof updatedcoords.h == 'number') ? updatedcoords.h : obj.h);
    }

    function update(node, obj, attr, updatedcoords) {
        if( typeof attr == 'object' && typeof updatedcoords == 'undefined' ) {
            updatedcoords = attr;
            attr = false;
        }

        if( !validate(obj) || typeof updatedcoords == 'undefined' )
            return false;

        if( !attr )
            attr = false;
        else if( typeof attr != 'string' )
            attr = 'id';

        var count = 0;
        for( var ci = 0; ci < node.c.length; ci++ )
            if( ( attr && node.c[ci][attr] == obj[attr] ) ||
                ( !attr && isequal(node.c[ci], obj) ) ) {

                // found the object to be updated
                var orig = node.c[ci];
                update_coords(orig, updatedcoords);

                if( orig.x > node.x + node.w ||
                    orig.y > node.y + node.h ||
                    orig.x + orig.w < node.x ||
                    orig.y + orig.h < node.y ) {

                    // this object needs to be removed and added
                    node.c.splice(ci, 1);
                    put(root, orig);
                } // updated object is still inside same node

                return true;
            }

        for( var ci = 0; ci < node.l.length; ci++ )
            if( ( attr && node.l[ci][attr] == obj[attr] ) ||
                ( !attr && isequal(node.l[ci], obj) ) ) {

                var orig = node.l[ci];
                update_coords(orig, updatedcoords);

                // found the object to be updated
                if( orig.x > node.x + node.w ||
                    orig.y > node.y + node.h ||
                    orig.x + orig.w < node.x ||
                    orig.y + orig.h < node.y ) {

                    // this object needs to be removed and added
                    node.l.splice(ci, 1);
                    put(root, orig);
                } // updated object is still inside same node

                return true;
            }

        var leaf = isleaf(node, obj);
        if( !leaf.leaf && leaf.childnode )
            if( update(leaf.childnode, obj, attr) )
                return true;
        return false;
    }

    // remove an object from this node
    function remove(node, obj, attr) {
        if( !validate(obj) )
            return 0;

        if( !attr )
            attr = false;
        else if( typeof attr != 'string' )
            attr = 'id';

        var count = 0;
        for( var ci = 0; ci < node.c.length; ci++ )
            if( ( attr && node.c[ci][attr] == obj[attr] ) ||
                ( !attr && isequal(node.c[ci], obj) ) ) {
                count++;
                node.c.splice(ci, 1);
                ci--;
            }

        for( var ci = 0; ci < node.l.length; ci++ )
            if( ( attr && node.l[ci][attr] == obj[attr] ) ||
                ( !attr && isequal(node.l[ci], obj) ) ) {
                count++;
                node.l.splice(ci, 1);
                ci--;
            }

        var leaf = isleaf(node, obj);
        if( !leaf.leaf && leaf.childnode )
            return count + remove(leaf.childnode, obj, attr);
        return count;
    }

    // put an object to this node
    function put(node, obj) {

        if( !validate(obj) )
            return;

        if( node.n.length == 0 ) {
            node.c.push(obj);

            // subdivide
            if( node.c.length > maxc ) {
                var w2 = node.w / 2;
                var h2 = node.h / 2;
                node.n.push(createnode(node.x, node.y, w2, h2),
                            createnode(node.x + w2, node.y, w2, h2),
                            createnode(node.x, node.y + h2, w2, h2),
                            createnode(node.x + w2, node.y + h2, w2, h2));
                for( var ci = 0; ci < node.c.length; ci++ )
                    put_to_nodes(node, node.c[ci]);
                node.c = [];
            }
        } else
            put_to_nodes(node, obj);
    }

    // iterate through all objects in this node matching given overlap
    // function
    function getter(overlapfun, node, obj, buf, strict, callbackOrArray) {
        for( var li = 0; li < node.l.length; li++ )
            if( !strict || overlapfun(obj, node.l[li], buf) )
                if( typeof callbackOrArray == 'object' )
                    callbackOrArray.push(node.l[li]);
                else if( !callbackOrArray(node.l[li]) )
                    return false;
        for( var li = 0; li < node.c.length; li++ )
            if( !strict || overlapfun(obj, node.c[li], buf) )
                if( typeof callbackOrArray == 'object' )
                    callbackOrArray.push(node.c[li]);
                else if( !callbackOrArray(node.c[li]) )
                    return false;
        for( var ni = 0; ni < node.n.length; ni++ ) {
            if( overlapfun(obj, node.n[ni], buf) ) {
                if( typeof callbackOrArray == 'object' )
                    callbackOrArray.concat(getter(overlapfun, node.n[ni], obj, buf, strict, callbackOrArray));
                else if( !getter(overlapfun, node.n[ni], obj, buf, strict, callbackOrArray) )
                    return false;
            }
        }
        return true;
    }

    // iterate through all objects in this node matching the given rectangle
    function get_rect(node, obj, buf, callbackOrArray) {
        return getter(overlap_rect, node, obj, buf, true, callbackOrArray);
    }

    // iterate through all objects in this node matching the given
    // line (segment)
    function get_line(node, obj, buf, callbackOrArray) {
        return getter(overlap_line, node, obj, buf, false, callbackOrArray);
    }

    // iterate through all objects in this node matching given
    // geometry, either a rectangle or a line segment
    function get(node, obj, buf, callbackOrArray) {

        if( (typeof buf == 'function' || typeof buf == 'object') && typeof callbackOrArray == 'undefined' ) {
            callbackOrArray = buf;
            buf = 0;
        }
        if( typeof callbackOrArray == 'undefined' ) {
            callbackOrArray = [];
            buf = 0;
        }
        if( obj == null )
            get_rect(node, obj, buf, callbackOrArray);
        else if( typeof obj.x == 'number' &&
                 typeof obj.y == 'number' &&
                 !isNaN(obj.x) && !isNaN(obj.y) ) {
            if( typeof obj.dx == 'number' &&
                typeof obj.dy == 'number' &&
                !isNaN(obj.dx) && !isNaN(obj.dy) )
                get_line(node, obj, buf, callbackOrArray);
            else if( typeof obj.w == 'number' &&
                     typeof obj.h == 'number' &&
                     !isNaN(obj.w) && !isNaN(obj.h) )
                get_rect(node, obj, buf, callbackOrArray);
        }
        if( typeof callbackOrArray == 'object' )
            return callbackOrArray;
    }

    // return the object interface
    return {
        get: function(obj, buf, callbackOrArray) {
            return get(root, obj, buf, callbackOrArray);
        },
        put: function(obj) {
            put(root, obj);
        },
        update: function(obj, attr, updatedcoords) {
            return update(root, obj, attr, updatedcoords);
        },
        remove: function(obj, attr) {
            return remove(root, obj, attr);
        },
        clear: function() {
            root = createnode(x, y, w, h);
        },
        stringify: function() {
            var strobj = {
                x: x, y: y, w: w, h: h,
                maxc: maxc,
                leafratio: leafratio,
                root: root
            };
            try {
                return JSON.stringify(strobj);
            } catch(err) {
                // could not stringify
                // probably due to objects included in qtree being non-stringifiable
                return null;
            }
        },
        parse: function(str) {
            if( typeof str == 'string' )
                str = JSON.parse(str);

            x = str.x;
            y = str.y;
            w = str.w;
            h = str.h;
            maxc = str.maxc;
            leafratio = str.leafratio;
            root = str.root;
        }
    };
}

// for use within node.js
if( typeof module != 'undefined' )
    module.exports = QuadTree;
<|endoftext|>


<commit_before>
/*
Implementation of queue as a circular buffer. Note: no checking
for overflow and underflow. Should never happen here.
*/

// constructor
  var Queue = function(maxItems) {
    this.n = maxItems;
    this.q = new Array(this.n);
    this.head = 0;
    this.len = 0;
  }

  Queue.prototype.length = function() {
    return this.len;
  }

  Queue.prototype.enqueue = function(item) {
    this.q[(this.head + this.len) % this.n] = item;
    this.len++;
  }

  Queue.prototype.dequeue = function() {
    var item = this.q[this.head];
    this.len--;
    this.head = (this.head + 1) % this.n;
    return item;
  }

  Queue.prototype.first = function() {
    return this.q[this.head];
  }

  Queue.prototype.last = function() {
    return this.q[(this.head + this.len - 1) % this.n];
  }

  Queue.prototype.peek = function(idx) {
    return this.q[(this.head + idx) % this.n];
  }

function qtest(n) {
  var q = new Queue(n);
  for (var i = 1; i <= n; i++) {
    q.enqueue(i);
  }
  for (var j = 1; j <= n; j++) {
    console.log(q.dequeue());
  }
}<commit_msg>Added explanation of the queue implementation.<commit_after>
/*
Implementation of queue as a circular buffer. Note: no checking
for overflow and underflow. Should never happen here.

I chose the circular buffer rather than Javascript's built-in
'unshift' and 'pop' methods for efficiency reasons: This way I
get constant-time enqueue and dequeue, whereas 'unshift' is linear
in the length of the queue.
*/

// constructor
  var Queue = function(maxItems) {
    this.n = maxItems;
    this.q = new Array(this.n);
    this.head = 0;
    this.len = 0;
  }

  Queue.prototype.length = function() {
    return this.len;
  }

  Queue.prototype.enqueue = function(item) {
    this.q[(this.head + this.len) % this.n] = item;
    this.len++;
  }

  Queue.prototype.dequeue = function() {
    var item = this.q[this.head];
    this.len--;
    this.head = (this.head + 1) % this.n;
    return item;
  }

  Queue.prototype.first = function() {
    return this.q[this.head];
  }

  Queue.prototype.last = function() {
    return this.q[(this.head + this.len - 1) % this.n];
  }

  Queue.prototype.peek = function(idx) {
    return this.q[(this.head + idx) % this.n];
  }

<|endoftext|>


<commit_before>var Node = function(value) {
	this.prevNode	= null;
	this.value		= value;
}

var Queue = function() {
	this.first	= null;
	this.last		= null;
	this.length = 0;
}

Queue.prototype.push = function(value) {
	var node = new Node(value);

	if(this.length !== 0) {
		this.last.prevNode	= node;
		this.last						= node;
	} else {
		this.last		= node;
		this.first	= node;
	}
	this.length++;
}

Queue.prototype.pop = function() {
	if(this.length === 0)
		return null;
	else if(this.length == 1) {
		var node		= this.first;
		this.first	= null;
		this.last		= null;
		this.length--;
		return node.value;
	}

	var node = this.first;
	this.first = this.first.prevNode;
	this.length--;
	return node.value;
}<commit_msg>tidied up the queue.js code<commit_after>var Node = function(value) {
	this.prevNode	= null;
	this.value		= value;
}

var Queue = function() {
	this.first	= null;
	this.last		= null;
	this.length = 0;
}

Queue.prototype.push = function(value) {
	var node = new Node(value);

	if(this.length !== 0) {
		this.last.prevNode	= node;
		this.last						= node;
	} else {
		this.last						= node;
		this.first					= node;
	}
	this.length++;
}

Queue.prototype.pop = function() {
	if(this.length === 0)
		return null;
	else if(this.length === 1) {
		var node		= this.first;
		this.first	= null;
		this.last		= null;
		this.length--;
		return node.value;
	}

	var node			= this.first;
	this.first		= this.first.prevNode;
	this.length--;
	return node.value;
}<|endoftext|>


<commit_before>import aboutTemplate from 'app/views/about.html';
import supportTemplate from 'app/views/support.html';
import filtersTemplate from 'app/views/filters.html';

export default class ContentController {
  constructor(
    dimActivityTrackerService,
    dimState,
    ngDialog,
    $rootScope,
    loadingTracker,
    dimPlatformService,
    $interval,
    hotkeys,
    $timeout,
    dimStoreService,
    dimXurService,
    dimSettingsService,
    $window,
    $scope,
    $state,
    dimVendorService,
    $translate,
    dimInfoService
  ) {
    'ngInject';

    const vm = this;

    // Variables for templates that webpack does not automatically correct.
    vm.$DIM_VERSION = $DIM_VERSION;
    vm.$DIM_FLAVOR = $DIM_FLAVOR;
    vm.$DIM_CHANGELOG = $DIM_CHANGELOG;

    vm.loadingTracker = loadingTracker;
    vm.platforms = [];

    vm.platformChange = function platformChange(platform) {
      loadingTracker.addPromise(dimPlatformService.setActive(platform));
    };

    $scope.$on('dim-platforms-updated', (e, args) => {
      vm.platforms = args.platforms;
    });

    $scope.$on('dim-active-platform-updated', (e, args) => {
      dimState.active = vm.currentPlatform = args.platform;
    });

    loadingTracker.addPromise(dimPlatformService.getPlatforms());

    vm.settings = dimSettingsService;
    $scope.$watch(() => vm.settings.itemSize, (size) => {
      document.querySelector('html').style.setProperty("--item-size", `${size}px`);
    });
    $scope.$watch(() => vm.settings.charCol, (cols) => {
      document.querySelector('html').style.setProperty("--character-columns", cols);
    });
    $scope.$watch(() => vm.settings.vaultMaxCol, (cols) => {
      document.querySelector('html').style.setProperty("--vault-max-columns", cols);
    });
    if ($featureFlags.colorA11y) {
      $scope.$watch(() => vm.settings.colorA11y, (color) => {
        if (color && color !== '-') {
          document.querySelector('html').style.setProperty("--color-filter", `url(#${color.toLowerCase()})`);
        } else {
          document.querySelector('html').style.removeProperty("--color-filter");
        }
      });
    }

    vm.featureFlags = {
      vendorsEnabled: $featureFlags.vendorsEnabled,
      activities: $featureFlags.activities,
      colorA11y: $featureFlags.colorA11y
    };
    vm.vendorService = dimVendorService;

    hotkeys = hotkeys.bindTo($scope);

    hotkeys.add({
      combo: ['i'],
      description: $translate.instant('Hotkey.ToggleDetails'),
      callback: function() {
        $rootScope.$broadcast('dim-toggle-item-details');
      }
    });

    if ($featureFlags.tagsEnabled) {
      dimSettingsService.itemTags.forEach((tag) => {
        if (tag.hotkey) {
          hotkeys.add({
            combo: [tag.hotkey],
            description: $translate.instant('Hotkey.MarkItemAs', {
              tag: $translate.instant(tag.label)
            }),
            callback: function() {
              $rootScope.$broadcast('dim-item-tag', { tag: tag.type });
            }
          });
        }
      });
    }

    hotkeys.add({
      combo: ['ctrl+alt+shift+d'],
      callback: function() {
        dimState.debug = true;
        console.log("***** DIM DEBUG MODE ENABLED *****");
      }
    });

    /**
     * Show a popup dialog containing the given template. Its class
     * will be based on the name.
     */
    function showPopupFunction(name, template) {
      let result;
      return function(e) {
        e.stopPropagation();

        if (result) {
          result.close();
        } else {
          ngDialog.closeAll();
          result = ngDialog.open({
            template: template,
            className: name,
            appendClassName: 'modal-dialog'
          });

          result.closePromise.then(() => {
            result = null;
          });
        }
      };
    }

    vm.showSetting = showPopupFunction('settings', '<settings></settings>');
    vm.showAbout = showPopupFunction('about', aboutTemplate);
    vm.showSupport = showPopupFunction('support', supportTemplate);
    vm.showFilters = showPopupFunction('filters', filtersTemplate);
    vm.showXur = showPopupFunction('xur', '<xur></xur>');

    // TODO: make this into a ui-sref-toggle attribute directive
    vm.toggleState = function(name) {
      $state.go($state.is(name) ? 'inventory' : name);
    };

    vm.xur = dimXurService;

    // An abbreviated version of the messager from storage.component.js,
    // just so we can send an info popup.
    function messageHandler(event) {
      // We only accept messages from ourselves
      if (event.source !== window) {
        return;
      }

      switch (event.data.type) {
      case 'DIM_EXT_PONG':
        dimInfoService.show('extension-deprecated', {
          title: $translate.instant('Help.ExtensionDeprecatedTitle'),
          body: $translate.instant('Help.ExtensionDeprecatedMessage'),
          type: 'info'
        }, 0);
        break;
      }
    }

    window.addEventListener('message', messageHandler, false);
    window.postMessage({ type: 'DIM_EXT_PING' }, "*");

    $scope.$on('$destroy', () => {
      window.removeEventListener('message', messageHandler);
    });
  }
}
<commit_msg>Make the extension deprecation info popup only show once<commit_after>import _ from 'underscore';

import aboutTemplate from 'app/views/about.html';
import supportTemplate from 'app/views/support.html';
import filtersTemplate from 'app/views/filters.html';

// This is outside the class in order to make it a truly global
// fire-once function, so no matter how many times they visit this
// page, they'll only see the popup once per session.
const showExtensionDeprecation = _.once(($translate, dimInfoService) => {
  dimInfoService.show('extension-deprecated', {
    title: $translate.instant('Help.ExtensionDeprecatedTitle'),
    body: $translate.instant('Help.ExtensionDeprecatedMessage'),
    type: 'info'
  }, 0);
});

export default class ContentController {
  constructor(
    dimActivityTrackerService,
    dimState,
    ngDialog,
    $rootScope,
    loadingTracker,
    dimPlatformService,
    $interval,
    hotkeys,
    $timeout,
    dimStoreService,
    dimXurService,
    dimSettingsService,
    $window,
    $scope,
    $state,
    dimVendorService,
    $translate,
    dimInfoService
  ) {
    'ngInject';

    const vm = this;

    // Variables for templates that webpack does not automatically correct.
    vm.$DIM_VERSION = $DIM_VERSION;
    vm.$DIM_FLAVOR = $DIM_FLAVOR;
    vm.$DIM_CHANGELOG = $DIM_CHANGELOG;

    vm.loadingTracker = loadingTracker;
    vm.platforms = [];

    vm.platformChange = function platformChange(platform) {
      loadingTracker.addPromise(dimPlatformService.setActive(platform));
    };

    $scope.$on('dim-platforms-updated', (e, args) => {
      vm.platforms = args.platforms;
    });

    $scope.$on('dim-active-platform-updated', (e, args) => {
      dimState.active = vm.currentPlatform = args.platform;
    });

    loadingTracker.addPromise(dimPlatformService.getPlatforms());

    vm.settings = dimSettingsService;
    $scope.$watch(() => vm.settings.itemSize, (size) => {
      document.querySelector('html').style.setProperty("--item-size", `${size}px`);
    });
    $scope.$watch(() => vm.settings.charCol, (cols) => {
      document.querySelector('html').style.setProperty("--character-columns", cols);
    });
    $scope.$watch(() => vm.settings.vaultMaxCol, (cols) => {
      document.querySelector('html').style.setProperty("--vault-max-columns", cols);
    });
    if ($featureFlags.colorA11y) {
      $scope.$watch(() => vm.settings.colorA11y, (color) => {
        if (color && color !== '-') {
          document.querySelector('html').style.setProperty("--color-filter", `url(#${color.toLowerCase()})`);
        } else {
          document.querySelector('html').style.removeProperty("--color-filter");
        }
      });
    }

    vm.featureFlags = {
      vendorsEnabled: $featureFlags.vendorsEnabled,
      activities: $featureFlags.activities,
      colorA11y: $featureFlags.colorA11y
    };
    vm.vendorService = dimVendorService;

    hotkeys = hotkeys.bindTo($scope);

    hotkeys.add({
      combo: ['i'],
      description: $translate.instant('Hotkey.ToggleDetails'),
      callback: function() {
        $rootScope.$broadcast('dim-toggle-item-details');
      }
    });

    if ($featureFlags.tagsEnabled) {
      dimSettingsService.itemTags.forEach((tag) => {
        if (tag.hotkey) {
          hotkeys.add({
            combo: [tag.hotkey],
            description: $translate.instant('Hotkey.MarkItemAs', {
              tag: $translate.instant(tag.label)
            }),
            callback: function() {
              $rootScope.$broadcast('dim-item-tag', { tag: tag.type });
            }
          });
        }
      });
    }

    hotkeys.add({
      combo: ['ctrl+alt+shift+d'],
      callback: function() {
        dimState.debug = true;
        console.log("***** DIM DEBUG MODE ENABLED *****");
      }
    });

    /**
     * Show a popup dialog containing the given template. Its class
     * will be based on the name.
     */
    function showPopupFunction(name, template) {
      let result;
      return function(e) {
        e.stopPropagation();

        if (result) {
          result.close();
        } else {
          ngDialog.closeAll();
          result = ngDialog.open({
            template: template,
            className: name,
            appendClassName: 'modal-dialog'
          });

          result.closePromise.then(() => {
            result = null;
          });
        }
      };
    }

    vm.showSetting = showPopupFunction('settings', '<settings></settings>');
    vm.showAbout = showPopupFunction('about', aboutTemplate);
    vm.showSupport = showPopupFunction('support', supportTemplate);
    vm.showFilters = showPopupFunction('filters', filtersTemplate);
    vm.showXur = showPopupFunction('xur', '<xur></xur>');

    // TODO: make this into a ui-sref-toggle attribute directive
    vm.toggleState = function(name) {
      $state.go($state.is(name) ? 'inventory' : name);
    };

    vm.xur = dimXurService;

    // An abbreviated version of the messager from storage.component.js,
    // just so we can send an info popup.
    function messageHandler(event) {
      // We only accept messages from ourselves
      if (event.source !== window) {
        return;
      }

      switch (event.data.type) {
      case 'DIM_EXT_PONG':
        showExtensionDeprecation($translate, dimInfoService);
        break;
      }
    }

    window.addEventListener('message', messageHandler, false);
    window.postMessage({ type: 'DIM_EXT_PING' }, "*");

    $scope.$on('$destroy', () => {
      window.removeEventListener('message', messageHandler);
    });
  }
}
<|endoftext|>


<commit_before>'use strict';

/**
 * Controller for all registration routes and functions.
 */

const co = require('co');
const _ = require('lodash');
const logger = require('../services/logger');

const passport = require('../services/auth');
const session = require('./SessionController');
const api = require('../services/api-proxy');

/**
 * All available URLs for redirecting the user.
 * @type {*}
 */
const URLS = {
  LOGIN: '/login',
  REGISTER: '/register',
  PARTICIPANT: '/participant',
  SPONSOR: '/sponsor',
  SELECTION: '/selection',
  TEAM: '/team-create',
  INVITE: '/team-invite',
  TEAM_SUCCESS: '/team-success',
  INVITE_SUCCESS: '/invite-success',
  SPONSOR_SUCCESS: '/sponsor-success'
};

let registration = {};

/**
 * Sends the occurred error back to the client, and logs it to the bunyan global logger.
 * @param res
 * @param errMsg
 * @param err
 * @returns {*}
 */
const sendErr = (res, errMsg, err) => {

  if (err) logger.error(errMsg, err);
  else logger.error(errMsg);

  res.status(500).send({ error: errMsg });
};

/**
 * Timebased closing of registration
 * @param req
 * @param res
 * @param next
 */

registration.lock = function*(req, res, next) {
  const events = yield api.event.all();
  if (events.find(event => event.openForRegistration)) {
    next();
  } else {
    res.redirect('/closed');
  }
};

/**
 * POST route for /register
 * @param req
 * @param res
 */
registration.createUser = (req, res) => co(function*() {
  if (!req.body) return sendErr(res, 'The server did not receive any data.');

  yield api.createUser(req.body.email, req.body.password);
  const token = yield api.authenticate(req.body.email, req.body.password);
  const session = yield passport.createSession(req.body.email, token);

  // TODO: It's possible that login needs too long, so the error gets thrown after the user gets
  // redirected
  req.login(session, (error) => {
    if (error) throw error;
  });
  return res.send({
    nextUrl: URLS.SELECTION
  });

}).catch(err => {
  sendErr(res, err.message, err);
});

/**
 * POST route for /participant
 * @param req
 * @param res
 * @param next
 */
registration.createParticipant = (req, res, next) => co(function*() {
  if (!req.body) return sendErr(res, 'The server did not receive any data.');

  let updateBody = {};

  let data = {};

  if (req.body.firstname) data.firstname = req.body.firstname;
  if (req.body.lastname) data.lastname = req.body.lastname;
  if (req.body.gender) data.gender = req.body.gender;

  if (Object.keys(data).length > 0) {
    updateBody = data;
  }

  let participant = {};
  if (req.body.emergencynumber) participant.emergencynumber = req.body.emergencynumber;
  if (req.body.phonenumber) participant.phonenumber = req.body.phonenumber;
  if (req.body.tshirtsize) participant.tshirtsize = req.body.tshirtsize;
  if (req.body.hometown) participant.hometown = req.body.hometown;

  if (Object.keys(participant).length > 0) {
    updateBody.participant = participant;
  }

  logger.info('Trying to create / update a participant', updateBody);

  if (req.file) {
    logger.debug('Uploading updated profile picture for user to cloudinary');
    const res = yield api.uploadFile(req.file);
    updateBody.profilePic = {
      type: 'IMAGE',
      url: res.secure_url
    };
    logger.debug('Attaching updated team picture url to backend request');
  }

  const user = yield api.getCurrentUser(req.user);
  yield api.putModel('user', user.id, req.user, updateBody);

  logger.info('Created / updated a participant', updateBody);

  yield session.refreshSession(req);

  return res.send({
    nextURL: URLS.INVITE
  });

}).catch(err => {
  sendErr(res, err.message, err);
});

/**
 * POST route for /team-invite
 * @param req
 * @param res
 * @param next
 */
registration.joinTeamAPI = (req, res, next) => co(function*() {

  if (!req.body) return sendErr(res, 'Did not receive any data');

  let me = yield api.getCurrentUser(req.user);

  if (me.participant.teamId !== null) {
    yield api.postModel(`/event/${req.body.event}/team/leave/`, req.user, {});
  }

  const team = yield api.postModel(
    `/event/${req.body.event}/team/${req.body.team}/member/`,
    req.user,
    { email: me.email }
  );

  if (!team) return res.status(500).send({ error: 'Could not join team.' });

  yield session.refreshSession(req);

  return res.send({
    error: '',
    nextUrl: URLS.INVITE_SUCCESS
  });

}).catch(err => {
  sendErr(res, err.message, err);
});

/**
 * POST route for /team-create
 * @param req
 * @param res
 * @param next
 */
registration.createTeam = (req, res, next) => co(function*() {
  logger.info('Trying to create team for event', req.body.event, 'with name', req.body.teamname);

  let teamData = {
    name: req.body.teamname
  };

  if (req.file) {
    logger.info('Found picture for team in ', req.body.event, 'with name', req.body.teamname);
    teamData.profilePic = ['image'];
  }

  const team =
    yield api.postModel(`event/${req.body.event}/team/`, req.user, teamData);

  if (req.file) {
    // TODO: This is not updated for cloudinary now because it is dead code that needs to be removed at some point
    yield api.uploadFile(req.file, team.profilePic);
  }

  logger.info('Created Team', req.body.teamname, 'for event', req.body.event);

  if (req.body.email) {
    logger.info('Trying to invite user', req.body.email, 'to team', team.id);

    const invite = yield api.inviteUser(req.user, req.body.event, team.id, req.body.email);

    if (!invite) return sendErr(res, 'Invite creation failed!');

    logger.info('Created Invitation for user', req.body.email, 'to team', team.id);
  }

  yield session.refreshSession(req);

  res.send({
    nextURL: URLS.TEAM_SUCCESS
  });
}).catch(err => {
  sendErr(res, err.message, err);
});

/**
 * POST route for /invite
 * @param req
 * @param res
 */
registration.inviteUser = (req, res) => co(function*() {

  const me = yield api.getCurrentUser(req.user);

  if (!me.participant) {
    return res.status(500).send({ error: 'User is not a participant!' });
  }

  const invite =
    yield api.inviteUser(req.user, me.participant.eventId, me.participant.teamId, req.body.email);

  if (invite) {
    res.send({ error: '' });
  } else {
    return res.status(500).send({ error: 'Invite creation failed!' });
  }

}).catch(err => {
  sendErr(res, err.message, err);
});

/**
 * Generates the transaction purpose for the given user.
 * @param user
 */
registration.getTransactionPurposeByUser = (user) => co(function*() {
  return `${user.participant.teamId}-BO16-${user.firstname}-${user.lastname}`
    .replace('ä', 'ae').replace('ü', 'ue').replace('ö', 'oe')
    .replace('Ä', 'Ae').replace('Ü', 'Ue').replace('Ö', 'Oe')
    .replace('ß', 'ss').replace(/[^A-Za-z0-9-]/, '')
    .substring(0, 140);
}).catch((ex) => {
  throw ex;
});

/**
 * Gets the transaction purpose.
 * @param req
 */
registration.getTransactionPurpose = (req) => co(function*() {
  const me = yield api.getCurrentUser(req.user);
  return yield registration.getTransactionPurposeByUser(me);
}).catch(ex => {
  throw ex;
});

/**
 * Activates the given token and refreshes our user session.
 * @param token
 */
registration.activateUser = (token) => co(function*() {
  yield api.activateUser(token);
}).catch(ex => {
  throw ex;
});

/**
 * Returns an invite for a provided token.
 * @param token
 */
registration.getInviteByToken = (token) => co(function*() {
  return yield api.getInviteByToken(token);
}).catch(ex => {
  throw ex;
});

/**
 * Returns all invites for the currently logged in user, req.user.
 * @param req
 */
registration.getInvites = (req) => co(function*() {
  logger.info('Trying to get all invites for', req.user);

  const events = yield registration.getEvents(req);

  let allInvites = yield events.map((e) => api.getModel(`/event/${e.id}/team/invitation/`, req.user));

  //ADD CITY
  allInvites = allInvites.map((invites, index) => {
    return invites.map(invite => {
      invite.city = events[index].city;
      invite.event = events[index].id;
      return invite;
    });
  });

  //FLATTEN ARRAY
  allInvites = [].concat.apply([], allInvites); // TODO: Maybe _.flatMap

  //REMOVE DUPLICATES
  allInvites = _.uniq(allInvites);

  logger.info('Got all Invites for user', req.user);

  return allInvites;
}).catch(ex => {
  throw ex;
});

/**
 * Returns all events for the logged in user.
 * @param req
 */
registration.getEvents = (req) => co(function*() {
  logger.info('Trying to get all events');

  const events = yield api.getModel('event', req.user);

  if (!events.length) {
    throw new Error('No events');
  }

  logger.info('Got all events');

  return events;
}).catch(ex => {
  throw ex;
});

/**
 * POST route for /request-pw-reset
 * @param req
 * @param res
 */
registration.requestPwReset = (req, res) => co(function*() {
  const reset = yield api.pwreset.requestPwReset(req.body.email);

  if (reset) {
    res.send({ error: '' });
  } else {
    return res.status(500).send({ error: 'Request password reset failed!' });
  }

}).catch(err => {
  sendErr(res, err.message, err);
});

/**
 * POST route for /reset-pw
 * @param req
 * @param res
 */
registration.resetPassword = (req, res) => co(function*() {
  const reset = yield api.pwreset.resetPassword(req.body.email, req.body.token, req.body.password);

  if (reset) {
    return res.send({
      success: 'Successfully reset password, you are now able to login with your new password.
Login Now!'
    });
  } else {
    return res.status(500).send({ error: 'Password reset failed!' });
  }

}).catch(err => {
  sendErr(res, err.message, err);
});

module.exports = registration;
<commit_msg>Allowing the user to still register as long as there is still an invitation for them<commit_after>'use strict';

/**
 * Controller for all registration routes and functions.
 */

const co = require('co');
const _ = require('lodash');
const logger = require('../services/logger');

const passport = require('../services/auth');
const session = require('./SessionController');
const api = require('../services/api-proxy');

/**
 * All available URLs for redirecting the user.
 * @type {*}
 */
const URLS = {
  LOGIN: '/login',
  REGISTER: '/register',
  PARTICIPANT: '/participant',
  SPONSOR: '/sponsor',
  SELECTION: '/selection',
  TEAM: '/team-create',
  INVITE: '/team-invite',
  TEAM_SUCCESS: '/team-success',
  INVITE_SUCCESS: '/invite-success',
  SPONSOR_SUCCESS: '/sponsor-success'
};

let registration = {};

/**
 * Sends the occurred error back to the client, and logs it to the bunyan global logger.
 * @param res
 * @param errMsg
 * @param err
 * @returns {*}
 */
const sendErr = (res, errMsg, err) => {

  if (err) logger.error(errMsg, err);
  else logger.error(errMsg);

  res.status(500).send({ error: errMsg });
};

/**
 * Timebased closing of registration
 * @param req
 * @param res
 * @param next
 */

registration.lock = function*(req, res, next) {
  const events = yield api.event.all();
  if (events.find(event => event.openForRegistration)) {
    next();
    return;
  }

  const time = new Date().getTime();
  const currentEventIds = events.filter((event) => event.date * 1000 > time).map((event) => event.id);
  const teams = yield registration.getInvites(req);
  const currentTeams = teams.filter((team) => currentEventIds.indexOf(team.event) >= 0);

  if (currentTeams.length > 0) {
    next();
    return;
  }

  res.redirect('/closed');
};

/**
 * POST route for /register
 * @param req
 * @param res
 */
registration.createUser = (req, res) => co(function*() {
  if (!req.body) return sendErr(res, 'The server did not receive any data.');

  yield api.createUser(req.body.email, req.body.password);
  const token = yield api.authenticate(req.body.email, req.body.password);
  const session = yield passport.createSession(req.body.email, token);

  // TODO: It's possible that login needs too long, so the error gets thrown after the user gets
  // redirected
  req.login(session, (error) => {
    if (error) throw error;
  });
  return res.send({
    nextUrl: URLS.SELECTION
  });

}).catch(err => {
  sendErr(res, err.message, err);
});

/**
 * POST route for /participant
 * @param req
 * @param res
 * @param next
 */
registration.createParticipant = (req, res, next) => co(function*() {
  if (!req.body) return sendErr(res, 'The server did not receive any data.');

  let updateBody = {};

  let data = {};

  if (req.body.firstname) data.firstname = req.body.firstname;
  if (req.body.lastname) data.lastname = req.body.lastname;
  if (req.body.gender) data.gender = req.body.gender;

  if (Object.keys(data).length > 0) {
    updateBody = data;
  }

  let participant = {};
  if (req.body.emergencynumber) participant.emergencynumber = req.body.emergencynumber;
  if (req.body.phonenumber) participant.phonenumber = req.body.phonenumber;
  if (req.body.tshirtsize) participant.tshirtsize = req.body.tshirtsize;
  if (req.body.hometown) participant.hometown = req.body.hometown;

  if (Object.keys(participant).length > 0) {
    updateBody.participant = participant;
  }

  logger.info('Trying to create / update a participant', updateBody);

  if (req.file) {
    logger.debug('Uploading updated profile picture for user to cloudinary');
    const res = yield api.uploadFile(req.file);
    updateBody.profilePic = {
      type: 'IMAGE',
      url: res.secure_url
    };
    logger.debug('Attaching updated team picture url to backend request');
  }

  const user = yield api.getCurrentUser(req.user);
  yield api.putModel('user', user.id, req.user, updateBody);

  logger.info('Created / updated a participant', updateBody);

  yield session.refreshSession(req);

  return res.send({
    nextURL: URLS.INVITE
  });

}).catch(err => {
  sendErr(res, err.message, err);
});

/**
 * POST route for /team-invite
 * @param req
 * @param res
 * @param next
 */
registration.joinTeamAPI = (req, res, next) => co(function*() {

  if (!req.body) return sendErr(res, 'Did not receive any data');

  let me = yield api.getCurrentUser(req.user);

  if (me.participant.teamId !== null) {
    yield api.postModel(`/event/${req.body.event}/team/leave/`, req.user, {});
  }

  const team = yield api.postModel(
    `/event/${req.body.event}/team/${req.body.team}/member/`,
    req.user,
    { email: me.email }
  );

  if (!team) return res.status(500).send({ error: 'Could not join team.' });

  yield session.refreshSession(req);

  return res.send({
    error: '',
    nextUrl: URLS.INVITE_SUCCESS
  });

}).catch(err => {
  sendErr(res, err.message, err);
});

/**
 * POST route for /team-create
 * @param req
 * @param res
 * @param next
 */
registration.createTeam = (req, res, next) => co(function*() {
  logger.info('Trying to create team for event', req.body.event, 'with name', req.body.teamname);

  let teamData = {
    name: req.body.teamname
  };

  if (req.file) {
    logger.info('Found picture for team in ', req.body.event, 'with name', req.body.teamname);
    teamData.profilePic = ['image'];
  }

  const team =
    yield api.postModel(`event/${req.body.event}/team/`, req.user, teamData);

  if (req.file) {
    // TODO: This is not updated for cloudinary now because it is dead code that needs to be removed at some point
    yield api.uploadFile(req.file, team.profilePic);
  }

  logger.info('Created Team', req.body.teamname, 'for event', req.body.event);

  if (req.body.email) {
    logger.info('Trying to invite user', req.body.email, 'to team', team.id);

    const invite = yield api.inviteUser(req.user, req.body.event, team.id, req.body.email);

    if (!invite) return sendErr(res, 'Invite creation failed!');

    logger.info('Created Invitation for user', req.body.email, 'to team', team.id);
  }

  yield session.refreshSession(req);

  res.send({
    nextURL: URLS.TEAM_SUCCESS
  });
}).catch(err => {
  sendErr(res, err.message, err);
});

/**
 * POST route for /invite
 * @param req
 * @param res
 */
registration.inviteUser = (req, res) => co(function*() {

  const me = yield api.getCurrentUser(req.user);

  if (!me.participant) {
    return res.status(500).send({ error: 'User is not a participant!' });
  }

  const invite =
    yield api.inviteUser(req.user, me.participant.eventId, me.participant.teamId, req.body.email);

  if (invite) {
    res.send({ error: '' });
  } else {
    return res.status(500).send({ error: 'Invite creation failed!' });
  }

}).catch(err => {
  sendErr(res, err.message, err);
});

/**
 * Generates the transaction purpose for the given user.
 * @param user
 */
registration.getTransactionPurposeByUser = (user) => co(function*() {
  return `${user.participant.teamId}-BO16-${user.firstname}-${user.lastname}`
    .replace('ä', 'ae').replace('ü', 'ue').replace('ö', 'oe')
    .replace('Ä', 'Ae').replace('Ü', 'Ue').replace('Ö', 'Oe')
    .replace('ß', 'ss').replace(/[^A-Za-z0-9-]/, '')
    .substring(0, 140);
}).catch((ex) => {
  throw ex;
});

/**
 * Gets the transaction purpose.
 * @param req
 */
registration.getTransactionPurpose = (req) => co(function*() {
  const me = yield api.getCurrentUser(req.user);
  return yield registration.getTransactionPurposeByUser(me);
}).catch(ex => {
  throw ex;
});

/**
 * Activates the given token and refreshes our user session.
 * @param token
 */
registration.activateUser = (token) => co(function*() {
  yield api.activateUser(token);
}).catch(ex => {
  throw ex;
});

/**
 * Returns an invite for a provided token.
 * @param token
 */
registration.getInviteByToken = (token) => co(function*() {
  return yield api.getInviteByToken(token);
}).catch(ex => {
  throw ex;
});

/**
 * Returns all invites for the currently logged in user, req.user.
 * @param req
 */
registration.getInvites = (req) => co(function*() {
  logger.info('Trying to get all invites for', req.user);

  const events = yield registration.getEvents(req);

  let allInvites = yield events.map((e) => api.getModel(`/event/${e.id}/team/invitation/`, req.user));

  //ADD CITY
  allInvites = allInvites.map((invites, index) => {
    return invites.map(invite => {
      invite.city = events[index].city;
      invite.event = events[index].id;
      return invite;
    });
  });

  //FLATTEN ARRAY
  allInvites = [].concat.apply([], allInvites); // TODO: Maybe _.flatMap

  //REMOVE DUPLICATES
  allInvites = _.uniq(allInvites);

  logger.info('Got all Invites for user', req.user);

  return allInvites;
}).catch(ex => {
  throw ex;
});

/**
 * Returns all events for the logged in user.
 * @param req
 */
registration.getEvents = (req) => co(function*() {
  logger.info('Trying to get all events');

  const events = yield api.getModel('event', req.user);

  if (!events.length) {
    throw new Error('No events');
  }

  logger.info('Got all events');

  return events;
}).catch(ex => {
  throw ex;
});

/**
 * POST route for /request-pw-reset
 * @param req
 * @param res
 */
registration.requestPwReset = (req, res) => co(function*() {
  const reset = yield api.pwreset.requestPwReset(req.body.email);

  if (reset) {
    res.send({ error: '' });
  } else {
    return res.status(500).send({ error: 'Request password reset failed!' });
  }

}).catch(err => {
  sendErr(res, err.message, err);
});

/**
 * POST route for /reset-pw
 * @param req
 * @param res
 */
registration.resetPassword = (req, res) => co(function*() {
  const reset = yield api.pwreset.resetPassword(req.body.email, req.body.token, req.body.password);

  if (reset) {
    return res.send({
      success: 'Successfully reset password, you are now able to login with your new password.
Login Now!'
    });
  } else {
    return res.status(500).send({ error: 'Password reset failed!' });
  }

}).catch(err => {
  sendErr(res, err.message, err);
});

module.exports = registration;
<|endoftext|>


<commit_before>const qs = require('query-string');
const url = require('url');
const QuickLRU = require('quick-lru');
const _ = require('lodash');

const { fetch } = require('../../util');
const logger = require('../logger');
const config = require('../../config');
const { validatorGconvert } = require('./gprofiler');

const pcCache = new QuickLRU({ maxSize: config.PC_CACHE_MAX_SIZE });

const fetchOptions = {
  method: 'GET',
  headers: {
    'Accept': 'application/json'
  }
};

//Pathway Commons HTTP GET request; options.cmd = 'pc2/get', 'pc2/search', 'pc2/traverse', 'pc2/graph', etc.
let query = opts => {
  let queryOpts = _.assign( { user: 'app-ui', cmd: 'pc2/get' }, opts);
  let { cmd } = queryOpts;
  let url = config.PC_URL + cmd + '?' + qs.stringify( queryOpts );

  return fetch(url, fetchOptions)
    .then(res => ( cmd === 'pc2/get' || cmd === 'pc2/graph' ? res.text() : res.json() ) )
    .catch((e) => {
      logger.error('query ' + queryOpts + ' failed - ' + e);
      return null;
    });
};

let sanitize = s => {
  // Escape (with '\'), to treat them literally, symbols, such as '*', ':', or space,
  // which otherwise play special roles in a Lucene query string.
  return s.replace(/([!*+\-&|()[\]{}^~?:/\\"\s])/g, '\\$1');
};

// recognize biological entities from an input string
let extractEntityIds = inputString => {
  let tokens = inputString.split(' ');

  return validatorGconvert( tokens ).then( result => {
    let { unrecognized, alias  } = result;
    let entities = _.keys( alias ).map( initialAlias => 'xrefid:' + sanitize( initialAlias.toUpperCase() ) );

    let otherIds = unrecognized.map( id => {
      id = id.toUpperCase();
      let isChebiId = /^CHEBI:\d+$/.test( id );
      let isSmpdbId = /^SMP\d{5}$/.test( id );
      let recognized = isSmpdbId || isChebiId && ( id.length <= ("CHEBI:".length + 6) );
      let sanitized = sanitize(id);

      return recognized ? ( 'xrefid:' + sanitized ) : ( 'name:' + '*' + sanitized + '*' );
    });

    return entities.concat(otherIds);
  });
};

// generate three search query candidates: the first one is the fastest, the last - slowest
let generateSearchQueries = inputString => {
  let phrase = sanitize( inputString );

  return extractEntityIds( inputString ).then( entities => {
    return [
      '(name:' + phrase + ') OR (' + 'name:*' + phrase + '*) OR (' + entities.join(' AND ') + ')',
      '(' + entities.join(' OR ') + ')',
      inputString //"as is" (won't additionally escape Lucene query syntax, spaces, etc.)
    ];
  });
};

// A fine-tuned PC search to improve relevance of full-text search and filter out unwanted hits.
// The argument (query object) has the following fields:
//  - q: user input - search query string
//  - type: BioPAX type to match/filter by
//  - lt: max graph size result returned
//  - gt: min graph size result returned
let search = async opts => {
  let { gt:minSize = 0, lt:maxSize = 250, q } = opts;
  let queryStrings = await generateSearchQueries( q.trim() );

  for( let queryString of queryStrings ) {
    let queryOpts = _.assign( opts, { cmd: 'pc2/search', q: queryString } );
    let searchResult = await query( queryOpts );
    let searchResults = _.get( searchResult, 'searchHit', []);

    if( searchResults.length > 0 ){
      let filteredResults = searchResults.filter( result => {
        let size = _.get( result, 'numParticipants', 0);

        return minSize < size && size < maxSize;
      });

      if (filteredResults.length > 0) {
        return filteredResults;
      }
    }
  }

  return [];
};

const sifGraph = opts => {
  let hasMultipleSources = _.get(opts, 'source', []).length > 1;
  let sifGraphType = hasMultipleSources ? 'pathsbetween' : 'neighborhood';
  let queryOpts = Object.assign( {}, opts, {
    limit: 1,
    pattern: ['CONTROLS_STATE_CHANGE_OF','CONTROLS_TRANSPORT_OF','CONTROLS_EXPRESSION_OF','CATALYSIS_PRECEDES','INTERACTS_WITH'],
    directed: hasMultipleSources ? 'false' : undefined,
    direction: hasMultipleSources ? undefined : 'UNDIRECTED'
  });
  let url = config.PC_URL + 'sifgraph/v1/' + sifGraphType + '?' + qs.stringify( queryOpts );

  return fetch( url, { method: 'GET', headers: { 'Accept': 'text/plain' } } )
  .then( res => res.text() )
  .catch( e => {
    logger.error( 'sifGraph ' + opts + ' failed - ' + e );
    throw e;
  });
};

const handleEntityUriResponse = text => {
  const uri = new url.URL( text ); // Throws TypeError
  const pathParts = _.compact( uri.pathname.split('/') );
  if( _.isEmpty( pathParts ) || pathParts.length !== 2 ) throw new Error( 'Unrecognized URI' );
  const namespace = _.head( pathParts );
  return {
    origin: uri.origin,
    namespace
  };
};

const constructQueryPath = ( name, localId ) => {
  // Edge case - localId has periods e.g. 'enzyme nomenclature/6.1.1.5' gotta add a trailing slash
  const suffix = /\./.test( localId ) ? '/' : '';
  return name + '/' + localId + suffix;
};

/* fetchEntityUriBase
 * Light wrapper around the pc2 service to get the uri given a collection name and local ID for entity
 * http://www.pathwaycommons.org/pc2/swagger-ui.html#!/metadata45controller/identifierOrgUriUsingGET
 * NB: pc2 service returns 200 and empty body if collection name and/or local ID are unrecognized.
 *   If the local ID is empty, throws a 404
 * @return { object } the URL origin and namespace
 */
const fetchEntityUriBase = ( name, localId ) => {
  const url = config.PC_URL + 'pc2/miriam/uri/' + constructQueryPath( name, localId ) ;
  return fetch( url , { method: 'GET', headers: { 'Accept': 'text/plain' } })
    .then( res => res.text() )
    .then( handleEntityUriResponse );
};

const getEntityUriParts = ( name, localId ) => {
  if( pcCache.has( name ) ){
    return pcCache.get( name );
  } else {
    let res = fetchEntityUriBase( name, localId );
    pcCache.set( name, res );
    res.catch( err => {
      pcCache.del( name );
      logger.error(`Failed to fill cache with ${name} and ${localId} - ${err}`);
    });
    return res;
  }
};

/*
 * xref2Uri: Obtain the URI for an xref
 * @param {string} name -  MIRIAM 'name', 'synonym' ?OR MI CV database citation (MI:0444) 'label'
 * @param {string} localId - Entity local entity identifier, should be valid
 * @return {Object} return the origin and 'namespace' in path
 */
const xref2Uri =  ( name, localId ) => {
  return getEntityUriParts( name, localId )
    .then( uriParts => ({
      uri: uriParts.origin + '/' + uriParts.namespace + '/' + localId,
      namespace: uriParts.namespace
    }) );
};

module.exports = { query, search: _.memoize( search, query => JSON.stringify( query ) ), sifGraph, xref2Uri };<commit_msg>add catch for gconvert fetch errors and return an empty list of recognized entities<commit_after>const qs = require('query-string');
const url = require('url');
const QuickLRU = require('quick-lru');
const _ = require('lodash');

const { fetch } = require('../../util');
const logger = require('../logger');
const config = require('../../config');
const { validatorGconvert } = require('./gprofiler');

const pcCache = new QuickLRU({ maxSize: config.PC_CACHE_MAX_SIZE });

const fetchOptions = {
  method: 'GET',
  headers: {
    'Accept': 'application/json'
  }
};

//Pathway Commons HTTP GET request; options.cmd = 'pc2/get', 'pc2/search', 'pc2/traverse', 'pc2/graph', etc.
let query = opts => {
  let queryOpts = _.assign( { user: 'app-ui', cmd: 'pc2/get' }, opts);
  let { cmd } = queryOpts;
  let url = config.PC_URL + cmd + '?' + qs.stringify( queryOpts );

  return fetch(url, fetchOptions)
    .then(res => ( cmd === 'pc2/get' || cmd === 'pc2/graph' ? res.text() : res.json() ) )
    .catch((e) => {
      logger.error('query ' + queryOpts + ' failed - ' + e);
      return null;
    });
};

let sanitize = s => {
  // Escape (with '\'), to treat them literally, symbols, such as '*', ':', or space,
  // which otherwise play special roles in a Lucene query string.
  return s.replace(/([!*+\-&|()[\]{}^~?:/\\"\s])/g, '\\$1');
};

// recognize biological entities from an input string
let extractEntityIds = inputString => {
  let tokens = inputString.split(' ');

  return validatorGconvert( tokens ).then( result => {
    let { unrecognized, alias  } = result;
    let entities = _.keys( alias ).map( initialAlias => 'xrefid:' + sanitize( initialAlias.toUpperCase() ) );

    let otherIds = unrecognized.map( id => {
      id = id.toUpperCase();
      let isChebiId = /^CHEBI:\d+$/.test( id );
      let isSmpdbId = /^SMP\d{5}$/.test( id );
      let recognized = isSmpdbId || isChebiId && ( id.length <= ("CHEBI:".length + 6) );
      let sanitized = sanitize(id);

      return recognized ? ( 'xrefid:' + sanitized ) : ( 'name:' + '*' + sanitized + '*' );
    });

    return entities.concat(otherIds);
  })
  .catch( e => {
    logger.error('unable to get response from gconvert with the following inputstring: ' + inputString);
    logger.error(e);
    return [];
  });
};

// generate three search query candidates: the first one is the fastest, the last - slowest
let generateSearchQueries = async inputString => {
  let phrase = sanitize( inputString );
  let entities = await extractEntityIds( inputString );
  return [
    '(name:' + phrase + ') OR (' + 'name:*' + phrase + '*) OR (' + entities.join(' AND ') + ')',
    '(' + entities.join(' OR ') + ')',
    inputString //"as is" (won't additionally escape Lucene query syntax, spaces, etc.)
  ];
};

// A fine-tuned PC search to improve relevance of full-text search and filter out unwanted hits.
// The argument (query object) has the following fields:
//  - q: user input - search query string
//  - type: BioPAX type to match/filter by
//  - lt: max graph size result returned
//  - gt: min graph size result returned
let search = async opts => {
  let { gt:minSize = 0, lt:maxSize = 250, q } = opts;
  let queryStrings = await generateSearchQueries( q.trim() );

  for( let queryString of queryStrings ) {
    let queryOpts = _.assign( opts, { cmd: 'pc2/search', q: queryString } );
    let searchResult = await query( queryOpts );
    let searchResults = _.get( searchResult, 'searchHit', []);

    if( searchResults.length > 0 ){
      let filteredResults = searchResults.filter( result => {
        let size = _.get( result, 'numParticipants', 0);

        return minSize < size && size < maxSize;
      });

      if (filteredResults.length > 0) {
        return filteredResults;
      }
    }
  }

  return [];
};

const sifGraph = opts => {
  let hasMultipleSources = _.get(opts, 'source', []).length > 1;
  let sifGraphType = hasMultipleSources ? 'pathsbetween' : 'neighborhood';
  let queryOpts = Object.assign( {}, opts, {
    limit: 1,
    pattern: ['CONTROLS_STATE_CHANGE_OF','CONTROLS_TRANSPORT_OF','CONTROLS_EXPRESSION_OF','CATALYSIS_PRECEDES','INTERACTS_WITH'],
    directed: hasMultipleSources ? 'false' : undefined,
    direction: hasMultipleSources ? undefined : 'UNDIRECTED'
  });
  let url = config.PC_URL + 'sifgraph/v1/' + sifGraphType + '?' + qs.stringify( queryOpts );

  return fetch( url, { method: 'GET', headers: { 'Accept': 'text/plain' } } )
  .then( res => res.text() )
  .catch( e => {
    logger.error( 'sifGraph ' + opts + ' failed - ' + e );
    throw e;
  });
};

const handleEntityUriResponse = text => {
  const uri = new url.URL( text ); // Throws TypeError
  const pathParts = _.compact( uri.pathname.split('/') );
  if( _.isEmpty( pathParts ) || pathParts.length !== 2 ) throw new Error( 'Unrecognized URI' );
  const namespace = _.head( pathParts );
  return {
    origin: uri.origin,
    namespace
  };
};

const constructQueryPath = ( name, localId ) => {
  // Edge case - localId has periods e.g. 'enzyme nomenclature/6.1.1.5' gotta add a trailing slash
  const suffix = /\./.test( localId ) ? '/' : '';
  return name + '/' + localId + suffix;
};

/* fetchEntityUriBase
 * Light wrapper around the pc2 service to get the uri given a collection name and local ID for entity
 * http://www.pathwaycommons.org/pc2/swagger-ui.html#!/metadata45controller/identifierOrgUriUsingGET
 * NB: pc2 service returns 200 and empty body if collection name and/or local ID are unrecognized.
 *   If the local ID is empty, throws a 404
 * @return { object } the URL origin and namespace
 */
const fetchEntityUriBase = ( name, localId ) => {
  const url = config.PC_URL + 'pc2/miriam/uri/' + constructQueryPath( name, localId ) ;
  return fetch( url , { method: 'GET', headers: { 'Accept': 'text/plain' } })
    .then( res => res.text() )
    .then( handleEntityUriResponse );
};

const getEntityUriParts = ( name, localId ) => {
  if( pcCache.has( name ) ){
    return pcCache.get( name );
  } else {
    let res = fetchEntityUriBase( name, localId );
    pcCache.set( name, res );
    res.catch( err => {
      pcCache.del( name );
      logger.error(`Failed to fill cache with ${name} and ${localId} - ${err}`);
    });
    return res;
  }
};

/*
 * xref2Uri: Obtain the URI for an xref
 * @param {string} name -  MIRIAM 'name', 'synonym' ?OR MI CV database citation (MI:0444) 'label'
 * @param {string} localId - Entity local entity identifier, should be valid
 * @return {Object} return the origin and 'namespace' in path
 */
const xref2Uri =  ( name, localId ) => {
  return getEntityUriParts( name, localId )
    .then( uriParts => ({
      uri: uriParts.origin + '/' + uriParts.namespace + '/' + localId,
      namespace: uriParts.namespace
    }) );
};

module.exports = { query, search: _.memoize( search, query => JSON.stringify( query ) ), sifGraph, xref2Uri };<|endoftext|>


<commit_before><commit_msg>add JSON quotes x10<commit_after>var quotes = [{
  "quote": "The pistachio: it's just like our politics. When the two sides are divided, that's when the nuts come out.",
  "source": "Stephen Colbert"
}, {
  "quote": "People usually consider walking on water or in thin air a miracle. But I think the real miracle is not to walk on water or in thin air, but to walk on earth.",
  "source": "Thich Nhat Hanh"
}, {
  "quote": "All that is gold does not glitter; not all those who wander are lost; the old that is strong does not wither; deep roots are not reached by the frost.",
  "source": "J.R.R. Tolkien"
}, {
  "quote": "Parrots make great pets. They have more personality than goldfish.",
  "source": "Chevy Chase"
}, {
  "quote": "Dying is a wild night and a new road.",
  "source": "Emily Dickinson"
}, {
  "quote": "Always remember that you are absolutely unique. Just like everyone else.",
  "source": "Margaret Mead"
}, {
  "quote": "Life isn't something you possess. It's something you take part in, and you witness.",
  "source": "Louis C. K."
}, {
  "quote": "Kia mate ururoa, kei mate wheke. (Fight like a shark, don’t give in like an octopus.)",
  "source": "Maori Proverb"
}, {
  "quote": "Ahakoa iti, he pounamu. (Although it is small, it is of greenstone.)",
  "source": "Maori Proverb"
}, {
  "quote": "To infinity, and beyond!",
  "source": "Buzz Lightyear"
}]

function newQuote() {

}<|endoftext|>


<commit_before>/* globals module */
/**
 * @module baasicApplicationSettingsRouteService
 * @description Baasic Application Settings Route Service provides Baasic route templates which can be expanded to Baasic REST URIs. Various services can use Baasic Application Settings Route Service to obtain a needed routes while other routes will be obtained through HAL. By convention, all route services  use the same function names as their corresponding services.
*/
(function (angular, module, undefined) {
    'use strict';
    module.service('baasicApplicationSettingsRouteService', ['baasicUriTemplateService',
        function (uriTemplateService) {
            return {
                /**
                * Parses get route; this route doesn't expose any properties.
                * @method
                * @example baasicApplicationSettingsRouteService.get.expand({});
                **/
                get: uriTemplateService.parse('applications/{?embed,fields}'),
                /**
                * Parses update route; this route doesn't expose any properties.
                * @method
                * @example baasicApplicationSettingsRouteService.update.expand({});
                **/
                update: uriTemplateService.parse('applications/'),
                /**
                * Parses and expands URI templates based on [RFC6570](http://tools.ietf.org/html/rfc6570) specifications. For more information please visit the project [GitHub](https://github.com/Baasic/uritemplate-js) page.
                * @method
                * @example baasicApplicationSettingsRouteService.parse('<route>/{?embed,fields,options}').expand({embed: '<embedded-resource>'});
                **/
                parse: uriTemplateService.parse
            };
        }]);
}(angular, module));
/**
 * @copyright (c) 2015 Mono
 * @license MIT
 * @author Mono
 * @overview
 ***Notes:**
 - Refer to the [REST API documentation](https://github.com/Baasic/baasic-rest-api/wiki) for detailed information about available Baasic REST API end-points.
 - [URI Template](https://github.com/Baasic/uritemplate-js) syntax enables expanding the Baasic route templates to Baasic REST URIs providing it with an object that contains URI parameters.
 - All end-point objects are transformed by the associated route service.
*/
<commit_msg>Route Services - changed example output<commit_after>/* globals module */
/**
 * @module baasicApplicationSettingsRouteService
 * @description Baasic Application Settings Route Service provides Baasic route templates which can be expanded to Baasic REST URIs. Various services can use Baasic Application Settings Route Service to obtain a needed routes while other routes will be obtained through HAL. By convention, all route services  use the same function names as their corresponding services.
*/
(function (angular, module, undefined) {
    'use strict';
    module.service('baasicApplicationSettingsRouteService', ['baasicUriTemplateService',
        function (uriTemplateService) {
            return {
                /**
                * Parses get route; this route doesn't expose any properties.
                * @method
                * @example baasicApplicationSettingsRouteService.get.expand({});
                **/
                get: uriTemplateService.parse('applications/{?embed,fields}'),
                /**
                * Parses update route; this route doesn't expose any properties.
                * @method
                * @example baasicApplicationSettingsRouteService.update.expand({});
                **/
                update: uriTemplateService.parse('applications/'),
                /**
                * Parses and expands URI templates based on [RFC6570](http://tools.ietf.org/html/rfc6570) specifications. For more information please visit the project [GitHub](https://github.com/Baasic/uritemplate-js) page.
                * @method
                * @example
baasicApplicationSettingsRouteService.parse(
	'<route>/{?embed,fields,options}'
).expand(
	{embed: '<embedded-resource>'}
);
                **/
                parse: uriTemplateService.parse
            };
        }]);
}(angular, module));
/**
 * @copyright (c) 2015 Mono
 * @license MIT
 * @author Mono
 * @overview
 ***Notes:**
 - Refer to the [REST API documentation](https://github.com/Baasic/baasic-rest-api/wiki) for detailed information about available Baasic REST API end-points.
 - [URI Template](https://github.com/Baasic/uritemplate-js) syntax enables expanding the Baasic route templates to Baasic REST URIs providing it with an object that contains URI parameters.
 - All end-point objects are transformed by the associated route service.
*/
<|endoftext|>