<commit_before>/******************************************************************************************************************************
COMMING SOON PAGE
*******************************************************************************************************************************/
(function($) {
    /**
    * Set your date here  (YEAR, MONTH (0 for January/11 for December), DAY, HOUR, MINUTE, SECOND)
    * according to the GMT+0 Timezone
    **/
    var launch = new Date(2015, 10, 10, 0, 00);
    /**
    * The script
    **/
    var message = $('#message');
    var days = $('#days');
    var hours = $('#hours');
    var minutes = $('#minutes');
    var seconds = $('#seconds');

    setDate();
    function setDate(){
        var now = new Date();
        if( launch < now ){
            days.html('
0

Day
');
            hours.html('
0

Hour
');
            minutes.html('
0

Minute
');
            seconds.html('
0

Second
');
            // message.html('OUR SITE IS NOT READY YET...');
        }
        else{
            var s = -now.getTimezoneOffset()*60 + (launch.getTime() - now.getTime())/1000;
            var d = Math.floor(s/86400);
            days.html('
'+d+'

Day'+(d>1?'s':''),'
');
            s -= d*86400;

            var h = Math.floor(s/3600);
            hours.html('
'+h+'

Hour'+(h>1?'s':''),'
');
            s -= h*3600;

            var m = Math.floor(s/60);
            minutes.html('
'+m+'

Minute'+(m>1?'s':''),'
');

            s = Math.floor(s-m*60);
            seconds.html('
'+s+'

Second'+(s>1?'s':''),'
');
            setTimeout(setDate, 1000);

            // message.html('OUR SITE IS NOT READY YET, BUT WE ARE COMING SOON');
        }
    }
})(jQuery);
/******************************************************************************************************************************
ANIMATIONS
*******************************************************************************************************************************/
(function($) {
    "use strict";
    var isMobile = false;
    if (navigator.userAgent.match(/Android/i) ||
        navigator.userAgent.match(/webOS/i) ||
        navigator.userAgent.match(/iPhone/i) ||
        navigator.userAgent.match(/iPad/i)||
        navigator.userAgent.match(/iPod/i) ||
        navigator.userAgent.match(/BlackBerry/i)) {
        isMobile = true;
    }
    if (isMobile == true) {
        $('body').removeClass('nomobile');
        $('.animated').removeClass('animated');
    }
    $(function() {
        if (isMobile == false) {
            $('*[data-animated]').addClass('animated');
        }
        function animated_contents() {
            $(".animated:appeared").each(function (i) {
                var $this    = $(this),
                    animated = $(this).data('animated');

                setTimeout(function () {
                    $this.addClass(animated);
                }, 50 * i);
            });
        }
        animated_contents();
        $(window).scroll(function () {
            animated_contents();
        });
    });
})(jQuery);
/******************************************************************************************************************************
SLIDER
*******************************************************************************************************************************/
(function($) {
    "use strict";
    $("body.nomobile #slider").revolution(
    {
            delay:9000,
            startheight:450,
            startwidth:890,

            thumbWidth:100,
            thumbHeight:50,
            thumbAmount:5,

            onHoverStop:"on",
            hideThumbs:200,
            navigationType:"bullet",
            navigationStyle:"round",
            navigationArrows:"none",

            touchenabled:"on",

            navOffsetHorizontal:0,
            navOffsetVertical:80,
            shadow:undefined,
            fullWidth:"on",
            fullScreen:"on"
    });
})(jQuery);
/******************************************************************************************************************************
BOOTSTRAP
*******************************************************************************************************************************/
(function($) {
    "use strict";
        $('[data-rel=tooltip]').tooltip();
        $(".alert").alert();
})(jQuery);
/******************************************************************************************************************************
PROGRESS BAR
*******************************************************************************************************************************/
(function($) {
    "use strict";
    $("a.btn-progress").click(function(){
        $('#bar-container').slideToggle();
    });
})(jQuery);
<commit_msg>Styles<commit_after>/******************************************************************************************************************************
COMMING SOON PAGE
*******************************************************************************************************************************/
(function($) {
    /**
    * Set your date here  (YEAR, MONTH (0 for January/11 for December), DAY, HOUR, MINUTE, SECOND)
    * according to the GMT+0 Timezone
    **/
    var launch = new Date(2015, 10, 10, 0, 00);
    /**
    * The script
    **/
    var message = $('#message');
    var days = $('#days');
    var hours = $('#hours');
    var minutes = $('#minutes');
    var seconds = $('#seconds');

    setDate();
    function setDate(){
        var now = new Date();
        if( launch < now ){
            days.html('
0

Day
');
            hours.html('
0

Hour
');
            minutes.html('
0

Minute
');
            seconds.html('
0

Second
');
            // message.html('OUR SITE IS NOT READY YET...');
        }
        else{
            var s = -now.getTimezoneOffset()*60 + (launch.getTime() - now.getTime())/1000;
            var d = Math.floor(s/86400);
            days.html('
'+d+'

Day'+(d>1?'s':''),'
');
            s -= d*86400;

            var h = Math.floor(s/3600);
            hours.html('
'+h+'

Hour'+(h>1?'s':''),'
');
            s -= h*3600;

            var m = Math.floor(s/60);
            minutes.html('
'+m+'

Minute'+(m>1?'s':''),'
');

            s = Math.floor(s-m*60);
            seconds.html('
'+s+'

Second'+(s>1?'s':''),'
');
            setTimeout(setDate, 1000);

            // message.html('OUR SITE IS NOT READY YET, BUT WE ARE COMING SOON');
        }
    }
})(jQuery);
/******************************************************************************************************************************
ANIMATIONS
*******************************************************************************************************************************/
(function($) {
    "use strict";
    var isMobile = false;
    if (navigator.userAgent.match(/Android/i) ||
        navigator.userAgent.match(/webOS/i) ||
        navigator.userAgent.match(/iPhone/i) ||
        navigator.userAgent.match(/iPad/i)||
        navigator.userAgent.match(/iPod/i) ||
        navigator.userAgent.match(/BlackBerry/i)) {
        isMobile = true;
    }
    if (isMobile == true) {
        $('body').removeClass('nomobile');
        $('.animated').removeClass('animated');
    }
    $(function() {
        if (isMobile == false) {
            $('*[data-animated]').addClass('animated');
        }
        function animated_contents() {
            $(".animated:appeared").each(function (i) {
                var $this    = $(this),
                    animated = $(this).data('animated');

                setTimeout(function () {
                    $this.addClass(animated);
                }, 50 * i);
            });
        }
        animated_contents();
        $(window).scroll(function () {
            animated_contents();
        });
    });
})(jQuery);
/******************************************************************************************************************************
SLIDER
*******************************************************************************************************************************/
(function($) {
    "use strict";
    $("body.nomobile #slider").revolution(
    {
            delay:9000,
            startheight:450,
            startwidth:890,

            thumbWidth:100,
            thumbHeight:50,
            thumbAmount:5,

            onHoverStop:"on",
            hideThumbs:200,
            navigationType:"bullet",
            navigationStyle:"round",
            navigationArrows:"none",

            touchenabled:"on",

            navOffsetHorizontal:0,
            navOffsetVertical:80,
            shadow:undefined,
            fullWidth:"on",
            fullScreen:"on"
    });
})(jQuery);
/******************************************************************************************************************************
BOOTSTRAP
*******************************************************************************************************************************/
(function($) {
    "use strict";
        $('[data-rel=tooltip]').tooltip();
        $(".alert").alert();
})(jQuery);
/******************************************************************************************************************************
PROGRESS BAR
*******************************************************************************************************************************/
(function($) {
    "use strict";
    $("a.btn-progress").click(function(){
        $('#bar-container').slideToggle();
    });
})(jQuery);
<|endoftext|>


<commit_before>/*
 * HTML Parser By John Resig (ejohn.org)
 * Original code by Erik Arvidsson, Mozilla Public License
 * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
 *
 * Modified by Wichert Akkerman to support act as a module and handle new
 * HTML5 elements.
 *
 * // Use like so:
 * HTMLParser(htmlString, {
 *     start: function(tag, attrs, unary) {},
 *     end: function(tag) {},
 *     chars: function(text) {},
 *     comment: function(text) {}
 * });
 *
 * // or to get an XML string:
 * HTMLtoXML(htmlString);
 *
 * // or to get an XML DOM Document
 * HTMLtoDOM(htmlString);
 *
 * // or to inject into an existing document/DOM node
 * HTMLtoDOM(htmlString, document);
 * HTMLtoDOM(htmlString, document.body);
 *
 */

define(function(){

	// Regular Expressions for parsing tags and attributes
	var startTag = /^<([-A-Za-z0-9:_]+)((?:\s+[-A-Za-z0-9:_]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/,
		endTag = /^<\/([-A-Za-z0-9:_]+)[^>]*>/,
		attr = /([-A-Za-z0-9:_]+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g;

	// Empty Elements - HTML 5 Working Draft 25 October 2012
	var empty = makeMap("area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed,command,source,embed,track");

	// Block Elements - HTML 5 Working Draft 25 October 2012 and Web Components
	var block = makeMap("address,applet,blockquote,button,center,dd,del,dir,div,dl,dt,fieldset,form,frameset,hr,iframe,ins,isindex,li,map,menu,noframes,noscript,object,ol,p,pre,script,table,tbody,td,tfoot,th,thead,tr,ul,article,aside,details,dialog,summary,figure,footer,header,hgroup,nav,section,audio,video,canvas,datalist,template,element,shadow,decorator,content");

	// Inline Elements - HTML 5 Working Draft 25 October 2012
	var inline = makeMap("a,abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,code,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,textarea,tt,u,var,bdi,bdo,figcaption,mark,meter,progress,ruby,rt,rp,time,wbr");

	// Elements that you can, intentionally, leave open
	// (and which close themselves)
	var closeSelf = makeMap("colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr");

	// Attributes that have their values filled in disabled="disabled"
	var fillAttrs = makeMap("checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected");

	// Special Elements (can contain anything)
	var special = makeMap("script,style");

	var HTMLParser = this.HTMLParser = function( html, handler ) {
		var index, chars, match, stack = [], last = html;
		stack.last = function(){
			return this[ this.length - 1 ];
		};

		if (html.indexOf("<!DOCTYPE")===0) {
			index = html.indexOf(">");
			if (index>=0) {
				if (handler.doctype)
					handler.doctype(html.substring(10, index));
				html = html.substring(index + 1).replace(/^\s+/,"");
			}
		}

		while ( html ) {
			chars = true;

			// Make sure we're not in a script or style element
			if ( !stack.last() || !special[ stack.last() ] ) {

				// Comment
				if ( html.indexOf("<!--") == 0 ) {
					index = html.indexOf("-->");

					if ( index >= 0 ) {
						if ( handler.comment )
							handler.comment( html.substring( 4, index ) );
						html = html.substring( index + 3 );
						chars = false;
					}

				// end tag
				} else if ( html.indexOf("</") == 0 ) {
					match = html.match( endTag );

					if ( match ) {
						html = html.substring( match[0].length );
						match[0].replace( endTag, parseEndTag );
						chars = false;
					}

				// start tag
				} else if ( html.indexOf("<") == 0 ) {
					match = html.match( startTag );

					if ( match ) {
						html = html.substring( match[0].length );
						match[0].replace( startTag, parseStartTag );
						chars = false;
					}
				}

				if ( chars ) {
					index = html.indexOf("<");

					var text = index < 0 ? html : html.substring( 0, index );
					html = index < 0 ? "" : html.substring( index );

					if ( handler.chars )
						handler.chars( text );
				}

			} else {
				html = html.replace(new RegExp("(.*)<\/" + stack.last() + "[^>]*>"), function(all, text){
					text = text.replace(/<!--(.*?)-->/g, "$1")
						.replace(/<!\[CDATA\[(.*?)]]>/g, "$1");

					if ( handler.chars )
						handler.chars( text );

					return "";
				});

				parseEndTag( "", stack.last() );
			}

			if ( html == last )
				throw "Parse Error: " + html;
			last = html;
		}

		// Clean up any remaining tags
		parseEndTag();

		function parseStartTag( tag, tagName, rest, unary ) {
			tagName = tagName.toLowerCase();

			if ( block[ tagName ] ) {
				while ( stack.last() && inline[ stack.last() ] ) {
					parseEndTag( "", stack.last() );
				}
			}

			if ( closeSelf[ tagName ] && stack.last() == tagName ) {
				parseEndTag( "", tagName );
			}

			unary = empty[ tagName ] || !!unary;

			if ( !unary )
				stack.push( tagName );

			if ( handler.start ) {
				var attrs = [];

				rest.replace(attr, function(match, name) {
					var value = arguments[2] ? arguments[2] :
						arguments[3] ? arguments[3] :
						arguments[4] ? arguments[4] :
						fillAttrs[name] ? name : "";

					attrs.push({
						name: name,
						value: value,
						escaped: value.replace(/"/g, """)
					});
				});

				if ( handler.start )
					handler.start( tagName, attrs, unary );
			}
		}

		function parseEndTag( tag, tagName ) {
			// If no tag name is provided, clean shop
			if ( !tagName )
				var pos = 0;

			// Find the closest opened tag of the same type
			else
				for ( var pos = stack.length - 1; pos >= 0; pos-- )
					if ( stack[ pos ] == tagName )
						break;

			if ( pos >= 0 ) {
				// Close all the open elements, up the stack
				for ( var i = stack.length - 1; i >= pos; i-- )
					if ( handler.end )
						handler.end( stack[ i ] );

				// Remove the open elements from the stack
				stack.length = pos;
			}
		}
	};

	this.HTMLtoXML = function( html ) {
		var results = "";

		HTMLParser(html, {
			start: function( tag, attrs, unary ) {
				results += "<" + tag;

				for ( var i = 0; i < attrs.length; i++ )
					results += " " + attrs[i].name + '="' + attrs[i].escaped + '"';

				results += (unary ? "/" : "") + ">";
			},
			end: function( tag ) {
				results += "</" + tag + ">";
			},
			chars: function( text ) {
				results += text;
			},
			comment: function( text ) {
				results += "<!--" + text + "-->";
			}
		});

		return results;
	};

	this.HTMLtoDOM = function( html, doc ) {
		// There can be only one of these elements
		var one = makeMap("html,head,body,title");

		// Enforce a structure for the document
		var structure = {
			link: "head",
			base: "head"
		};

		if ( !doc ) {
			if ( typeof DOMDocument != "undefined" )
				doc = new DOMDocument();
			else if ( typeof document != "undefined" && document.implementation && document.implementation.createDocument )
				doc = document.implementation.createDocument("", "", null);
			else if ( typeof ActiveX != "undefined" )
				doc = new ActiveXObject("Msxml.DOMDocument");

		} else
			doc = doc.ownerDocument ||
				doc.getOwnerDocument && doc.getOwnerDocument() ||
				doc;

		var elems = [],
			documentElement = doc.documentElement ||
				doc.getDocumentElement && doc.getDocumentElement();

		// If we're dealing with an empty document then we
		// need to pre-populate it with the HTML document structure
		if ( !documentElement && doc.createElement ) (function(){
			var html = doc.createElement("html");
			var head = doc.createElement("head");
			head.appendChild( doc.createElement("title") );
			html.appendChild( head );
			html.appendChild( doc.createElement("body") );
			doc.appendChild( html );
		})();

		// Find all the unique elements
		if ( doc.getElementsByTagName )
			for ( var i in one )
				one[ i ] = doc.getElementsByTagName( i )[0];

		// If we're working with a document, inject contents into
		// the body element
		var curParentNode = one.body;

		HTMLParser( html, {
			start: function( tagName, attrs, unary ) {
				// If it's a pre-built element, then we can ignore
				// its construction
				if ( one[ tagName ] ) {
					curParentNode = one[ tagName ];
					if ( !unary ) {
						elems.push( curParentNode );
					}
					return;
				}

				var elem = doc.createElement( tagName );

				for ( var attr in attrs )
					elem.setAttribute( attrs[ attr ].name, attrs[ attr ].value );

				if ( structure[ tagName ] && typeof one[ structure[ tagName ] ] != "boolean" )
					one[ structure[ tagName ] ].appendChild( elem );

				else if ( curParentNode && curParentNode.appendChild )
					curParentNode.appendChild( elem );

				if ( !unary ) {
					elems.push( elem );
					curParentNode = elem;
				}
			},
			end: function( tag ) {
				elems.length -= 1;

				// Init the new parentNode
				curParentNode = elems[ elems.length - 1 ];
			},
			chars: function( text ) {
				curParentNode.appendChild( doc.createTextNode( text ) );
			},
			comment: function( text ) {
				// create comment node
			}
		});

		return doc;
	};

	function makeMap(str){
		var obj = {}, items = str.split(",");
		for ( var i = 0; i < items.length; i++ )
			obj[ items[i] ] = true;
		return obj;
	}

	return {HTMLParser: HTMLParser,
		HTMLtoXML: HTMLtoXML,
		HTMLtoDOM: HTMLtoDOM
	};
});
<commit_msg>Add textarea and title to special map.<commit_after>/*
 * HTML Parser By John Resig (ejohn.org)
 * Original code by Erik Arvidsson, Mozilla Public License
 * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
 *
 * Modified by Wichert Akkerman to support act as a module and handle new
 * HTML5 elements.
 *
 * // Use like so:
 * HTMLParser(htmlString, {
 *     start: function(tag, attrs, unary) {},
 *     end: function(tag) {},
 *     chars: function(text) {},
 *     comment: function(text) {}
 * });
 *
 * // or to get an XML string:
 * HTMLtoXML(htmlString);
 *
 * // or to get an XML DOM Document
 * HTMLtoDOM(htmlString);
 *
 * // or to inject into an existing document/DOM node
 * HTMLtoDOM(htmlString, document);
 * HTMLtoDOM(htmlString, document.body);
 *
 */

define(function(){

	// Regular Expressions for parsing tags and attributes
	var startTag = /^<([-A-Za-z0-9:_]+)((?:\s+[-A-Za-z0-9:_]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/,
		endTag = /^<\/([-A-Za-z0-9:_]+)[^>]*>/,
		attr = /([-A-Za-z0-9:_]+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g;

	// Empty Elements - HTML 5 Working Draft 25 October 2012
	var empty = makeMap("area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed,command,source,embed,track");

	// Block Elements - HTML 5 Working Draft 25 October 2012 and Web Components
	var block = makeMap("address,applet,blockquote,button,center,dd,del,dir,div,dl,dt,fieldset,form,frameset,hr,iframe,ins,isindex,li,map,menu,noframes,noscript,object,ol,p,pre,script,table,tbody,td,tfoot,th,thead,tr,ul,article,aside,details,dialog,summary,figure,footer,header,hgroup,nav,section,audio,video,canvas,datalist,template,element,shadow,decorator,content");

	// Inline Elements - HTML 5 Working Draft 25 October 2012
	var inline = makeMap("a,abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,code,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,textarea,tt,u,var,bdi,bdo,figcaption,mark,meter,progress,ruby,rt,rp,time,wbr");

	// Elements that you can, intentionally, leave open
	// (and which close themselves)
	var closeSelf = makeMap("colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr");

	// Attributes that have their values filled in disabled="disabled"
	var fillAttrs = makeMap("checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected");

	// Special Elements (can contain anything) Reference: http://www.w3.org/TR/html-markup/syntax.html#replaceable-character-data
	var special = makeMap("script,style,textarea,title");

	var HTMLParser = this.HTMLParser = function( html, handler ) {
		var index, chars, match, stack = [], last = html;
		stack.last = function(){
			return this[ this.length - 1 ];
		};

		if (html.indexOf("<!DOCTYPE")===0) {
			index = html.indexOf(">");
			if (index>=0) {
				if (handler.doctype)
					handler.doctype(html.substring(10, index));
				html = html.substring(index + 1).replace(/^\s+/,"");
			}
		}

		while ( html ) {
			chars = true;

			// Make sure we're not in a script or style element
			if ( !stack.last() || !special[ stack.last() ] ) {

				// Comment
				if ( html.indexOf("<!--") == 0 ) {
					index = html.indexOf("-->");

					if ( index >= 0 ) {
						if ( handler.comment )
							handler.comment( html.substring( 4, index ) );
						html = html.substring( index + 3 );
						chars = false;
					}

				// end tag
				} else if ( html.indexOf("</") == 0 ) {
					match = html.match( endTag );

					if ( match ) {
						html = html.substring( match[0].length );
						match[0].replace( endTag, parseEndTag );
						chars = false;
					}

				// start tag
				} else if ( html.indexOf("<") == 0 ) {
					match = html.match( startTag );

					if ( match ) {
						html = html.substring( match[0].length );
						match[0].replace( startTag, parseStartTag );
						chars = false;
					}
				}

				if ( chars ) {
					index = html.indexOf("<");

					var text = index < 0 ? html : html.substring( 0, index );
					html = index < 0 ? "" : html.substring( index );

					if ( handler.chars )
						handler.chars( text );
				}

			} else {
				html = html.replace(new RegExp("(.*)<\/" + stack.last() + "[^>]*>"), function(all, text){
					text = text.replace(/<!--(.*?)-->/g, "$1")
						.replace(/<!\[CDATA\[(.*?)]]>/g, "$1");

					if ( handler.chars )
						handler.chars( text );

					return "";
				});

				parseEndTag( "", stack.last() );
			}

			if ( html == last )
				throw "Parse Error: " + html;
			last = html;
		}

		// Clean up any remaining tags
		parseEndTag();

		function parseStartTag( tag, tagName, rest, unary ) {
			tagName = tagName.toLowerCase();

			if ( block[ tagName ] ) {
				while ( stack.last() && inline[ stack.last() ] ) {
					parseEndTag( "", stack.last() );
				}
			}

			if ( closeSelf[ tagName ] && stack.last() == tagName ) {
				parseEndTag( "", tagName );
			}

			unary = empty[ tagName ] || !!unary;

			if ( !unary )
				stack.push( tagName );

			if ( handler.start ) {
				var attrs = [];

				rest.replace(attr, function(match, name) {
					var value = arguments[2] ? arguments[2] :
						arguments[3] ? arguments[3] :
						arguments[4] ? arguments[4] :
						fillAttrs[name] ? name : "";

					attrs.push({
						name: name,
						value: value,
						escaped: value.replace(/"/g, """)
					});
				});

				if ( handler.start )
					handler.start( tagName, attrs, unary );
			}
		}

		function parseEndTag( tag, tagName ) {
			// If no tag name is provided, clean shop
			if ( !tagName )
				var pos = 0;

			// Find the closest opened tag of the same type
			else
				for ( var pos = stack.length - 1; pos >= 0; pos-- )
					if ( stack[ pos ] == tagName )
						break;

			if ( pos >= 0 ) {
				// Close all the open elements, up the stack
				for ( var i = stack.length - 1; i >= pos; i-- )
					if ( handler.end )
						handler.end( stack[ i ] );

				// Remove the open elements from the stack
				stack.length = pos;
			}
		}
	};

	this.HTMLtoXML = function( html ) {
		var results = "";

		HTMLParser(html, {
			start: function( tag, attrs, unary ) {
				results += "<" + tag;

				for ( var i = 0; i < attrs.length; i++ )
					results += " " + attrs[i].name + '="' + attrs[i].escaped + '"';

				results += (unary ? "/" : "") + ">";
			},
			end: function( tag ) {
				results += "</" + tag + ">";
			},
			chars: function( text ) {
				results += text;
			},
			comment: function( text ) {
				results += "<!--" + text + "-->";
			}
		});

		return results;
	};

	this.HTMLtoDOM = function( html, doc ) {
		// There can be only one of these elements
		var one = makeMap("html,head,body,title");

		// Enforce a structure for the document
		var structure = {
			link: "head",
			base: "head"
		};

		if ( !doc ) {
			if ( typeof DOMDocument != "undefined" )
				doc = new DOMDocument();
			else if ( typeof document != "undefined" && document.implementation && document.implementation.createDocument )
				doc = document.implementation.createDocument("", "", null);
			else if ( typeof ActiveX != "undefined" )
				doc = new ActiveXObject("Msxml.DOMDocument");

		} else
			doc = doc.ownerDocument ||
				doc.getOwnerDocument && doc.getOwnerDocument() ||
				doc;

		var elems = [],
			documentElement = doc.documentElement ||
				doc.getDocumentElement && doc.getDocumentElement();

		// If we're dealing with an empty document then we
		// need to pre-populate it with the HTML document structure
		if ( !documentElement && doc.createElement ) (function(){
			var html = doc.createElement("html");
			var head = doc.createElement("head");
			head.appendChild( doc.createElement("title") );
			html.appendChild( head );
			html.appendChild( doc.createElement("body") );
			doc.appendChild( html );
		})();

		// Find all the unique elements
		if ( doc.getElementsByTagName )
			for ( var i in one )
				one[ i ] = doc.getElementsByTagName( i )[0];

		// If we're working with a document, inject contents into
		// the body element
		var curParentNode = one.body;

		HTMLParser( html, {
			start: function( tagName, attrs, unary ) {
				// If it's a pre-built element, then we can ignore
				// its construction
				if ( one[ tagName ] ) {
					curParentNode = one[ tagName ];
					if ( !unary ) {
						elems.push( curParentNode );
					}
					return;
				}

				var elem = doc.createElement( tagName );

				for ( var attr in attrs )
					elem.setAttribute( attrs[ attr ].name, attrs[ attr ].value );

				if ( structure[ tagName ] && typeof one[ structure[ tagName ] ] != "boolean" )
					one[ structure[ tagName ] ].appendChild( elem );

				else if ( curParentNode && curParentNode.appendChild )
					curParentNode.appendChild( elem );

				if ( !unary ) {
					elems.push( elem );
					curParentNode = elem;
				}
			},
			end: function( tag ) {
				elems.length -= 1;

				// Init the new parentNode
				curParentNode = elems[ elems.length - 1 ];
			},
			chars: function( text ) {
				curParentNode.appendChild( doc.createTextNode( text ) );
			},
			comment: function( text ) {
				// create comment node
			}
		});

		return doc;
	};

	function makeMap(str){
		var obj = {}, items = str.split(",");
		for ( var i = 0; i < items.length; i++ )
			obj[ items[i] ] = true;
		return obj;
	}

	return {HTMLParser: HTMLParser,
		HTMLtoXML: HTMLtoXML,
		HTMLtoDOM: HTMLtoDOM
	};
});
<|endoftext|>


<commit_before>/**!
Copyright 2016 SEITENBAU GmbH

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.
*/

'use strict';

var express = require('express');
var http = require('http');
var https = require('https');
var request = require('request');
var konphyg = require('konphyg');
var redirect = require('express-redirect');
var fs = require('fs');
var path = require('path');
var fileExists = require('file-exists');

var unIndent = require('./helpers/unIndent.js');

module.exports = (projectName) => {

    // Setup
    var sourceFolder = path.join(__dirname, '..');
    var projectFolderRelative = projectName || 'project';
    var projectFolderAbs = path.join(sourceFolder, projectFolderRelative);
    var globalConfig = konphyg(sourceFolder);
    var projectConfig, server;
    try {
        projectConfig = konphyg(path.join(projectFolderAbs, 'config'));
    } catch (e) {
        console.error(`FATAL: Could not open project "${projectFolderRelative}".`);
        if (!projectName) {
            console.error(`Remember to pass the project folder name as an argument.`);
        }
        process.exit(1);
    }

    // Server port configuration
    var configuredPort = projectConfig('server.port');

    // HTTPS certificates
    var privateKey = fs.readFileSync(path.join(sourceFolder, 'sslcert/localhost.pem'), 'utf8');
    var certificate = fs.readFileSync(path.join(sourceFolder, 'sslcert/localhost.crt'), 'utf8');

    // Mocking services
    var rootConfig = projectConfig('server.root');
    var serverRoot = rootConfig.root.replace(/\/+$/, '');
    var RESTRoot = (serverRoot + '/' + rootConfig.serviceBasePath).replace(/\/+$/, '').replace(/\/+/g, '/');

    // Path config
    var staticFilesDirRel = projectConfig('filesystem.path').public;
    var mockFilesDirRel = projectConfig('filesystem.path').mock;

    /**
     * @typedef {{
        *   active: {boolean} Whether the above mentioned configuration should be used or not. If you are not behind a proxy set it to false,
        *   url: {string} The URL of the proxy server,
        *   port: {(string|number)} The port of the proxy server
        * }}
     */
    var localProxyConfig = globalConfig('local.proxy');

    /**
     * @typedef {{
     *      backend: {string} The base URL of the real backend,
     *      delayedServices: {Object} Services specified here are called after the given amount of time has passed,
     *      rejectUnauthorized: {boolean} Whether invalid certificates should be rejected or not
     * }}
     */
    var serverProxyConfig = projectConfig('server.proxy');

    var unVersionedFileNameInfix = 'development';
    var staticFilesDirAbs = path.join(projectFolderAbs, staticFilesDirRel);

    /**
     * Given a file path, it tries to find the local and un-versioned version of it (marked by *.development.*) and returns
     * it. If no local version exists the regular file path is returned. E.G. config.json & config.development.json
     * @param {string} filePath
     * @param {string} fileName
     * @return {string}
     */
    var getFilePath = (filePath, fileName) => {
        var splitFileName = fileName.split('.');
        var name = splitFileName[0];
        var ending = splitFileName[1];
        var devFileName = name + `.${unVersionedFileNameInfix}.${ending}`;
        var devFilePath = path.join(filePath, devFileName);
        var regularFilePath = path.join(filePath, fileName);
        return fileExists(devFilePath) ? devFilePath : regularFilePath;
    };

    /**
     * Sends back the given file and delays the response
     * @param {string} filePath The full path to the file which is send as a response
     * @param {number} delayBy The number of milliseconds by which the response is delayed
     * @param {{}} request The request object
     * @param {{}} response The response object
     */
    var sendDelayedFile = (filePath, delayBy, request, response) => {
        setTimeout(response.sendfile.bind(response, filePath), delayBy);
    };

    /**
     * Sends back the given error and delays the response
     * @param {{error: string, status: number}} errorConfig The error configuration object
     * @param {number} delayBy The number of milliseconds by which the response is delayed
     * @param {string=} filePath
     * @param {{}} request The request object
     * @param {{}} response The response object
     */
    var sendDelayedError = (errorConfig, delayBy, filePath, request, response) => {
        var responseFunc;
        if (errorConfig.error) {
            responseFunc = response.send.bind(response, errorConfig.status, {faultCode: errorConfig.error});
        } else if (filePath) {
            responseFunc = () => {
                response.status(errorConfig.status).sendfile(filePath);
            };
        }
        setTimeout(responseFunc, delayBy);
    };

    /**
     * Sends back the given file
     * @param {string} filePath The full path to the file which is send as a response
     * @param {{}} request The request object
     * @param {{}} response The response object
     */
    var sendFile = (filePath, request, response) => {
        response.sendfile(filePath);
    };

    /**
     * Sends back the specified error
     * @param {{status: string, error: string=}} errorConfig The error config holding the HTTP status and the
     * error id or the error response object
     * @param {string=} filePath
     * @param {{}} request The request object
     * @param {{}} response The response object
     */
    var sendError = (errorConfig, filePath, request, response) => {
        if (errorConfig.error) {
            response.send(errorConfig.status, {faultCode: errorConfig.error});
        } else if (filePath) {
            response.status(errorConfig.status).sendfile(filePath);
        }
    };

    /**
     * Returns a function for mocking the request based on the given mock configuration
     * @param {{
     *      file: string,
     *      delayBy: number,
     *      error: {
     *          status: number,
     *          error: string
     *      },
     *      path: string,
     *      active: boolean
     * }} mock The configuration object
     * @param {string} filePath The full path to the file which is send as a response
     * @return {Function|undefined}
     */
    var getMockingFunction = (mock, filePath) => {
        var mockFunc;
        if (mock.file && !mock.error) {
            if (mock.delayBy) {
                mockFunc = sendDelayedFile.bind(this, filePath, mock.delayBy);
            } else {
                mockFunc = sendFile.bind(this, filePath);
            }
        } else if (mock.error) {
            if (mock.delayBy) {
                mockFunc = sendDelayedError.bind(this, mock.error, mock.delayBy, filePath);
            } else {
                mockFunc = sendError.bind(this, mock.error, filePath);
            }
        }
        return mockFunc;
    };

    /**
     * Setups a mock service for the given path and method
     * @param {string} methodName The name of the HTTP method PUT, GET, POST, …
     * @param {{
     *      file: string,
     *      delayBy: number,
     *      error: {
     *          status: number,
     *          error: string
     *      },
     *      path: string,
     *      active: boolean
     * }} mock The configuration object
     * @param {string} mock.path The REST path which has to be mocked
     * @param {string} mock.file The JSON file which should be returned by the service mock
     */
    var setupRESTMock = (methodName, mock) => {
        var directory = path.join(projectFolderAbs, mockFilesDirRel, methodName.toUpperCase());
        var filePath = mock.file ? getFilePath(directory, mock.file) : '';
        var mockFunc = getMockingFunction(mock, filePath);
        if (mock.path.indexOf('/') === 0) { // FIXME: why do we need this?
            throw `${mock.path} should not start with a slash. The mocked service will not work!`;
        }
        if (mockFunc) {
            server[methodName.toLowerCase()](RESTRoot + '/' + mock.path, mockFunc);
        }
    };

    /**
     * Setups up the mock services for the given mock configuration using the given mocking function
     * @param {{}} mocks
     * @param {boolean} mocks.active Whether the mock should in general be enabled or not
     * @param {Array.<{active: boolean, path: string, file:string}>} mocks.services The description of the services which
     * @param {Function} mockFunc The function which is called on each entry and which is doing the actual mocking work
     * should be mocked
     */
    var setupRESTMocks = (mocks, mockFunc) => {
        if (mocks.active) {
            mocks.services.filter(s => s.active).forEach(mockFunc);
        }
    };

    /**
     * Takes the given request ands proxies it through to real backend
     * @param {{}} requestConfig The configuration for the request object
     * @param {string=} requestConfig.proxy The URL + port of the proxy server (if one should be used)
     * @param {Object} req The request object
     * @param {Object} res The response object
     */
    var pipeRequest = (requestConfig, req, res) => {
        var remote = request(Object.assign({}, requestConfig, {
            url: serverProxyConfig.backend + req.url.replace(serverRoot, '')
        }));
        req.pipe(remote);
        remote.pipe(res);
    };

    /**
     * Proxies REST calls through to the real backend. If the REST  services appears in the delayedServices in the proxy.json
     * it is delayed
     * @param {{}} requestConfig The configuration for the request object
     * @param {string=} requestConfig.proxy The URL + port of the proxy server (if one should be used)
     * @param req
     * @param res
     * @param next
     */
    var proxyREST = (requestConfig, req, res, next) => {
        if (req.url.indexOf(RESTRoot) === 0) {
            if (serverProxyConfig.delayedServices) {
                var key = decodeURIComponent(req.url.replace(RESTRoot, ''));
                var delay = serverProxyConfig.delayedServices[key];
                if (delay) {
                    console.log(`delaying ${key} for ${delay} ms`);
                    setTimeout(pipeRequest.bind(this, requestConfig, req, res), delay);
                } else {
                    pipeRequest(requestConfig, req, res);
                }
            } else {
                pipeRequest(requestConfig, req, res);
            }
        } else {
            next();
        }
    };

    /**
     * Return the file system path for the requested file
     * @param {string} srcPath The path on the file system where the files are located
     * @param {string} relPath The relative path of the request
     * @param {Object} req The request object itself
     * @return {?string}
     */
    var getFilePathForRequest = (srcPath, relPath, req) => {
        var file;
        var requestedFile = path.resolve(srcPath, relPath);
        if (path.normalize(requestedFile) === path.normalize(staticFilesDirAbs) && req.path.replace(/\/$/, '') !== serverRoot) {
            // The requested file can be found in our static file directory and can therefore can be handled by
            // express.static AND is not our root path, which can of course not be found in the static dir
            file = null;
        } else if (rootConfig.defaultFile && req.path.replace(/\/?$/, '') === serverRoot) {
            // The requested file is our server root, therefore we need to send index.html
            file = path.join(path.normalize(staticFilesDirAbs), rootConfig.defaultFile);
        } else if (fs.existsSync(requestedFile)) {
            // The file is neither a file found in our dev path nor a request for the index, then it must be a file which
            // can only be found in the src directory (during development);
            file = requestedFile;
        }
        return file;
    };

    var proxyFilesystem = (req, res, next) => {
        var requestedUrl = req.path;
        var relPath = path.relative(serverRoot, requestedUrl);
        var file = getFilePathForRequest(staticFilesDirRel, relPath, req);
        if (file) {
            res.charset = 'utf-8'; // TODO: use https://www.npmjs.com/package/detect-encoding ?
            res.sendfile(file);
            return;
        }
        next();
    };

    /**
     * Setups the proxying so the REST request can be answered by our real backend on a remote server
     */
    var setupProxying = () => {
        var requestConfig = {
            rejectUnauthorized: serverProxyConfig.rejectUnauthorized !== false
        };
        if (localProxyConfig.active) {
            requestConfig.proxy = `${localProxyConfig.url}:${localProxyConfig.port}`;
        }
        server.use(proxyREST.bind(this, requestConfig));
        server.use(proxyFilesystem);
    };

    /**
     * Setups the port of the server
     * @param {{port: number, sslPort: number}=} portConfig The port configuration file
     * @return {Array<number>} The server ports
     */
    var setupServerPort = portConfig => {
        var serverPort = 80;
        var sslPort = 443;
        if (portConfig) {
            if (typeof portConfig.port !== 'undefined') {
                serverPort = portConfig.port;
            }
            if (typeof portConfig.sslPort !== 'undefined') {
                sslPort = portConfig.sslPort;
            }
        }
        return [serverPort, sslPort];
    };

    // SERVER CONFIGURATION
    // ====================
    (() => {
        server = express();
        server.use(express.compress());

        redirect(server);
        server.redirect('/', serverRoot); // TODO: make configurable? GH-8
        server.redirect(serverRoot, `${serverRoot}/`); // TODO: make configurable? GH-8

        ['get', 'put', 'post', 'delete'].forEach(method => {
            let mockCfg = projectConfig(`services/${method.toUpperCase()}.mock`);
            setupRESTMocks(mockCfg, setupRESTMock.bind(this, method));
        });

        setupProxying();

        server.use(serverRoot, express.static(staticFilesDirAbs, {redirect: false}));
        server.use('/', express.static(path.join(__dirname, '..'), {redirect: false})); // TODO: wrong path?

        server.use(express.errorHandler({
            dumpExceptions: true,
            showStack: true
        }));

        server.use(serverRoot, express.json());
        server.use(serverRoot, express.urlencoded());
        server.use(serverRoot, server.router);
    })();

    // SERVER
    // ======
    var ports = setupServerPort(configuredPort);
    var port = ports[0];
    var sslPort = ports[1];

    // Start Node.js Server
    var httpServer = http.createServer(server);
    httpServer.listen(port);
    if (sslPort > 0) {
        var httpsServer = https.createServer({key: privateKey, cert: certificate}, server);
        httpsServer.on('error', () => {
            console.warn(`Could not launch HTTPS server on port ${sslPort}! Try again as admin or use a different port.`);
            sslPort = -1;
        });
        httpsServer.listen(sslPort);
    }

    // get local IP addresses
    var getIp = () => {
        var interfaces = require('os').networkInterfaces();
        for (var device in interfaces) {
            if (interfaces.hasOwnProperty(device)) {
                var details = interfaces[device];
                for (var i = 0; i < details.length; i++) {
                    var detail = details[i];
                    if (detail.family === 'IPv4' && !detail.internal) {
                        return detail.address;
                    }
                }
            }
        }
        return null;
    };
    var ip = getIp();

    process.nextTick(() => {
        var urls = [`http://localhost:${port}${serverRoot}`];
        if (sslPort > 0) {
            urls.unshift(`https://localhost:${sslPort}${serverRoot}`);
        }
        if (ip) {
            urls.push(`http://${ip}:${port}${serverRoot}`);
        }

        console.log(unIndent `Welcome to mix-n-mock!

        Please go to ${urls.join(' or ')} to start using it.`);
    });
};
<commit_msg>GH-13: Cleanup TODO comments<commit_after>/**!
Copyright 2016 SEITENBAU GmbH

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.
*/

'use strict';

var express = require('express');
var http = require('http');
var https = require('https');
var request = require('request');
var konphyg = require('konphyg');
var redirect = require('express-redirect');
var fs = require('fs');
var path = require('path');
var fileExists = require('file-exists');

var unIndent = require('./helpers/unIndent.js');

module.exports = (projectName) => {

    // Setup
    var sourceFolder = path.join(__dirname, '..');
    var projectFolderRelative = projectName || 'project';
    var projectFolderAbs = path.join(sourceFolder, projectFolderRelative);
    var globalConfig = konphyg(sourceFolder);
    var projectConfig, server;
    try {
        projectConfig = konphyg(path.join(projectFolderAbs, 'config'));
    } catch (e) {
        console.error(`FATAL: Could not open project "${projectFolderRelative}".`);
        if (!projectName) {
            console.error(`Remember to pass the project folder name as an argument.`);
        }
        process.exit(1);
    }

    // Server port configuration
    var configuredPort = projectConfig('server.port');

    // HTTPS certificates
    var privateKey = fs.readFileSync(path.join(sourceFolder, 'sslcert/localhost.pem'), 'utf8');
    var certificate = fs.readFileSync(path.join(sourceFolder, 'sslcert/localhost.crt'), 'utf8');

    // Mocking services
    var rootConfig = projectConfig('server.root');
    var serverRoot = rootConfig.root.replace(/\/+$/, '');
    var RESTRoot = (serverRoot + '/' + rootConfig.serviceBasePath).replace(/\/+$/, '').replace(/\/+/g, '/');

    // Path config
    var staticFilesDirRel = projectConfig('filesystem.path').public;
    var mockFilesDirRel = projectConfig('filesystem.path').mock;

    /**
     * @typedef {{
        *   active: {boolean} Whether the above mentioned configuration should be used or not. If you are not behind a proxy set it to false,
        *   url: {string} The URL of the proxy server,
        *   port: {(string|number)} The port of the proxy server
        * }}
     */
    var localProxyConfig = globalConfig('local.proxy');

    /**
     * @typedef {{
     *      backend: {string} The base URL of the real backend,
     *      delayedServices: {Object} Services specified here are called after the given amount of time has passed,
     *      rejectUnauthorized: {boolean} Whether invalid certificates should be rejected or not
     * }}
     */
    var serverProxyConfig = projectConfig('server.proxy');

    var unVersionedFileNameInfix = 'development';
    var staticFilesDirAbs = path.join(projectFolderAbs, staticFilesDirRel);

    /**
     * Given a file path, it tries to find the local and un-versioned version of it (marked by *.development.*) and returns
     * it. If no local version exists the regular file path is returned. E.G. config.json & config.development.json
     * @param {string} filePath
     * @param {string} fileName
     * @return {string}
     */
    var getFilePath = (filePath, fileName) => {
        var splitFileName = fileName.split('.');
        var name = splitFileName[0];
        var ending = splitFileName[1];
        var devFileName = name + `.${unVersionedFileNameInfix}.${ending}`;
        var devFilePath = path.join(filePath, devFileName);
        var regularFilePath = path.join(filePath, fileName);
        return fileExists(devFilePath) ? devFilePath : regularFilePath;
    };

    /**
     * Sends back the given file and delays the response
     * @param {string} filePath The full path to the file which is send as a response
     * @param {number} delayBy The number of milliseconds by which the response is delayed
     * @param {{}} request The request object
     * @param {{}} response The response object
     */
    var sendDelayedFile = (filePath, delayBy, request, response) => {
        setTimeout(response.sendfile.bind(response, filePath), delayBy);
    };

    /**
     * Sends back the given error and delays the response
     * @param {{error: string, status: number}} errorConfig The error configuration object
     * @param {number} delayBy The number of milliseconds by which the response is delayed
     * @param {string=} filePath
     * @param {{}} request The request object
     * @param {{}} response The response object
     */
    var sendDelayedError = (errorConfig, delayBy, filePath, request, response) => {
        var responseFunc;
        if (errorConfig.error) {
            responseFunc = response.send.bind(response, errorConfig.status, {faultCode: errorConfig.error});
        } else if (filePath) {
            responseFunc = () => {
                response.status(errorConfig.status).sendfile(filePath);
            };
        }
        setTimeout(responseFunc, delayBy);
    };

    /**
     * Sends back the given file
     * @param {string} filePath The full path to the file which is send as a response
     * @param {{}} request The request object
     * @param {{}} response The response object
     */
    var sendFile = (filePath, request, response) => {
        response.sendfile(filePath);
    };

    /**
     * Sends back the specified error
     * @param {{status: string, error: string=}} errorConfig The error config holding the HTTP status and the
     * error id or the error response object
     * @param {string=} filePath
     * @param {{}} request The request object
     * @param {{}} response The response object
     */
    var sendError = (errorConfig, filePath, request, response) => {
        if (errorConfig.error) {
            response.send(errorConfig.status, {faultCode: errorConfig.error});
        } else if (filePath) {
            response.status(errorConfig.status).sendfile(filePath);
        }
    };

    /**
     * Returns a function for mocking the request based on the given mock configuration
     * @param {{
     *      file: string,
     *      delayBy: number,
     *      error: {
     *          status: number,
     *          error: string
     *      },
     *      path: string,
     *      active: boolean
     * }} mock The configuration object
     * @param {string} filePath The full path to the file which is send as a response
     * @return {Function|undefined}
     */
    var getMockingFunction = (mock, filePath) => {
        var mockFunc;
        if (mock.file && !mock.error) {
            if (mock.delayBy) {
                mockFunc = sendDelayedFile.bind(this, filePath, mock.delayBy);
            } else {
                mockFunc = sendFile.bind(this, filePath);
            }
        } else if (mock.error) {
            if (mock.delayBy) {
                mockFunc = sendDelayedError.bind(this, mock.error, mock.delayBy, filePath);
            } else {
                mockFunc = sendError.bind(this, mock.error, filePath);
            }
        }
        return mockFunc;
    };

    /**
     * Setups a mock service for the given path and method
     * @param {string} methodName The name of the HTTP method PUT, GET, POST, …
     * @param {{
     *      file: string,
     *      delayBy: number,
     *      error: {
     *          status: number,
     *          error: string
     *      },
     *      path: string,
     *      active: boolean
     * }} mock The configuration object
     * @param {string} mock.path The REST path which has to be mocked
     * @param {string} mock.file The JSON file which should be returned by the service mock
     */
    var setupRESTMock = (methodName, mock) => {
        var directory = path.join(projectFolderAbs, mockFilesDirRel, methodName.toUpperCase());
        var filePath = mock.file ? getFilePath(directory, mock.file) : '';
        var mockFunc = getMockingFunction(mock, filePath);
        if (mock.path.indexOf('/') === 0) {
            throw `${mock.path} should not start with a slash. The mocked service will not work!`;
        }
        if (mockFunc) {
            server[methodName.toLowerCase()](RESTRoot + '/' + mock.path, mockFunc);
        }
    };

    /**
     * Setups up the mock services for the given mock configuration using the given mocking function
     * @param {{}} mocks
     * @param {boolean} mocks.active Whether the mock should in general be enabled or not
     * @param {Array.<{active: boolean, path: string, file:string}>} mocks.services The description of the services which
     * @param {Function} mockFunc The function which is called on each entry and which is doing the actual mocking work
     * should be mocked
     */
    var setupRESTMocks = (mocks, mockFunc) => {
        if (mocks.active) {
            mocks.services.filter(s => s.active).forEach(mockFunc);
        }
    };

    /**
     * Takes the given request ands proxies it through to real backend
     * @param {{}} requestConfig The configuration for the request object
     * @param {string=} requestConfig.proxy The URL + port of the proxy server (if one should be used)
     * @param {Object} req The request object
     * @param {Object} res The response object
     */
    var pipeRequest = (requestConfig, req, res) => {
        var remote = request(Object.assign({}, requestConfig, {
            url: serverProxyConfig.backend + req.url.replace(serverRoot, '')
        }));
        req.pipe(remote);
        remote.pipe(res);
    };

    /**
     * Proxies REST calls through to the real backend. If the REST  services appears in the delayedServices in the proxy.json
     * it is delayed
     * @param {{}} requestConfig The configuration for the request object
     * @param {string=} requestConfig.proxy The URL + port of the proxy server (if one should be used)
     * @param req
     * @param res
     * @param next
     */
    var proxyREST = (requestConfig, req, res, next) => {
        if (req.url.indexOf(RESTRoot) === 0) {
            if (serverProxyConfig.delayedServices) {
                var key = decodeURIComponent(req.url.replace(RESTRoot, ''));
                var delay = serverProxyConfig.delayedServices[key];
                if (delay) {
                    console.log(`delaying ${key} for ${delay} ms`);
                    setTimeout(pipeRequest.bind(this, requestConfig, req, res), delay);
                } else {
                    pipeRequest(requestConfig, req, res);
                }
            } else {
                pipeRequest(requestConfig, req, res);
            }
        } else {
            next();
        }
    };

    /**
     * Return the file system path for the requested file
     * @param {string} srcPath The path on the file system where the files are located
     * @param {string} relPath The relative path of the request
     * @param {Object} req The request object itself
     * @return {?string}
     */
    var getFilePathForRequest = (srcPath, relPath, req) => {
        var file;
        var requestedFile = path.resolve(srcPath, relPath);
        if (path.normalize(requestedFile) === path.normalize(staticFilesDirAbs) && req.path.replace(/\/$/, '') !== serverRoot) {
            // The requested file can be found in our static file directory and can therefore can be handled by
            // express.static AND is not our root path, which can of course not be found in the static dir
            file = null;
        } else if (rootConfig.defaultFile && req.path.replace(/\/?$/, '') === serverRoot) {
            // The requested file is our server root, therefore we need to send index.html
            file = path.join(path.normalize(staticFilesDirAbs), rootConfig.defaultFile);
        } else if (fs.existsSync(requestedFile)) {
            // The file is neither a file found in our dev path nor a request for the index, then it must be a file which
            // can only be found in the src directory (during development);
            file = requestedFile;
        }
        return file;
    };

    var proxyFilesystem = (req, res, next) => {
        var requestedUrl = req.path;
        var relPath = path.relative(serverRoot, requestedUrl);
        var file = getFilePathForRequest(staticFilesDirRel, relPath, req);
        if (file) {
            res.charset = 'utf-8'; // TODO: use https://www.npmjs.com/package/detect-encoding ? GH-15
            res.sendfile(file);
            return;
        }
        next();
    };

    /**
     * Setups the proxying so the REST request can be answered by our real backend on a remote server
     */
    var setupProxying = () => {
        var requestConfig = {
            rejectUnauthorized: serverProxyConfig.rejectUnauthorized !== false
        };
        if (localProxyConfig.active) {
            requestConfig.proxy = `${localProxyConfig.url}:${localProxyConfig.port}`;
        }
        server.use(proxyREST.bind(this, requestConfig));
        server.use(proxyFilesystem);
    };

    /**
     * Setups the port of the server
     * @param {{port: number, sslPort: number}=} portConfig The port configuration file
     * @return {Array<number>} The server ports
     */
    var setupServerPort = portConfig => {
        var serverPort = 80;
        var sslPort = 443;
        if (portConfig) {
            if (typeof portConfig.port !== 'undefined') {
                serverPort = portConfig.port;
            }
            if (typeof portConfig.sslPort !== 'undefined') {
                sslPort = portConfig.sslPort;
            }
        }
        return [serverPort, sslPort];
    };

    // SERVER CONFIGURATION
    // ====================
    (() => {
        server = express();
        server.use(express.compress());

        redirect(server);
        server.redirect('/', serverRoot); // TODO: make configurable? GH-8
        server.redirect(serverRoot, `${serverRoot}/`); // TODO: make configurable? GH-8

        ['get', 'put', 'post', 'delete'].forEach(method => {
            let mockCfg = projectConfig(`services/${method.toUpperCase()}.mock`);
            setupRESTMocks(mockCfg, setupRESTMock.bind(this, method));
        });

        setupProxying();

        server.use(serverRoot, express.static(staticFilesDirAbs, {redirect: false}));
        server.use('/', express.static(path.join(sourceFolder, '..'), {redirect: false})); // TODO: This grants access to the mix-n-mock project folder. Do we want this? GH-16

        server.use(express.errorHandler({
            dumpExceptions: true,
            showStack: true
        }));

        server.use(serverRoot, express.json());
        server.use(serverRoot, express.urlencoded());
        server.use(serverRoot, server.router);
    })();

    // SERVER
    // ======
    var ports = setupServerPort(configuredPort);
    var port = ports[0];
    var sslPort = ports[1];

    // Start Node.js Server
    var httpServer = http.createServer(server);
    httpServer.listen(port);
    if (sslPort > 0) {
        var httpsServer = https.createServer({key: privateKey, cert: certificate}, server);
        httpsServer.on('error', () => {
            console.warn(`Could not launch HTTPS server on port ${sslPort}! Try again as admin or use a different port.`);
            sslPort = -1;
        });
        httpsServer.listen(sslPort);
    }

    // get local IP addresses
    var getIp = () => {
        var interfaces = require('os').networkInterfaces();
        for (var device in interfaces) {
            if (interfaces.hasOwnProperty(device)) {
                var details = interfaces[device];
                for (var i = 0; i < details.length; i++) {
                    var detail = details[i];
                    if (detail.family === 'IPv4' && !detail.internal) {
                        return detail.address;
                    }
                }
            }
        }
        return null;
    };
    var ip = getIp();

    process.nextTick(() => {
        var urls = [`http://localhost:${port}${serverRoot}`];
        if (sslPort > 0) {
            urls.unshift(`https://localhost:${sslPort}${serverRoot}`);
        }
        if (ip) {
            urls.push(`http://${ip}:${port}${serverRoot}`);
        }

        console.log(unIndent `Welcome to mix-n-mock!

        Please go to ${urls.join(' or ')} to start using it.`);
    });
};
<|endoftext|>


<commit_before>/*
  Simple serial "one by one" sync/async promises based validation.
*/
import Promise from 'bluebird';
import React from 'react';
import validator from 'validator';

export class ValidationError extends Error {
  constructor(message: string, prop: string) {
    super();
    this.message = message;
    this.prop = prop;
  }
}

export function focusInvalidField(component) {
  return (error) => {
    if (error instanceof ValidationError) {
      if (!error.prop) return;
      const node = React.findDOMNode(component);
      if (!node) return;
      const el = node.querySelector(`[name=${error.prop}]`);
      if (!el) return;
      el.focus();
      return;
    }
    throw error;
  };
}

export default class Validation {

  constructor(object: Object) {
    this._object = object;
    this._prop = null;
    this._validator = validator;
    this.promise = Promise.resolve();
  }

  custom(callback: Function, {required} = {}) {
    const prop = this._prop;
    const value = this._object[prop];
    const object = this._object;
    this.promise = this.promise.then(() => {
      if (required && !this._isEmptyString(value)) return;
      callback(value, prop, object);
    });
    return this;
  }

  _isEmptyString(value) {
    return !this._validator.toString(value).trim();
  }

  prop(prop: string) {
    this._prop = prop;
    return this;
  }

  required() {
    return this.custom((value, prop) => {
      throw new ValidationError(
        this.getRequiredMessage(prop, value),
        prop
      );
    }, {required: true});
  }

  getRequiredMessage(prop, value) {
    return `Please fill out '${prop}' field.`;
  }

  email() {
    return this.custom((value, prop) => {
      if (this._validator.isEmail(value)) return;
      throw new ValidationError(
        this.getEmailMessage(prop, value),
        prop
      );
    });
  }

  getEmailMessage() {
    return `Email address is not valid.`;
  }

  simplePassword() {
    return this.custom((value, prop) => {
      const minLength = 5;
      if (value.length >= minLength) return;
      throw new ValidationError(
        this.getSimplePasswordMessage(minLength),
        prop
      );
    });
  }

  getSimplePasswordMessage(minLength) {
    return `Password must contain at least ${minLength} characters.`;
  }

}
<commit_msg>Add optional getRequiredMessage for validation.<commit_after>/*
  Simple serial "one by one" sync/async promises based validation.
*/
import Promise from 'bluebird';
import React from 'react';
import validator from 'validator';

export class ValidationError extends Error {
  constructor(message: string, prop: string) {
    super();
    this.message = message;
    this.prop = prop;
  }
}

export function focusInvalidField(component) {
  return (error) => {
    if (error instanceof ValidationError) {
      if (!error.prop) return;
      const node = React.findDOMNode(component);
      if (!node) return;
      const el = node.querySelector(`[name=${error.prop}]`);
      if (!el) return;
      el.focus();
      return;
    }
    throw error;
  };
}

export default class Validation {

  constructor(object: Object) {
    this._object = object;
    this._prop = null;
    this._validator = validator;
    this.promise = Promise.resolve();
  }

  custom(callback: Function, {required} = {}) {
    const prop = this._prop;
    const value = this._object[prop];
    const object = this._object;
    this.promise = this.promise.then(() => {
      if (required && !this._isEmptyString(value)) return;
      callback(value, prop, object);
    });
    return this;
  }

  _isEmptyString(value) {
    return !this._validator.toString(value).trim();
  }

  prop(prop: string) {
    this._prop = prop;
    return this;
  }

  required(getRequiredMessage?) {
    return this.custom((value, prop) => {
      const msg = getRequiredMessage
        ? getRequiredMessage(prop, value)
        : this.getRequiredMessage(prop, value);
      throw new ValidationError(msg, prop);
    }, {required: true});
  }

  getRequiredMessage(prop, value) {
    return `Please fill out '${prop}' field.`;
  }

  email() {
    return this.custom((value, prop) => {
      if (this._validator.isEmail(value)) return;
      throw new ValidationError(
        this.getEmailMessage(prop, value),
        prop
      );
    });
  }

  getEmailMessage() {
    return `Email address is not valid.`;
  }

  simplePassword() {
    return this.custom((value, prop) => {
      const minLength = 5;
      if (value.length >= minLength) return;
      throw new ValidationError(
        this.getSimplePasswordMessage(minLength),
        prop
      );
    });
  }

  getSimplePasswordMessage(minLength) {
    return `Password must contain at least ${minLength} characters.`;
  }

}
<|endoftext|>


<commit_before>'use strict';

import Rect from 'flockn/types/rect';
import Vector2 from 'flockn/types/vector2';

describe('flockn/types/rect', function () {

  it('is a function', function () {
    expect(Rect).to.be.a('function');
  });

  describe('constructor', function () {
    var rect = new Rect();

    it('can be instantiated', function () {
      expect(rect).to.be.a('object');
      expect(rect).to.be.an.instanceOf(Rect);
    });

    it('default values', function() {
      expect(rect.x).to.be.a('number');
      expect(rect.y).to.be.a('number');
      expect(rect.w).to.be.a('number');
      expect(rect.h).to.be.a('number');

      expect(rect.x).to.equal(0);
      expect(rect.y).to.equal(0);
      expect(rect.w).to.equal(0);
      expect(rect.h).to.equal(0);
    });

    it('custom values', function() {
      var rect2 = new Rect(10, 10, 50, 50);

      expect(rect2.x).to.be.a('number');
      expect(rect2.y).to.be.a('number');
      expect(rect2.w).to.be.a('number');
      expect(rect2.h).to.be.a('number');

      expect(rect2.x).to.equal(10);
      expect(rect2.y).to.equal(10);
      expect(rect2.w).to.equal(50);
      expect(rect2.h).to.equal(50);
    });
  });

  describe('#center', function() {
    var rect = new Rect();

    it('x value should return a number', () => expect(rect.center().x).to.be.a('number'));
    it('y value should return a number', () => expect(rect.center().y).to.be.a('number'));
    it('x value should be 0 if x, y, w and h are 0', () => expect(rect.center().x).to.equal(0));
    it('y value should be 0 if x, y, w and h are 0', () => expect(rect.center().y).to.equal(0));
  });

  describe('#toJSON', function() {
    var rect = new Rect();
    var rect2 = new Rect(20, 20, 40, 40);

    it('stringified empty rect', () => expect(rect.toJSON()).to.deep.equal({x: 0, y: 0, w: 0, h: 0}));
    it('stringified custom rect', () => expect(rect2.toJSON()).to.deep.equal({x: 20, y: 20, w: 40, h: 40}));
  });

  describe('#toString', function() {
    var rect = new Rect();
    var rect2 = new Rect(20, 20, 40, 40);

    it('stringified empty rect', () => expect(rect.toString()).to.equal(JSON.stringify({x: 0, y: 0, w: 0, h: 0})));
    it('stringified custom rect', () => expect(rect2.toString()).to.equal(JSON.stringify({x: 20, y: 20, w: 40, h: 40})));
  });

  describe('#contains', function() {
    var rect = new Rect(0, 0, 20, 20);
    var rect2 = new Rect(50, 50, 30, 30);
    var vector = new Vector2(10, 10);

    it('vector contained in rect', () => expect(rect.contains(vector)).to.equal(true));
    it('vector not contained in rect', () => expect(rect2.contains(vector)).to.equal(false));
  });

  describe('#center', function () {
    var rect = new Rect(10, 10, 40, 40);
    var vector = rect.center();

    it('returns a vector', () => expect(vector).to.be.an.instanceOf(Vector2));
    it('centers the rect', () => {
      expect(vector.x).to.equal(30);
      expect(vector.y).to.equal(30);
    });
  });

  describe('.fromString', function () {
    var obj = {x: 10, y: 10, w: 50, h: 50};
    var string = JSON.stringify(obj);

    var rect = Rect.fromString(string);

    it('is a rect', () => expect(rect).to.be.an.instanceOf(Rect));
    it('has the correct values', () => {
      expect(rect.x).to.equal(10);
      expect(rect.y).to.equal(10);
      expect(rect.w).to.equal(50);
      expect(rect.h).to.equal(50);
    });
  });
});
<commit_msg>:ok: Fixes dependency<commit_after>'use strict';

import {Rect, Vector2} from 'flockn/types';

describe('flockn/types/rect', function () {

  it('is a function', function () {
    expect(Rect).to.be.a('function');
  });

  describe('constructor', function () {
    var rect = new Rect();

    it('can be instantiated', function () {
      expect(rect).to.be.a('object');
      expect(rect).to.be.an.instanceOf(Rect);
    });

    it('default values', function() {
      expect(rect.x).to.be.a('number');
      expect(rect.y).to.be.a('number');
      expect(rect.w).to.be.a('number');
      expect(rect.h).to.be.a('number');

      expect(rect.x).to.equal(0);
      expect(rect.y).to.equal(0);
      expect(rect.w).to.equal(0);
      expect(rect.h).to.equal(0);
    });

    it('custom values', function() {
      var rect2 = new Rect(10, 10, 50, 50);

      expect(rect2.x).to.be.a('number');
      expect(rect2.y).to.be.a('number');
      expect(rect2.w).to.be.a('number');
      expect(rect2.h).to.be.a('number');

      expect(rect2.x).to.equal(10);
      expect(rect2.y).to.equal(10);
      expect(rect2.w).to.equal(50);
      expect(rect2.h).to.equal(50);
    });
  });

  describe('#center', function() {
    var rect = new Rect();

    it('x value should return a number', () => expect(rect.center().x).to.be.a('number'));
    it('y value should return a number', () => expect(rect.center().y).to.be.a('number'));
    it('x value should be 0 if x, y, w and h are 0', () => expect(rect.center().x).to.equal(0));
    it('y value should be 0 if x, y, w and h are 0', () => expect(rect.center().y).to.equal(0));
  });

  describe('#toJSON', function() {
    var rect = new Rect();
    var rect2 = new Rect(20, 20, 40, 40);

    it('stringified empty rect', () => expect(rect.toJSON()).to.deep.equal({x: 0, y: 0, w: 0, h: 0}));
    it('stringified custom rect', () => expect(rect2.toJSON()).to.deep.equal({x: 20, y: 20, w: 40, h: 40}));
  });

  describe('#toString', function() {
    var rect = new Rect();
    var rect2 = new Rect(20, 20, 40, 40);

    it('stringified empty rect', () => expect(rect.toString()).to.equal(JSON.stringify({x: 0, y: 0, w: 0, h: 0})));
    it('stringified custom rect', () => expect(rect2.toString()).to.equal(JSON.stringify({x: 20, y: 20, w: 40, h: 40})));
  });

  describe('#contains', function() {
    var rect = new Rect(0, 0, 20, 20);
    var rect2 = new Rect(50, 50, 30, 30);
    var vector = new Vector2(10, 10);

    it('vector contained in rect', () => expect(rect.contains(vector)).to.equal(true));
    it('vector not contained in rect', () => expect(rect2.contains(vector)).to.equal(false));
  });

  describe('#center', function () {
    var rect = new Rect(10, 10, 40, 40);
    var vector = rect.center();

    it('returns a vector', () => expect(vector).to.be.an.instanceOf(Vector2));
    it('centers the rect', () => {
      expect(vector.x).to.equal(30);
      expect(vector.y).to.equal(30);
    });
  });

  describe('.fromString', function () {
    var obj = {x: 10, y: 10, w: 50, h: 50};
    var string = JSON.stringify(obj);

    var rect = Rect.fromString(string);

    it('is a rect', () => expect(rect).to.be.an.instanceOf(Rect));
    it('has the correct values', () => {
      expect(rect.x).to.equal(10);
      expect(rect.y).to.equal(10);
      expect(rect.w).to.equal(50);
      expect(rect.h).to.equal(50);
    });
  });
});
<|endoftext|>


<commit_before>'use strict';

/**
 * Data Access Object.
 *
 * All functions that return a Promise of data will reject that Promise if no data is found.
 *
 * @module dao
 * @license MIT
 * @author RaceProUK
 */

const sqlite3 = require('sqlite3').verbose();

const Board = require('./model/Board');
const Game = require('./model/Game');

const async = require('async');

let db;

let initialised = false;

module.exports = {
	initialise: initialise,
	teardown: teardown,
	isInitialised: isInitialised,
	//User operations
	getAllUsers: getAllUsers,
	getUser: getUser,
	getUserByName: getUserByName,
	addUser: addUser,
	updateUser: updateUser,
	//Board operations
	getAllBoards: getAllBoards,
	getBoards: getBoards,
	getBoard: getBoard,
	addBoard: addBoard,
	updateBoard: updateBoard,
	//Game operations
	getAllGames: getAllGames,
	getGames: getGames,
	getGame: getGame,
	addGame: addGame,
	updateGame: updateGame
};

/**
 * Initialise the DAO. *Must be called before any other function.*
 *
 * @param {Object} config The necessary information to set up the database
 * @param {String} config.sqlite The location of the SQLite file to use; if it doesn't exist, it'll be created
 * @param {Object} config.postgres The necessary information to connect to a PostgreSQL database
 * @param {String} config.postgres.host The host running the PostGreSQL server
 * @param {String} config.postgres.name The name of the database to connect to
 * @param {String} config.postgres.username The username of the user to connect as
 * @param {String} config.postgres.password The password of the user to connect as
 *
 * @returns {Promise} A Promise that is resolved when the DAO is initialised.
 */
function initialise() {
	if (initialised) {
		return Promise.resolve();
	}

	console.log('initalising')
	db = new sqlite3.Database(':memory:');

	return new Promise((resolve, reject) => {

		async.series([
			(callback) => db.run('CREATE TABLE IF NOT EXISTS Games (id INTEGER PRIMARY KEY, dummyColumn TEXT)', callback),
			(callback) => db.run('CREATE TABLE IF NOT EXISTS Users (id INTEGER PRIMARY KEY, Username TEXT)', callback),
			(callback) => db.run('CREATE TABLE IF NOT EXISTS Boards (id INTEGER PRIMARY KEY, Owner INTEGER, GameID INTEGER, Name TEXT, FOREIGN KEY(GameID) REFERENCES Game(id))', callback),
			(callback) => db.run('CREATE TABLE IF NOT EXISTS ChildBoards (ParentID INTEGER, ChildID INTEGER, FOREIGN KEY(ParentID) REFERENCES Board(id), FOREIGN KEY(ChildID) REFERENCES Board(id))', callback),
			//	db.run('CREATE TABLE IF NOT EXISTS GameMasters (UserID INTEGER, GameID INTEGER, FOREIGN KEY(GameID) REFERENCES Game(id), FOREIGN KEY(UserID) REFERENCES Users(id))')
			],
			(err, results) => {
				if (err) {
					db = null;
					reject(err);
				} else {
					initialised = true;
					resolve(true);
				}
			}
		);
	});
}

/**
 * Tears down the DAO. *The DAO must be reinitialised before it can be used again.*
 *
 * @returns {Promise} A Promise that is resolved when the DAO is torn down.
 */
function teardown() {
	db = null;
	initialised = false;

	return Promise.resolve();
}

/**
 * Reports if the DAO is initialised.
 *
 * @returns {boolean} Flag indicating whether the DAO is initialised.
 */
function isInitialised() {
	return initialised;
}

/**
 * Get all users in the forum.
 *
 * @returns {Promise} A Promise that is resolved with a list of users
 */
function getAllUsers() {
	return new Promise((resolve, reject) => {
		db.all('SELECT * FROM Users', (err, rows) => {
			if (err) {
				reject(err);
			} else {
				resolve(rows); //TODO: Make a model instead of a bare object
			}
		});
	});
}

/**
 * Get a user by ID.
 *
 * @param {Number} id The ID of the user requested
 *
 * @returns {Promise} A Promise that is resolved with the user requested
 */
function getUser(id) {
	return new Promise((resolve, reject) => {
		db.get('SELECT * FROM Users WHERE id = ?', id, (err, row) => {
			if (err) {
				reject(err);
			} else {
				resolve(row);
			}
		});
	});
}

/**
 * Get a user by name.
 *
 * @param {String} name The name of the user requested
 *
 * @returns {Promise} A Promise that is resolved with the user requested
 */
function getUserByName(name) {
	return Promise.reject('Not yet implemented');
}

/**
 * Add a user.
 *
 * @param {Object} user The user to add
 *
 * @returns {Promise} A Promise that is resolved with the user added
 */
function addUser(user) {
	return new Promise((resolve, reject) => {
		db.run('INSERT INTO Users (Username) VALUES (?)', user.Username, (err) => {
			if (err) {
				reject(err);
			} else {
				resolve(true);
			}
		});
	});
}

/**
 * Update a user.
 *
 * @param {Number} id The ID of the user to update
 * @param {Object} user The details to update the user with
 *
 * @returns {Promise} A Promise that is resolved with the user updated
 */
function updateUser(id, user) {
	return Promise.reject('Not yet implemented');
}

/**
 * Get all vanilla boards in the forum.
 *
 * @returns {Promise} A Promise that is resolved with a list of vanilla boards
 */
function getAllBoards() {
	return new Promise((resolve, reject) => {
		db.all('SELECT * FROM Boards', (err, rows) => {
			if (err) {
				reject(err);
			} else {
				resolve(rows.map((row) => {
					return new Board(row);
				}));
			}
		});
	});
}

/**
 * Get all vanilla boards that belong to a parent board, or all root-level vanilla boards if no parent specified.
 *
 * @param {Number} [parentID] The ID of the parent board, or `null` for root-level boards
 *
 * @returns {Promise} A Promise that is resolved with a list of vanilla boards
 */
function getBoards(parentID) {
	if (parentID !== 0) {
		parentID = parentID || null; //Coerce to null to prevent avoidable errors
	}

	return new Promise((resolve, reject) => {
		db.all('SELECT id, Owner, Name FROM Boards INNER JOIN ChildBoards ON Boards.id = ChildBoards.ChildID WHERE ChildBoards.parentID = ? AND GameID IS NULL', parentID, (err, rows) => {
			if (err) {
				reject(err);
			} else {
				resolve(rows.map((row) => {
					return new Board(row);
				}));
			}
		});
	});
}

/**
 * Get a vanilla board by ID.
 *
 * @param {Number} id The ID of the board requested
 *
 * @returns {Promise} A Promise that is resolved with the board requested
 */
function getBoard(id) {
	return new Promise((resolve, reject) => {
		db.get('SELECT id, Owner, Name FROM Boards WHERE id = ? AND GameID IS NULL', id, (err, row) => {
			if (err) {
				reject(err);
			} else {
				if (row) {
					resolve(new Board(row));
				} else {
					resolve(null);
				}
			}
		});
	});
}

/**
 * Add a vanilla board.
 *
 * @param {Object} board The board to add
 *
 * @returns {Promise} A Promise that is resolved with the board added
 */
function addBoard(board) {
	return new Promise((resolve, reject) => {
		if (board.GameID || board.Game) {
			reject(new Error('Games cannot be added using this method; please use addGame() instead'));
		} else {
			resolve();
		}
	}).then(() => {
		if (!board.Name) {
			throw new Error('A board has no name.');
		}
	})
	.then(() => {
		return new Promise((resolve, reject) => {
			db.run('INSERT INTO Boards (Owner, Name) VALUES (?,?)', board.Owner, board.Name, function (err) {
				if (err) {
					reject(err);
				} else {
					resolve(module.exports.getBoard(this.lastID));
				}
			});
		});
	});
}

/**
 * Update a vanilla board.
 *
 * @param {Number} id The ID of the board to update
 * @param {Object} board The details to update the board with
 *
 * @returns {Promise} A Promise that is resolved with the board updated
 */
function updateBoard(id, board) {
	return new Promise((resolve, reject) => {
		if (board.GameID || board.Game) {
			reject(new Error('Games cannot be updated using this method; please use updateGame() instead'));
		} else {
			resolve();
		}
	}).then(() => {
		return new Promise((resolve, reject) => {
			db.run('UPDATE Boards SET Owner=?, Name=? WHERE id=?', board.Owner, board.Name, board.id, function (err) {
				if (err) {
					reject(err);
				} else {
					resolve(module.exports.getBoard(this.lastID));
				}
			});
		});
	});
}

/**
 * Get all games in the forum.
 *
 * @returns {Promise} A Promise that is resolved with a list of games
 */
function getAllGames() {
	return new Promise((resolve, reject) => {
		db.all('SELECT * FROM Boards INNER JOIN Games ON Boards.GameID = Games.id', (err, rows) => {
			if (err) {
				reject(err);
			} else {
				resolve(rows.map((row) => {
					return new Game(row);
				}));
			}
		});
	});
}

/**
 * Get all games that belong to a parent game, or all root-level games if no parent specified.
 *
 * @param {Number} [parentID] The ID of the parent game, or `null` for root-level games
 *
 * @returns {Promise} A Promise that is resolved with a list of games
 */
function getGames(parentID) {
	if (parentID !== 0) {
		parentID = parentID || null; //Coerce to null to prevent avoidable errors
	}
	return new Promise((resolve, reject) => {
		db.all('SELECT Boards.id, Owner, Name FROM Boards INNER JOIN ChildBoards ON Boards.id = ChildBoards.ChildID WHERE ChildBoards.parentID = ?', parentID, (err, rows) => {
			if (err) {
				reject(err);
			} else {
				resolve(rows.map((row) => {
					return new Game(row);
				}));
			}
		});
	});
}

/**
 * Get a game by ID.
 *
 * @param {Number} id The ID of the game requested
 *
 * @returns {Promise} A Promise that is resolved with the game requested
 */
function getGame(id) {
	return new Promise((resolve, reject) => {
		db.get('SELECT Boards.id, Owner, Name, GameID FROM Boards INNER JOIN Games ON Boards.GameID = Games.id WHERE Games.id = ? AND GameID IS NOT NULL', id, (err, row) => {
			if (err) {
				reject(err);
			} else {
				if (row) {
					resolve(new Game(row));
				} else {
					resolve(null);
				}
			}
		});
	});
}

/**
 * Add a game.
 *
 * @param {Object} game The game to add
 *
 * @returns {Promise} A Promise that is resolved with the game added
 */
function addGame(game) {
	return new Promise((resolve, reject) => {
		if (!game.GameID && !game.Game) {
			reject(new Error('Vanilla boards cannot be added using this method; please use addBoard() instead'));
		} else {
			resolve();
		}
	}).then(() => {
		//TODO: this is probably wrong

		return new Promise((resolve, reject) => {
			async.waterfall([
				(callback) => db.run('INSERT INTO Games (dummyColumn) VALUES ("hi")', function(err) {
						if (err) {
							callback(err);
						} else {
							callback(null, this.lastID)
						}
					}),
				(gameID, callback) => db.run('INSERT INTO BOARDS (Owner, Name, GameID) VALUES (?,?,?)', game.Owner, game.Name, gameID, function(err) {
						if (err) {
							callback(err);
						} else {
							callback(null, this.lastID)
						}
					})
				],
				function (err, ID) {
					if (err) {
						reject(err);
					} else {
						resolve(module.exports.getGame(ID));
					}
				}
			);
		});
	});
}

/**
 * Update a game.
 *
 * @param {Number} id The ID of the game to update
 * @param {Object} game The details to update the game with
 *
 * @returns {Promise} A Promise that is resolved with the game updated
 */
function updateGame(id, game) {
	return new Promise((resolve, reject) => {
		if (!game.GameID && !game.Game) {
			reject(new Error('Vanilla boards cannot be updated using this method; please use updateBoard() instead'));
		} else {
			resolve();
		}
	}).then(() => {
		//TODO: this is probably wrong
		return new Promise((resolve, reject) => {
			db.run('UPDATE Boards SET Owner=?, Name=? WHERE id=?', game.Owner, game.Name, id, function (err) {
				if (err) {
					reject(err);
				} else {
					resolve(module.exports.getGame(this.lastID));
				}
			});
		});
	});
}
<commit_msg>Fix: Fixed for editing nonexistant games/boards<commit_after>'use strict';

/**
 * Data Access Object.
 *
 * All functions that return a Promise of data will reject that Promise if no data is found.
 *
 * @module dao
 * @license MIT
 * @author RaceProUK
 */

const sqlite3 = require('sqlite3').verbose();

const Board = require('./model/Board');
const Game = require('./model/Game');

const async = require('async');

let db;

let initialised = false;

module.exports = {
	initialise: initialise,
	teardown: teardown,
	isInitialised: isInitialised,
	//User operations
	getAllUsers: getAllUsers,
	getUser: getUser,
	getUserByName: getUserByName,
	addUser: addUser,
	updateUser: updateUser,
	//Board operations
	getAllBoards: getAllBoards,
	getBoards: getBoards,
	getBoard: getBoard,
	addBoard: addBoard,
	updateBoard: updateBoard,
	//Game operations
	getAllGames: getAllGames,
	getGames: getGames,
	getGame: getGame,
	addGame: addGame,
	updateGame: updateGame
};

/**
 * Initialise the DAO. *Must be called before any other function.*
 *
 * @param {Object} config The necessary information to set up the database
 * @param {String} config.sqlite The location of the SQLite file to use; if it doesn't exist, it'll be created
 * @param {Object} config.postgres The necessary information to connect to a PostgreSQL database
 * @param {String} config.postgres.host The host running the PostGreSQL server
 * @param {String} config.postgres.name The name of the database to connect to
 * @param {String} config.postgres.username The username of the user to connect as
 * @param {String} config.postgres.password The password of the user to connect as
 *
 * @returns {Promise} A Promise that is resolved when the DAO is initialised.
 */
function initialise() {
	if (initialised) {
		return Promise.resolve();
	}

	console.log('initalising')
	db = new sqlite3.Database(':memory:');

	return new Promise((resolve, reject) => {

		async.series([
			(callback) => db.run('CREATE TABLE IF NOT EXISTS Games (id INTEGER PRIMARY KEY, dummyColumn TEXT)', callback),
			(callback) => db.run('CREATE TABLE IF NOT EXISTS Users (id INTEGER PRIMARY KEY, Username TEXT)', callback),
			(callback) => db.run('CREATE TABLE IF NOT EXISTS Boards (id INTEGER PRIMARY KEY, Owner INTEGER, GameID INTEGER, Name TEXT, FOREIGN KEY(GameID) REFERENCES Game(id))', callback),
			(callback) => db.run('CREATE TABLE IF NOT EXISTS ChildBoards (ParentID INTEGER, ChildID INTEGER, FOREIGN KEY(ParentID) REFERENCES Board(id), FOREIGN KEY(ChildID) REFERENCES Board(id))', callback),
			//	db.run('CREATE TABLE IF NOT EXISTS GameMasters (UserID INTEGER, GameID INTEGER, FOREIGN KEY(GameID) REFERENCES Game(id), FOREIGN KEY(UserID) REFERENCES Users(id))')
			],
			(err, results) => {
				if (err) {
					db = null;
					reject(err);
				} else {
					initialised = true;
					resolve(true);
				}
			}
		);
	});
}

/**
 * Tears down the DAO. *The DAO must be reinitialised before it can be used again.*
 *
 * @returns {Promise} A Promise that is resolved when the DAO is torn down.
 */
function teardown() {
	db = null;
	initialised = false;

	return Promise.resolve();
}

/**
 * Reports if the DAO is initialised.
 *
 * @returns {boolean} Flag indicating whether the DAO is initialised.
 */
function isInitialised() {
	return initialised;
}

/**
 * Get all users in the forum.
 *
 * @returns {Promise} A Promise that is resolved with a list of users
 */
function getAllUsers() {
	return new Promise((resolve, reject) => {
		db.all('SELECT * FROM Users', (err, rows) => {
			if (err) {
				reject(err);
			} else {
				resolve(rows); //TODO: Make a model instead of a bare object
			}
		});
	});
}

/**
 * Get a user by ID.
 *
 * @param {Number} id The ID of the user requested
 *
 * @returns {Promise} A Promise that is resolved with the user requested
 */
function getUser(id) {
	return new Promise((resolve, reject) => {
		db.get('SELECT * FROM Users WHERE id = ?', id, (err, row) => {
			if (err) {
				reject(err);
			} else {
				resolve(row);
			}
		});
	});
}

/**
 * Get a user by name.
 *
 * @param {String} name The name of the user requested
 *
 * @returns {Promise} A Promise that is resolved with the user requested
 */
function getUserByName(name) {
	return Promise.reject('Not yet implemented');
}

/**
 * Add a user.
 *
 * @param {Object} user The user to add
 *
 * @returns {Promise} A Promise that is resolved with the user added
 */
function addUser(user) {
	return new Promise((resolve, reject) => {
		db.run('INSERT INTO Users (Username) VALUES (?)', user.Username, (err) => {
			if (err) {
				reject(err);
			} else {
				resolve(true);
			}
		});
	});
}

/**
 * Update a user.
 *
 * @param {Number} id The ID of the user to update
 * @param {Object} user The details to update the user with
 *
 * @returns {Promise} A Promise that is resolved with the user updated
 */
function updateUser(id, user) {
	return Promise.reject('Not yet implemented');
}

/**
 * Get all vanilla boards in the forum.
 *
 * @returns {Promise} A Promise that is resolved with a list of vanilla boards
 */
function getAllBoards() {
	return new Promise((resolve, reject) => {
		db.all('SELECT * FROM Boards', (err, rows) => {
			if (err) {
				reject(err);
			} else {
				resolve(rows.map((row) => {
					return new Board(row);
				}));
			}
		});
	});
}

/**
 * Get all vanilla boards that belong to a parent board, or all root-level vanilla boards if no parent specified.
 *
 * @param {Number} [parentID] The ID of the parent board, or `null` for root-level boards
 *
 * @returns {Promise} A Promise that is resolved with a list of vanilla boards
 */
function getBoards(parentID) {
	if (parentID !== 0) {
		parentID = parentID || null; //Coerce to null to prevent avoidable errors
	}

	return new Promise((resolve, reject) => {
		db.all('SELECT id, Owner, Name FROM Boards INNER JOIN ChildBoards ON Boards.id = ChildBoards.ChildID WHERE ChildBoards.parentID = ? AND GameID IS NULL', parentID, (err, rows) => {
			if (err) {
				reject(err);
			} else {
				resolve(rows.map((row) => {
					return new Board(row);
				}));
			}
		});
	});
}

/**
 * Get a vanilla board by ID.
 *
 * @param {Number} id The ID of the board requested
 *
 * @returns {Promise} A Promise that is resolved with the board requested
 */
function getBoard(id) {
	return new Promise((resolve, reject) => {
		db.get('SELECT id, Owner, Name FROM Boards WHERE id = ? AND GameID IS NULL', id, (err, row) => {
			if (err) {
				reject(err);
			} else {
				if (row) {
					resolve(new Board(row));
				} else {
					resolve(null);
				}
			}
		});
	});
}

/**
 * Add a vanilla board.
 *
 * @param {Object} board The board to add
 *
 * @returns {Promise} A Promise that is resolved with the board added
 */
function addBoard(board) {
	return new Promise((resolve, reject) => {
		if (board.GameID || board.Game) {
			reject(new Error('Games cannot be added using this method; please use addGame() instead'));
		} else {
			resolve();
		}
	}).then(() => {
		if (!board.Name) {
			throw new Error('A board has no name.');
		}
	})
	.then(() => {
		return new Promise((resolve, reject) => {
			db.run('INSERT INTO Boards (Owner, Name) VALUES (?,?)', board.Owner, board.Name, function (err) {
				if (err) {
					reject(err);
				} else {
					resolve(module.exports.getBoard(this.lastID));
				}
			});
		});
	});
}

/**
 * Update a vanilla board.
 *
 * @param {Number} id The ID of the board to update
 * @param {Object} board The details to update the board with
 *
 * @returns {Promise} A Promise that is resolved with the board updated
 */
function updateBoard(id, board) {
	return new Promise((resolve, reject) => {
		if (board.GameID || board.Game) {
			reject(new Error('Games cannot be updated using this method; please use updateGame() instead'));
		} else {
			resolve();
		}
	}).then(() => {
		return new Promise((resolve, reject) => {
			db.run('UPDATE Boards SET Owner=?, Name=? WHERE id=?', board.Owner, board.Name, id, function (err) {
				if (err) {
					reject(err);
				} else {
					if (this.changes <= 0) {
						reject(new Error('No such board'));
					} else {
						resolve(module.exports.getBoard(this.lastID));
					}
				}
			});
		});
	});
}

/**
 * Get all games in the forum.
 *
 * @returns {Promise} A Promise that is resolved with a list of games
 */
function getAllGames() {
	return new Promise((resolve, reject) => {
		db.all('SELECT * FROM Boards INNER JOIN Games ON Boards.GameID = Games.id', (err, rows) => {
			if (err) {
				reject(err);
			} else {
				resolve(rows.map((row) => {
					return new Game(row);
				}));
			}
		});
	});
}

/**
 * Get all games that belong to a parent game, or all root-level games if no parent specified.
 *
 * @param {Number} [parentID] The ID of the parent game, or `null` for root-level games
 *
 * @returns {Promise} A Promise that is resolved with a list of games
 */
function getGames(parentID) {
	if (parentID !== 0) {
		parentID = parentID || null; //Coerce to null to prevent avoidable errors
	}
	return new Promise((resolve, reject) => {
		db.all('SELECT Boards.id, Owner, Name FROM Boards INNER JOIN ChildBoards ON Boards.id = ChildBoards.ChildID WHERE ChildBoards.parentID = ?', parentID, (err, rows) => {
			if (err) {
				reject(err);
			} else {
				resolve(rows.map((row) => {
					return new Game(row);
				}));
			}
		});
	});
}

/**
 * Get a game by ID.
 *
 * @param {Number} id The ID of the game requested
 *
 * @returns {Promise} A Promise that is resolved with the game requested
 */
function getGame(id) {
	return new Promise((resolve, reject) => {
		db.get('SELECT Boards.id, Owner, Name, GameID FROM Boards INNER JOIN Games ON Boards.GameID = Games.id WHERE Games.id = ? AND GameID IS NOT NULL', id, (err, row) => {
			if (err) {
				reject(err);
			} else {
				if (row) {
					resolve(new Game(row));
				} else {
					resolve(null);
				}
			}
		});
	});
}

/**
 * Add a game.
 *
 * @param {Object} game The game to add
 *
 * @returns {Promise} A Promise that is resolved with the game added
 */
function addGame(game) {
	return new Promise((resolve, reject) => {
		if (!game.GameID && !game.Game) {
			reject(new Error('Vanilla boards cannot be added using this method; please use addBoard() instead'));
		} else {
			resolve();
		}
	}).then(() => {
		//TODO: this is probably wrong

		return new Promise((resolve, reject) => {
			async.waterfall([
				(callback) => db.run('INSERT INTO Games (dummyColumn) VALUES ("hi")', function(err) {
						if (err) {
							callback(err);
						} else {
							callback(null, this.lastID)
						}
					}),
				(gameID, callback) => db.run('INSERT INTO BOARDS (Owner, Name, GameID) VALUES (?,?,?)', game.Owner, game.Name, gameID, function(err) {
						if (err) {
							callback(err);
						} else {
							callback(null, this.lastID)
						}
					})
				],
				function (err, ID) {
					if (err) {
						reject(err);
					} else {
						resolve(module.exports.getGame(ID));
					}
				}
			);
		});
	});
}

/**
 * Update a game.
 *
 * @param {Number} id The ID of the game to update
 * @param {Object} game The details to update the game with
 *
 * @returns {Promise} A Promise that is resolved with the game updated
 */
function updateGame(id, game) {
	return new Promise((resolve, reject) => {
		if (!game.GameID && !game.Game) {
			reject(new Error('Vanilla boards cannot be updated using this method; please use updateBoard() instead'));
		} else {
			resolve();
		}
	}).then(() => {
		//TODO: this is probably wrong
		return new Promise((resolve, reject) => {
			db.run('UPDATE Boards SET Owner=?, Name=? WHERE id=?', game.Owner, game.Name, id, function (err) {
				if (err) {
					reject(err);
				} else {
					if (this.changes <= 0) {
						reject(new Error('No such board'));
					} else {
						resolve(module.exports.getGame(this.lastID));
					}
				}
			});
		});
	});
}
<|endoftext|>