<commit_before>'use strict';

// MODULES //

var tape = require( 'tape' );
var kstest = require( '@stdlib/math/generics/statistics/kstest' );
var round = require( '@stdlib/math/base/special/round' );
var isnan = require( '@stdlib/math/base/utils/is-nan' );
var factory = require( './../lib/factory.js' );

// VARIABLES //

var opts = {
	'skip': ( process.env.TEST_MODE === 'coverage' )
};

// TESTS //

tape( 'main export is a function', function test( t ) {
	t.ok( true, __filename );
	t.equal( typeof factory, 'function', 'main export is a function' );
	t.end();
});

tape( 'the function returns a pseudorandom number generator (no seed)', function test( t ) {
	var beta;
	var r;
	var i;

	// When binding distribution parameters...
	beta = factory( 2.0, 2.0 );
	for ( i = 0; i < 400; i++ ) {
		r = beta();
		t.equal( typeof r, 'number', 'returns a number' );
	}
	beta = factory( 30.0, 30.0 );
	for ( i = 0; i < 400; i++ ) {
		r = beta();
		t.equal( typeof r, 'number', 'returns a number' );
	}

	// Without binding distribution parameters...
	beta = factory();
	for ( i = 0; i < 100; i++ ) {
		r = beta( 2.5, 2.0 );
		t.equal( typeof r, 'number', 'returns a number' );
	}

	t.end();
});

tape( 'the function returns a seeded pseudorandom number generator (alpha,beta <= 1.0)', function test( t ) {
	var beta1;
	var beta2;
	var seed;
	var r1;
	var r2;
	var i;

	seed = round( Date.now() / 1000 );

	beta1 = factory( 0.5, 0.5, {
		'seed': seed
	});
	beta2 = factory( 0.5, 0.5, {
		'seed': seed
	});

	t.notEqual( beta1, beta2, 'separate generators' );

	for ( i = 0; i < 1e3; i++ ) {
		r1 = beta1();
		r2 = beta2();
		t.equal( r1, r2, 'both return same number: '+r1 );
	}
	t.end();
});

tape( 'the function returns a seeded pseudorandom number generator (alpha = beta > 1.5)', function test( t ) {
	var beta1;
	var beta2;
	var seed;
	var r1;
	var r2;
	var i;

	seed = round( Date.now() / 1000 );

	beta1 = factory( 3.14, 3.14, {
		'seed': seed
	});
	beta2 = factory( 3.14, 3.14, {
		'seed': seed
	});

	t.notEqual( beta1, beta2, 'separate generators' );

	for ( i = 0; i < 1e3; i++ ) {
		r1 = beta1();
		r2 = beta2();
		t.equal( r1, r2, 'both return same number: '+r1 );
	}
	t.end();
});

tape( 'the function returns a seeded pseudorandom number generator (alpha != beta > 1.0)', function test( t ) {
	var beta1;
	var beta2;
	var seed;
	var r1;
	var r2;
	var i;

	seed = round( Date.now() / 1000 );

	beta1 = factory( 3.14, 1.25, {
		'seed': seed
	});
	beta2 = factory( 3.14, 1.25, {
		'seed': seed
	});

	t.notEqual( beta1, beta2, 'separate generators' );

	for ( i = 0; i < 1e3; i++ ) {
		r1 = beta1();
		r2 = beta2();
		t.equal( r1, r2, 'both return same number: '+r1 );
	}
	t.end();
});

tape( 'attached to the returned function is the generator name', function test( t ) {
	var beta = factory();
	t.equal( beta.NAME, 'beta', 'has `NAME` property' );
	t.end();
});

tape( 'attached to the returned function is the underlying PRNG', function test( t ) {
	var beta = factory();
	t.equal( typeof beta.PRNG, 'function', 'has `PRNG` property' );
	t.end();
});

tape( 'attached to the returned function is the generator seed', function test( t ) {
	var beta = factory({
		'seed': 12345
	});
	t.equal( typeof beta.SEED, 'number', 'has `SEED` property' );
	t.equal( beta.SEED, 12345, 'equal to provided seed' );
	t.end();
});

tape( 'the function throws an error if `alpha` is not a positive number', function test( t ) {
	var values;
	var i;

	values = [
		-2.0,
		0.0,
		'5',
		null,
		true,
		undefined,
		NaN,
		[],
		{},
		function(){}
	];

	for ( i = 0; i < values.length; i++ ) {
		t.throws( badValue( values[i] ), TypeError, 'throws an error when provided '+values[i] );
	}
	t.end();

	function badValue( value ) {
		return function() {
			factory( value, 2.0 );
		};
	}
});

tape( 'the function throws an error if `beta` is not a positive number', function test( t ) {
	var values;
	var i;

	values = [
		-2.0,
		0.0,
		'5',
		null,
		true,
		undefined,
		NaN,
		[],
		{},
		function(){}
	];

	for ( i = 0; i < values.length; i++ ) {
		t.throws( badValue( values[i] ), TypeError, 'throws an error when provided '+values[i] );
	}
	t.end();

	function badValue( value ) {
		return function() {
			factory( 2.0, value );
		};
	}
});

tape( 'the function throws an error if provided an options argument which is not an object (no other arguments)', function test( t ) {
	var values;
	var i;

	values = [
		'abc',
		5,
		null,
		true,
		undefined,
		NaN,
		[],
		function(){}
	];

	for ( i = 0; i < values.length; i++ ) {
		t.throws( badValue( values[i] ), TypeError, 'throws an error when provided '+values[i] );
	}
	t.end();

	function badValue( value ) {
		return function() {
			factory( value );
		};
	}
});

tape( 'the function throws an error if provided an options argument which is not an object (other arguments)', function test( t ) {
	var values;
	var i;

	values = [
		'abc',
		5,
		null,
		true,
		undefined,
		NaN,
		[],
		function(){}
	];

	for ( i = 0; i < values.length; i++ ) {
		t.throws( badValue( values[i] ), TypeError, 'throws an error when provided '+values[i] );
	}
	t.end();

	function badValue( value ) {
		return function() {
			factory( 1.0, 1.0, value );
		};
	}
});

tape( 'when called without arguments, the function returns a PRNG that returns `NaN` if provided a first argument which is `NaN`', function test( t ) {
	var beta;
	var r;

	beta = factory();
	r = beta( NaN, 1.0 );

	t.strictEqual( isnan( r ), true, 'returns NaN' );
	t.end();
});

tape( 'when called without arguments, the function returns a PRNG that returns `NaN` if provided a second argument which is `NaN`', function test( t ) {
	var beta;
	var r;

	beta = factory();
	r = beta( 1.0, NaN );

	t.strictEqual( isnan( r ), true, 'returns NaN' );
	t.end();
});

tape( 'when called without arguments, the function returns a PRNG that returns `NaN` if provided `NaNs`', function test( t ) {
	var beta;
	var r;

	beta = factory();
	r = beta( NaN, NaN );

	t.strictEqual( isnan( r ), true, 'returns NaN' );
	t.end();
});

tape( 'when called without arguments, the function returns a PRNG that returns `NaN` if provided a first argument which is nonpositive', function test( t ) {
	var beta;
	var r;

	beta = factory();

	r = beta( 0.0, 1.0 );
	t.strictEqual( isnan( r ), true, 'returns NaN' );

	r = beta( -1.0, 1.0 );
	t.strictEqual( isnan( r ), true, 'returns NaN' );

	t.end();
});

tape( 'when called without arguments, the function returns a PRNG that returns `NaN` if provided a second argument which is nonpositive', function test( t ) {
	var beta;
	var r;

	beta = factory();

	r = beta( 1.0, 0.0 );
	t.strictEqual( isnan( r ), true, 'returns NaN' );

	r = beta( 1.0, -1.0 );
	t.strictEqual( isnan( r ), true, 'returns NaN' );

	t.end();
});

tape( 'the function returns a generator for creating random numbers from a specified beta distribution', opts, function test( t ) {
	var count;
	var alpha;
	var beta;
	var N;
	var x;

	alpha = 2.0;
	beta = 4.0;

	x = new Array( 1e3 );
	N = 300;

	count = -1;
	gof();

	function gof() {
		var rejected;
		var pValue;
		var rbeta;
		var bool;
		var i;
		var j;

		rejected = 0;
		for ( i = 0; i < N; i++ ) {
			rbeta = factory( alpha, beta );
			t.ok( true, 'seed: '+rbeta.SEED );
			for ( j = 0; j < x.length; j++ ) {
				x[ j ] = rbeta();
			}
			// Test using Kolmogorov-Smirnov goodness-of-fit test:
			pValue = kstest( x, 'beta', alpha, beta ).pValue;
			t.equal( typeof pValue, 'number', 'returns a p-value: '+pValue );
			if ( pValue < 0.05 ) {
				rejected += 1;
			}
		}
		// Account for small sample size and few repeats...
		bool = ( rejected / N < 0.10 );

		count += 1;
		if ( !bool && count < 1 ) {
			// Retry:
			gof();
		} else {
			t.ok( bool, 'null hypothesis (i.e., that numbers are drawn from Beta('+alpha+','+beta+')) is rejected in less than 10% of cases ('+rejected+' of '+N+'). Repeats: '+count+'.' );
			t.end();
		}
	}
});
<commit_msg>Require that goodness-of-fit tests succeed twice in a row if the first run fails<commit_after>'use strict';

// MODULES //

var tape = require( 'tape' );
var kstest = require( '@stdlib/math/generics/statistics/kstest' );
var round = require( '@stdlib/math/base/special/round' );
var isnan = require( '@stdlib/math/base/utils/is-nan' );
var factory = require( './../lib/factory.js' );

// VARIABLES //

var opts = {
	'skip': ( process.env.TEST_MODE === 'coverage' )
};

// TESTS //

tape( 'main export is a function', function test( t ) {
	t.ok( true, __filename );
	t.equal( typeof factory, 'function', 'main export is a function' );
	t.end();
});

tape( 'the function returns a pseudorandom number generator (no seed)', function test( t ) {
	var beta;
	var r;
	var i;

	// When binding distribution parameters...
	beta = factory( 2.0, 2.0 );
	for ( i = 0; i < 400; i++ ) {
		r = beta();
		t.equal( typeof r, 'number', 'returns a number' );
	}
	beta = factory( 30.0, 30.0 );
	for ( i = 0; i < 400; i++ ) {
		r = beta();
		t.equal( typeof r, 'number', 'returns a number' );
	}

	// Without binding distribution parameters...
	beta = factory();
	for ( i = 0; i < 100; i++ ) {
		r = beta( 2.5, 2.0 );
		t.equal( typeof r, 'number', 'returns a number' );
	}

	t.end();
});

tape( 'the function returns a seeded pseudorandom number generator (alpha,beta <= 1.0)', function test( t ) {
	var beta1;
	var beta2;
	var seed;
	var r1;
	var r2;
	var i;

	seed = round( Date.now() / 1000 );

	beta1 = factory( 0.5, 0.5, {
		'seed': seed
	});
	beta2 = factory( 0.5, 0.5, {
		'seed': seed
	});

	t.notEqual( beta1, beta2, 'separate generators' );

	for ( i = 0; i < 1e3; i++ ) {
		r1 = beta1();
		r2 = beta2();
		t.equal( r1, r2, 'both return same number: '+r1 );
	}
	t.end();
});

tape( 'the function returns a seeded pseudorandom number generator (alpha = beta > 1.5)', function test( t ) {
	var beta1;
	var beta2;
	var seed;
	var r1;
	var r2;
	var i;

	seed = round( Date.now() / 1000 );

	beta1 = factory( 3.14, 3.14, {
		'seed': seed
	});
	beta2 = factory( 3.14, 3.14, {
		'seed': seed
	});

	t.notEqual( beta1, beta2, 'separate generators' );

	for ( i = 0; i < 1e3; i++ ) {
		r1 = beta1();
		r2 = beta2();
		t.equal( r1, r2, 'both return same number: '+r1 );
	}
	t.end();
});

tape( 'the function returns a seeded pseudorandom number generator (alpha != beta > 1.0)', function test( t ) {
	var beta1;
	var beta2;
	var seed;
	var r1;
	var r2;
	var i;

	seed = round( Date.now() / 1000 );

	beta1 = factory( 3.14, 1.25, {
		'seed': seed
	});
	beta2 = factory( 3.14, 1.25, {
		'seed': seed
	});

	t.notEqual( beta1, beta2, 'separate generators' );

	for ( i = 0; i < 1e3; i++ ) {
		r1 = beta1();
		r2 = beta2();
		t.equal( r1, r2, 'both return same number: '+r1 );
	}
	t.end();
});

tape( 'attached to the returned function is the generator name', function test( t ) {
	var beta = factory();
	t.equal( beta.NAME, 'beta', 'has `NAME` property' );
	t.end();
});

tape( 'attached to the returned function is the underlying PRNG', function test( t ) {
	var beta = factory();
	t.equal( typeof beta.PRNG, 'function', 'has `PRNG` property' );
	t.end();
});

tape( 'attached to the returned function is the generator seed', function test( t ) {
	var beta = factory({
		'seed': 12345
	});
	t.equal( typeof beta.SEED, 'number', 'has `SEED` property' );
	t.equal( beta.SEED, 12345, 'equal to provided seed' );
	t.end();
});

tape( 'the function throws an error if `alpha` is not a positive number', function test( t ) {
	var values;
	var i;

	values = [
		-2.0,
		0.0,
		'5',
		null,
		true,
		undefined,
		NaN,
		[],
		{},
		function(){}
	];

	for ( i = 0; i < values.length; i++ ) {
		t.throws( badValue( values[i] ), TypeError, 'throws an error when provided '+values[i] );
	}
	t.end();

	function badValue( value ) {
		return function() {
			factory( value, 2.0 );
		};
	}
});

tape( 'the function throws an error if `beta` is not a positive number', function test( t ) {
	var values;
	var i;

	values = [
		-2.0,
		0.0,
		'5',
		null,
		true,
		undefined,
		NaN,
		[],
		{},
		function(){}
	];

	for ( i = 0; i < values.length; i++ ) {
		t.throws( badValue( values[i] ), TypeError, 'throws an error when provided '+values[i] );
	}
	t.end();

	function badValue( value ) {
		return function() {
			factory( 2.0, value );
		};
	}
});

tape( 'the function throws an error if provided an options argument which is not an object (no other arguments)', function test( t ) {
	var values;
	var i;

	values = [
		'abc',
		5,
		null,
		true,
		undefined,
		NaN,
		[],
		function(){}
	];

	for ( i = 0; i < values.length; i++ ) {
		t.throws( badValue( values[i] ), TypeError, 'throws an error when provided '+values[i] );
	}
	t.end();

	function badValue( value ) {
		return function() {
			factory( value );
		};
	}
});

tape( 'the function throws an error if provided an options argument which is not an object (other arguments)', function test( t ) {
	var values;
	var i;

	values = [
		'abc',
		5,
		null,
		true,
		undefined,
		NaN,
		[],
		function(){}
	];

	for ( i = 0; i < values.length; i++ ) {
		t.throws( badValue( values[i] ), TypeError, 'throws an error when provided '+values[i] );
	}
	t.end();

	function badValue( value ) {
		return function() {
			factory( 1.0, 1.0, value );
		};
	}
});

tape( 'when called without arguments, the function returns a PRNG that returns `NaN` if provided a first argument which is `NaN`', function test( t ) {
	var beta;
	var r;

	beta = factory();
	r = beta( NaN, 1.0 );

	t.strictEqual( isnan( r ), true, 'returns NaN' );
	t.end();
});

tape( 'when called without arguments, the function returns a PRNG that returns `NaN` if provided a second argument which is `NaN`', function test( t ) {
	var beta;
	var r;

	beta = factory();
	r = beta( 1.0, NaN );

	t.strictEqual( isnan( r ), true, 'returns NaN' );
	t.end();
});

tape( 'when called without arguments, the function returns a PRNG that returns `NaN` if provided `NaNs`', function test( t ) {
	var beta;
	var r;

	beta = factory();
	r = beta( NaN, NaN );

	t.strictEqual( isnan( r ), true, 'returns NaN' );
	t.end();
});

tape( 'when called without arguments, the function returns a PRNG that returns `NaN` if provided a first argument which is nonpositive', function test( t ) {
	var beta;
	var r;

	beta = factory();

	r = beta( 0.0, 1.0 );
	t.strictEqual( isnan( r ), true, 'returns NaN' );

	r = beta( -1.0, 1.0 );
	t.strictEqual( isnan( r ), true, 'returns NaN' );

	t.end();
});

tape( 'when called without arguments, the function returns a PRNG that returns `NaN` if provided a second argument which is nonpositive', function test( t ) {
	var beta;
	var r;

	beta = factory();

	r = beta( 1.0, 0.0 );
	t.strictEqual( isnan( r ), true, 'returns NaN' );

	r = beta( 1.0, -1.0 );
	t.strictEqual( isnan( r ), true, 'returns NaN' );

	t.end();
});

tape( 'the function returns a generator for creating random numbers from a specified beta distribution', opts, function test( t ) {
	var count;
	var npass;
	var alpha;
	var beta;
	var N;
	var x;

	alpha = 2.0;
	beta = 4.0;

	x = new Array( 1e3 );
	N = 300;

	count = -1;
	npass = 0;

	gof();

	function gof() {
		var rejected;
		var pValue;
		var rbeta;
		var bool;
		var i;
		var j;

		count += 1;
		rejected = 0;
		for ( i = 0; i < N; i++ ) {
			rbeta = factory( alpha, beta );
			t.ok( true, 'seed: '+rbeta.SEED );
			for ( j = 0; j < x.length; j++ ) {
				x[ j ] = rbeta();
			}
			// Test using Kolmogorov-Smirnov goodness-of-fit test:
			pValue = kstest( x, 'beta', alpha, beta ).pValue;
			t.equal( typeof pValue, 'number', 'returns a p-value: '+pValue );
			if ( pValue < 0.05 ) {
				rejected += 1;
			}
		}
		// Account for small sample size and few repeats...
		bool = ( rejected / N < 0.10 );

		// If we succeed the first time, we are done...
		if ( count === 0 && bool ) {
			return done( bool, rejected );
		}
		// Retry mode...
		if ( bool ) {
			npass += 1;
		}
		// Retry twice...
		if ( count < 2 ) {
			return gof();
		}
		// Both retries must succeed for test to pass:
		bool = ( npass >= 2 );
		return done( bool, rejected );
	}

	function done( bool, rejected ) {
		t.ok( bool, 'null hypothesis (i.e., that numbers are drawn from Beta('+alpha+','+beta+')) is rejected in less than 10% of cases ('+rejected+' of '+N+'). Repeats: '+npass+' of '+count+'.' );
		return t.end();
	}
});
<|endoftext|>


<commit_before>'use strict';

treeherder.factory('ThJobLogUrlModel', [
    '$http', '$log', 'thUrl',
    function($http, $log, thUrl) {

    // ThJobLogUrlModel is the js counterpart of job_type

    var ThJobLogUrlModelModel = function(data) {
        // creates a new instance of ThJobLogUrlModelModel
        // using the provided properties
        angular.extend(this, data);
    };

    ThJobLogUrlModelModel.get_uri = function(){
        var url = thUrl.getProjectUrl("/job-log-url/");
        $log.log(url);
        return url;
    };

    ThJobLogUrlModelModel.get_list = function(job_id) {
        // a static method to retrieve a list of ThJobLogUrlModelModel
        return $http.get(ThJobLogUrlModelModel.get_uri()+"?job_id="+job_id)
            .then(function(response) {
                var item_list = [];
                angular.forEach(response.data, function(elem){
                    item_list.push(new ThJobLogUrlModelModel(elem));
                });
                return item_list;
        });
    };

    ThJobLogUrlModelModel.get = function(pk) {
        // a static method to retrieve a single instance of ThJobLogUrlModelModel
        return $http.get(ThJobLogUrlModelModel.get_uri()+pk).then(function(response) {
            return new ThJobLogUrlModelModel(response.data);
        });
    };

    return ThJobLogUrlModelModel;
}]);
<commit_msg>Bug 1045846: Part 1: Remove the typo in the model name<commit_after>'use strict';

treeherder.factory('ThJobLogUrlModel', [
    '$http', '$log', 'thUrl',
    function($http, $log, thUrl) {

    // ThJobLogUrlModel is the js counterpart of job_type

    var ThJobLogUrlModel = function(data) {
        // creates a new instance of ThJobLogUrlModel
        // using the provided properties
        angular.extend(this, data);
    };

    ThJobLogUrlModel.get_uri = function(){
        var url = thUrl.getProjectUrl("/job-log-url/");
        $log.log(url);
        return url;
    };

    ThJobLogUrlModel.get_list = function(job_id) {
        // a static method to retrieve a list of ThJobLogUrlModel
        return $http.get(ThJobLogUrlModel.get_uri()+"?job_id="+job_id)
            .then(function(response) {
                var item_list = [];
                angular.forEach(response.data, function(elem){
                    item_list.push(new ThJobLogUrlModel(elem));
                });
                return item_list;
        });
    };

    ThJobLogUrlModel.get = function(pk) {
        // a static method to retrieve a single instance of ThJobLogUrlModel
        return $http.get(ThJobLogUrlModel.get_uri()+pk).then(function(response) {
            return new ThJobLogUrlModel(response.data);
        });
    };

    return ThJobLogUrlModel;
}]);
<|endoftext|>


<commit_before>'use strict';

var fs           = require('fs-extra');
var _            = require('lodash');
var Generator    = require('yeoman-generator');
var Gruntfile    = require('gruntfile-editor');
var utils        = require('../app/utils');
var banner       = require('../app/banner');
var prompt       = require('../app/prompts').project;
var tasks        = require('../app/gruntTaskConfigs');
var copy         = utils.copy;
var copyTpl      = utils.copyTpl;
var maybeInclude = utils.maybeInclude;

var commandLineOptions = {
    defaults: {
        type: Boolean,
        desc: 'Scaffold app with no user input using default settings',
        defaults: false
    },
    skipBenchmark: {
        type: Boolean,
        desc: 'DO NOT add benchmark.js code and dependencies to project',
        defaults: false
    },
    skipCoveralls: {
        type: Boolean,
        desc: 'DO NOT add coveralls tasks and dependencies to project',
        defaults: false
    },
    skipJsinspect: {
        type: Boolean,
        desc: 'DO NOT add JSInspect tasks and dependencies to project',
        defaults: false
    }
};

module.exports = Generator.extend({
    constructor: function() {
        Generator.apply(this, arguments);
        var generator = this;
        Object.keys(commandLineOptions).forEach(function(option) {
            generator.option(option, commandLineOptions[option]);
        });
        generator.config.set('userName', generator.user.git.name() ? generator.user.git.name() : 'A. Developer');
    },
    prompting: function() {
        var generator = this;
        generator.userName = generator.config.get('userName');
        generator.use = prompt.defaults;
        !generator.config.get('hideBanner') && generator.log(banner);
        if (generator.options.defaults) {
            var done = this.async();
            var sourceDirectory = generator.use.sourceDirectory;
            generator.projectName = generator.use.projectName;
            generator.config.set('projectName', generator.projectName);
            generator.sourceDirectory = (!/\/$/.test(sourceDirectory)) ? sourceDirectory + '/' : sourceDirectory;
            generator.config.set('sourceDirectory', generator.sourceDirectory);
            done();
        } else {
            var isUnAnswered = function(option) {
                return !!!generator.options[option.name] || (generator.options[option.name] === commandLineOptions[option.name].defaults);
            };
            var isComposed = generator.config.get('isComposed');
            return generator.prompt(prompt.getQuestions(isComposed).filter(isUnAnswered)).then(function(answers) {
                generator.use = answers;
                generator.projectName = answers.projectName;
                generator.sourceDirectory = (!/\/$/.test(answers.sourceDirectory)) ? answers.sourceDirectory + '/' : answers.sourceDirectory;
                generator.config.set('sourceDirectory', generator.sourceDirectory);
            }.bind(generator));
        }
    },
    writing: {
        configFiles: function() {
            var generator = this;
            var options = generator.options;
            var use = generator.use;
            var _copyTpl = _.partial(copyTpl, _, _, generator);
            var isComposed = generator.config.get('isComposed');
            _.extend(generator, {
                useBenchmark: use.benchmark && !options.skipBenchmark,
                useCoveralls: use.coveralls && !options.skipCoveralls,
                useJsinspect: use.jsinspect && !options.skipJsinspect
            });
            generator.config.set('projectName', generator.projectName);
            generator.config.set('useBenchmark', generator.useBenchmark);
            generator.config.set('useCoveralls', generator.useCoveralls);
            generator.config.set('useJsinspect', generator.useJsinspect);
            _copyTpl('_LICENSE', 'LICENSE');
            _copyTpl('_package.json', 'package.json');
            _copyTpl('config/_gitignore', '.gitignore');
            if (isComposed) {
                _copyTpl('_Gruntfile.js', 'Gruntfile.js');
                _copyTpl('config/_default.json', 'config/default.json');
                _copyTpl('config/_karma.conf.js', 'config/karma.conf.js');
                _copyTpl('config/_eslintrc_webapp.js', 'config/.eslintrc.js');
            } else {
                _copyTpl('config/_eslintrc.js', 'config/.eslintrc.js');
            }
            if (generator.useCoveralls) {
                _copyTpl('_travis.yml', '.travis.yml');
            }
            fs.mkdirp(generator.sourceDirectory);
        },
        testFiles: function() {
            var generator = this;
            var isComposed = generator.config.get('isComposed');
            copy('test/data/**/*.*', 'test/data', generator);
            copy('test/mocha/**/*.*', 'test/mocha', generator);
            copy('test/mocha.opts', 'test/mocha.opts', generator);
            copyTpl('test/config.js', 'test/config.js', generator);
            if (isComposed && generator.useBenchmark) {
                copyTpl('test/example.benchmark.js', 'test/benchmarks/example.benchmark.js', generator);
            }
        }
    },
    install: function() {
        var generator = this;
        var isComposed = generator.config.get('isComposed');
        var devDependencies = [].concat(
            maybeInclude(!isComposed, ['nyc', 'coveralls', 'watch']),
            maybeInclude(!isComposed && generator.useJsinspect, 'jsinspect'),
            maybeInclude(isComposed && generator.useJsinspect, ['jsinspect', 'grunt-jsinspect']),
            maybeInclude(isComposed && generator.useBenchmark, ['lodash', 'grunt-benchmark']),
            maybeInclude(isComposed && generator.useCoveralls, 'grunt-karma-coveralls')
        );
        generator.npmInstall();
        generator.npmInstall(devDependencies, {saveDev: true});
    },
    end: function() {
        var generator = this;
        var updatePackageJson = _.partial(utils.json.extend, generator.destinationPath('package.json'));
        //
        // TODO: move to webapp/index.js
        //
        if (generator.config.get('isComposed')) {// webapp only
            //
            // Configure package.json
            //
            if (generator.useCoveralls) {
                updatePackageJson({
                    scripts: {'test:ci': 'npm test && grunt coveralls'}
                });
            }
            if (generator.useJsinspect) {
                updatePackageJson({
                    scripts: {inspect: 'grunt jsinspect:app'}
                });
            }
            //
            //  Configure Grunt tasks
            //
            var placeholder = '/* -- load tasks placeholder -- */';
            var loadTasks = 'grunt.loadTasks(config.folders.tasks);';
            var text = fs.readFileSync(generator.destinationPath('Gruntfile.js')).toString().replace(placeholder, loadTasks);
            var gruntfile = new Gruntfile(text);
            [// Tasks enabled by default
                'browserSync',
                'clean',
                'copy',
                'eslint',
                'jsdoc',
                'jsonlint',
                'karma',
                'open',
                'plato',
                'requirejs',
                'watch'
            ]
            .concat(// Tasks enabled by user
                maybeInclude(generator.useBenchmark, 'benchmark'),
                maybeInclude(generator.useCoveralls, 'coveralls'),
                maybeInclude(generator.useJsinspect, 'jsinspect')
            )
            .sort()
            .forEach(name => gruntfile.insertConfig(name, tasks[name]));
            //
            // Write to file
            //
            fs.writeFileSync(generator.destinationPath('Gruntfile.js'), gruntfile.toString());
        } else {
            updatePackageJson({
                scripts: {coverage: 'nyc report -r text'}
            });
            if (generator.useCoveralls) {
                updatePackageJson({
                    scripts: {'test:travis': 'nyc report --reporter=text-lcov | coveralls'}
                });
            }
        }
    }
});
<commit_msg>Refined test boilerplate for standalone projects<commit_after>'use strict';

var fs           = require('fs-extra');
var _            = require('lodash');
var Generator    = require('yeoman-generator');
var Gruntfile    = require('gruntfile-editor');
var utils        = require('../app/utils');
var banner       = require('../app/banner');
var prompt       = require('../app/prompts').project;
var tasks        = require('../app/gruntTaskConfigs');
var copy         = utils.copy;
var copyTpl      = utils.copyTpl;
var maybeInclude = utils.maybeInclude;

var commandLineOptions = {
    defaults: {
        type: Boolean,
        desc: 'Scaffold app with no user input using default settings',
        defaults: false
    },
    skipBenchmark: {
        type: Boolean,
        desc: 'DO NOT add benchmark.js code and dependencies to project',
        defaults: false
    },
    skipCoveralls: {
        type: Boolean,
        desc: 'DO NOT add coveralls tasks and dependencies to project',
        defaults: false
    },
    skipJsinspect: {
        type: Boolean,
        desc: 'DO NOT add JSInspect tasks and dependencies to project',
        defaults: false
    }
};

module.exports = Generator.extend({
    constructor: function() {
        Generator.apply(this, arguments);
        var generator = this;
        Object.keys(commandLineOptions).forEach(function(option) {
            generator.option(option, commandLineOptions[option]);
        });
        generator.config.set('userName', generator.user.git.name() ? generator.user.git.name() : 'A. Developer');
    },
    prompting: function() {
        var generator = this;
        generator.userName = generator.config.get('userName');
        generator.use = prompt.defaults;
        !generator.config.get('hideBanner') && generator.log(banner);
        if (generator.options.defaults) {
            var done = this.async();
            var sourceDirectory = generator.use.sourceDirectory;
            generator.projectName = generator.use.projectName;
            generator.config.set('projectName', generator.projectName);
            generator.sourceDirectory = (!/\/$/.test(sourceDirectory)) ? sourceDirectory + '/' : sourceDirectory;
            generator.config.set('sourceDirectory', generator.sourceDirectory);
            done();
        } else {
            var isUnAnswered = function(option) {
                return !!!generator.options[option.name] || (generator.options[option.name] === commandLineOptions[option.name].defaults);
            };
            var isComposed = generator.config.get('isComposed');
            return generator.prompt(prompt.getQuestions(isComposed).filter(isUnAnswered)).then(function(answers) {
                generator.use = answers;
                generator.projectName = answers.projectName;
                generator.sourceDirectory = (!/\/$/.test(answers.sourceDirectory)) ? answers.sourceDirectory + '/' : answers.sourceDirectory;
                generator.config.set('sourceDirectory', generator.sourceDirectory);
            }.bind(generator));
        }
    },
    writing: {
        configFiles: function() {
            var generator = this;
            var options = generator.options;
            var use = generator.use;
            var _copyTpl = _.partial(copyTpl, _, _, generator);
            var isComposed = generator.config.get('isComposed');
            _.extend(generator, {
                useBenchmark: use.benchmark && !options.skipBenchmark,
                useCoveralls: use.coveralls && !options.skipCoveralls,
                useJsinspect: use.jsinspect && !options.skipJsinspect
            });
            generator.config.set('projectName', generator.projectName);
            generator.config.set('useBenchmark', generator.useBenchmark);
            generator.config.set('useCoveralls', generator.useCoveralls);
            generator.config.set('useJsinspect', generator.useJsinspect);
            _copyTpl('_LICENSE', 'LICENSE');
            _copyTpl('_package.json', 'package.json');
            _copyTpl('config/_gitignore', '.gitignore');
            if (isComposed) {
                _copyTpl('_Gruntfile.js', 'Gruntfile.js');
                _copyTpl('config/_default.json', 'config/default.json');
                _copyTpl('config/_karma.conf.js', 'config/karma.conf.js');
                _copyTpl('config/_eslintrc_webapp.js', 'config/.eslintrc.js');
            } else {
                _copyTpl('config/_eslintrc.js', 'config/.eslintrc.js');
            }
            if (generator.useCoveralls) {
                _copyTpl('_travis.yml', '.travis.yml');
            }
            fs.mkdirp(generator.sourceDirectory);
        },
        testFiles: function() {
            var generator = this;
            var isComposed = generator.config.get('isComposed');
            copyTpl('test/config.js', 'test/config.js', generator);
            copy('test/data/**/*.*', 'test/data', generator);
            copy('test/mocha.opts', 'test/mocha.opts', generator);
            copy('test/mocha/specs/' + (isComposed ? 'example' : 'simple') + '.spec.js', 'test/mocha/specs/example.spec.js', generator);
            if (isComposed && generator.useBenchmark) {
                copyTpl('test/example.benchmark.js', 'test/benchmarks/example.benchmark.js', generator);
            }
        }
    },
    install: function() {
        var generator = this;
        var isComposed = generator.config.get('isComposed');
        var devDependencies = [].concat(
            maybeInclude(!isComposed, ['nyc', 'coveralls', 'watch']),
            maybeInclude(!isComposed && generator.useJsinspect, 'jsinspect'),
            maybeInclude(isComposed && generator.useJsinspect, ['jsinspect', 'grunt-jsinspect']),
            maybeInclude(isComposed && generator.useBenchmark, ['lodash', 'grunt-benchmark']),
            maybeInclude(isComposed && generator.useCoveralls, 'grunt-karma-coveralls')
        );
        generator.npmInstall();
        generator.npmInstall(devDependencies, {saveDev: true});
    },
    end: function() {
        var generator = this;
        var updatePackageJson = _.partial(utils.json.extend, generator.destinationPath('package.json'));
        //
        // TODO: move to webapp/index.js
        //
        if (generator.config.get('isComposed')) {// webapp only
            //
            // Configure package.json
            //
            if (generator.useCoveralls) {
                updatePackageJson({
                    scripts: {'test:ci': 'npm test && grunt coveralls'}
                });
            }
            if (generator.useJsinspect) {
                updatePackageJson({
                    scripts: {inspect: 'grunt jsinspect:app'}
                });
            }
            //
            //  Configure Grunt tasks
            //
            var placeholder = '/* -- load tasks placeholder -- */';
            var loadTasks = 'grunt.loadTasks(config.folders.tasks);';
            var text = fs.readFileSync(generator.destinationPath('Gruntfile.js')).toString().replace(placeholder, loadTasks);
            var gruntfile = new Gruntfile(text);
            [// Tasks enabled by default
                'browserSync',
                'clean',
                'copy',
                'eslint',
                'jsdoc',
                'jsonlint',
                'karma',
                'open',
                'plato',
                'requirejs',
                'watch'
            ]
            .concat(// Tasks enabled by user
                maybeInclude(generator.useBenchmark, 'benchmark'),
                maybeInclude(generator.useCoveralls, 'coveralls'),
                maybeInclude(generator.useJsinspect, 'jsinspect')
            )
            .sort()
            .forEach(name => gruntfile.insertConfig(name, tasks[name]));
            //
            // Write to file
            //
            fs.writeFileSync(generator.destinationPath('Gruntfile.js'), gruntfile.toString());
        } else {
            updatePackageJson({
                scripts: {coverage: 'nyc report -r text'}
            });
            if (generator.useCoveralls) {
                updatePackageJson({
                    scripts: {'test:travis': 'nyc report --reporter=text-lcov | coveralls'}
                });
            }
        }
    }
});
<|endoftext|>


<commit_before>import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'
import VueAxios from 'vue-axios'
import normalize from 'hal-json-normalizer'
import { normalizeEntityUri } from '@/store/uriUtils'
import storeValueProxy from '@/store/storeValueProxy'

Vue.use(Vuex)
axios.defaults.withCredentials = true
Vue.use(VueAxios, axios)

export const API_ROOT = process.env.VUE_APP_ROOT_API

export const state = {
  api: {}
}

export const mutations = {
  /**
   * Adds a placeholder into the store that indicates that the entity with the given URI is currently being
   * fetched from the API and not yet available.
   * @param state Vuex state
   * @param uri   URI of the object that is being fetched
   */
  addEmpty (state, uri) {
    Vue.set(state.api, uri, { _meta: { self: uri, loading: true } })
  },
  /**
   * Adds entities loaded from the API to the Vuex store.
   * @param state Vuex state
   * @param data  An object mapping URIs to entities that should be merged into the Vuex state.
   */
  add (state, data) {
    Object.keys(data).forEach((uri) => {
      Vue.set(state.api, uri, data[uri])
    })
  },
  /**
   * Removes a single entity from the Vuex store.
   * @param state Vuex state
   * @param uri   URI of the entity to be removed
   */
  purge (state, uri) {
    Vue.delete(state.api, uri)
  },
  /**
   * Marks a single entity in the Vuex store as deleting, meaning the process of deletion is currently ongoing.
   * @param state Vuex state
   * @param uri   URI of the entity that is currently being deleted
   */
  deleting (state, uri) {
    Vue.set(state.api[uri]._meta, 'deleting', true)
  },
  /**
   * Marks a single entity in the Vuex store as normal again, after it has been marked as deleting before.
   * @param state Vuex state
   * @param uri   URI of the entity that failed to be deleted
   */
  deletingFailed (state, uri) {
    Vue.set(state.api[uri]._meta, 'deleting', false)
  }
}

const store = new Vuex.Store({
  state,
  mutations,
  strict: process.env.NODE_ENV !== 'production'
})
export default store

/**
 * Sends a POST request to the backend, in order to create a new entity. Note that this does not
 * reload any collections that this new entity might be in, the caller has to do that on its own.
 * @param uriOrCollection URI (or instance) of a collection in which the entity should be created
 * @param data            Payload to be sent in the POST request
 * @returns Promise       resolves when the POST request has completed and the entity is available
 *                        in the Vuex store.
 */
export const post = function (uriOrCollection, data) {
  const uri = normalizeEntityUri(uriOrCollection, API_ROOT)
  if (uri === null) {
    return Promise.reject(new Error(`Could not perform POST, "${uriOrCollection}" is not an entity or URI`))
  }
  return markAsDoneWhenResolved(axios.post(API_ROOT + uri, data).then(({ data }) => {
    storeHalJsonData(data)
    return get(data._links.self.href)
  }))
}

/**
 * Reloads an entity from the API.
 *
 * @param uriOrEntity URI (or instance) of an entity to reload from the API
 * @returns entity    Entity from the store. Note that when fetching an object for the first time, a reactive
 *                    dummy is returned, which will be replaced with the true data through Vue's reactivity
 *                    system as soon as the API request finishes.
 */
export const reload = function (uriOrEntity) {
  return get(uriOrEntity, true)
}

/**
 * Retrieves an entity from the Vuex store, or from the API in case it is not already fetched or a reload
 * is forced.
 * This function attempts to hide all backend implementation details such as pagination, linked vs.
 * embedded relations and loading state and instead provide an easy-to-use and consistent interface for
 * developing frontend components.
 *
 * Basic usage in a Vue component:
 * computed: {
 *   allCamps () { return this.api.get('/camp').items }
 *   oneSpecificCamp () { return this.api.get(`/camp/${this.campId}`) }
 *   campUri () { return this.oneSpecificCamp._meta.self }
 *   eventTypes () { return this.oneSpecificCamp.event_types() }
 *   user () { return this.api.get().profile() } // Root endpoint ('/') and navigate through self-discovery API
 * },
 * created () {
 *   this.oneSpecificCamp._meta.load.then(() => {
 *     // do something now that the camp is loaded from the API
 *   })
 * }
 *
 * @param uriOrEntity URI (or instance) of an entity to load from the store or API
 * @param forceReload If true, the entity will be fetched from the API even if it is already in the Vuex store.
 *                    Note that the function will still return the old value in this case, but you can
 *                    wait for the new value using the ._meta.load promise.
 * @returns entity    Entity from the store. Note that when fetching an object for the first time, a reactive
 *                    dummy is returned, which will be replaced with the true data through Vue's reactivity
 *                    system as soon as the API request finishes.
 */
export const get = function (uriOrEntity, forceReload = false) {
  const forceReloadingEmbeddedCollection = forceReload && uriOrEntity._meta && uriOrEntity._meta.reload && uriOrEntity._meta.reload.uri
  const uri = forceReloadingEmbeddedCollection
    ? normalizeEntityUri(uriOrEntity._meta.reload.uri, API_ROOT)
    : normalizeEntityUri(uriOrEntity, API_ROOT)
  if (uri === null) {
    if (uriOrEntity[Symbol.for('isLoadingProxy')]) {
      // A loadingProxy is safe to return without breaking the UI.
      return uriOrEntity
    }
    // We don't know anything about the requested object, something is wrong.
    throw new Error(`Could not perform GET, "${uriOrEntity}" is not an entity or URI`)
  }

  const storeData = load(uri, forceReload)
  return forceReloadingEmbeddedCollection
    ? storeValueProxy(storeData)[uriOrEntity._meta.reload.property]()
    : storeValueProxy(storeData)
}

/**
 * Loads the entity specified by the URI from the Vuex store, or from the API if necessary. If applicable,
 * sets the load promise on the entity in the Vuex store.
 * @param uri         URI of the entity to load
 * @param forceReload If true, the entity will be fetched from the API even if it is already in the Vuex store.
 * @returns entity    the current entity data from the Vuex store. Note: This may be a reactive dummy if the
 *                    backend request is still ongoing.
 */
function load (uri, forceReload) {
  const existsInStore = (uri in store.state.api)
  const isLoading = existsInStore && (store.state.api[uri]._meta || {}).loading

  if (!existsInStore) {
    store.commit('addEmpty', uri)
  }
  if (isLoading) {
    // Reuse the loading entity and load promise that is already waiting for a pending API request
    return store.state.api[uri]
  }

  let dataFinishedLoading = Promise.resolve(store.state.api[uri])
  if (!existsInStore || forceReload) {
    dataFinishedLoading = loadFromApi(uri)
  } else if (store.state.api[uri]._meta.load) {
    // reuse the existing promise from the store if possible
    dataFinishedLoading = store.state.api[uri]._meta.load
  }

  // We mutate the store state here without telling Vuex about it, so it won't complain and won't make load reactive.
  // The promise is needed in the store for some special cases when a loading entity is requested a second time with
  // this.api.get(...) or this.api.reload(...).
  store.state.api[uri]._meta.load = markAsDoneWhenResolved(dataFinishedLoading)

  return store.state.api[uri]
}

/**
 * Loads the entity specified by the URI from the API and stores it into the Vuex store. Returns a promise
 * that resolves to the raw data stored in the Vuex store (needs to be wrapped into a storeValueProxy before
 * being usable in Vue components).
 * @param uri       URI of the entity to load from the API
 * @returns Promise resolves to the raw data stored in the Vuex store after the API request completes, or
 *                  rejects when the API request fails
 */
function loadFromApi (uri) {
  return new Promise((resolve, reject) => {
    axios.get(API_ROOT + uri).then(
      ({ data }) => {
        // Workaround because API adds page parameter even to first page when it was not requested that way
        // TODO fix backend API and remove the next line
        data._links.self.href = uri
        storeHalJsonData(data)
        resolve(store.state.api[uri])
      },
      ({ response }) => {
        if (response.status === 404) {
          return deleted(uri)
        }
        reject(response)
      }
    )
  })
}

/**
 * Loads the URI of a related entity from the store, or the API in case it is not already fetched.
 *
 * @param uriOrEntity URI (or instance) of an entity from the API
 * @param relation    the name of the relation for which the URI should be retrieved
 * @returns Promise   resolves to the URI of the related entity.
 */
export const href = async function (uriOrEntity, relation) {
  const self = normalizeEntityUri(await get(uriOrEntity)._meta.load, API_ROOT)
  const href = (state.api[self][relation] || {}).href
  return href ? API_ROOT + href : href
}

/**
 * Sends a PATCH request to the backend, in order to update some fields in an existing entity.
 * @param uriOrEntity URI (or instance) of an entity which should be updated
 * @param data        Payload (fields to be updated) to be sent in the PATCH request
 * @returns Promise   resolves when the PATCH request has completed and the updated entity is available
 *                    in the Vuex store.
 */
const patch = function (uriOrEntity, data) {
  const uri = normalizeEntityUri(uriOrEntity, API_ROOT)
  if (uri === null) {
    return Promise.reject(new Error(`Could not perform PATCH, "${uriOrEntity}" is not an entity or URI`))
  }
  return markAsDoneWhenResolved(axios.patch(API_ROOT + uri, data).then(({ data }) => {
    // Workaround because API adds page parameter even to first page when it was not requested that way
    // TODO fix backend API and remove the next line
    data._links.self.href = uri
    storeHalJsonData(data)
    return get(uri)
  }, ({ response }) => {
    if (response.status === 404) {
      return deleted(uri)
    }
  }))
}

/**
 * Removes a single entity from the Vuex store (but does not delete it using the API). Note that if the
 * entity is currently referenced and displayed through any other entity, the reactivity system will
 * immediately re-fetch the purged entity from the API in order to re-display it.
 * @param uriOrEntity URI (or instance) of an entity which should be removed from the Vuex store
 */
const purge = function (uriOrEntity) {
  const uri = normalizeEntityUri(uriOrEntity, API_ROOT)
  if (uri === null) {
    // Can't purge an unknown URI, do nothing
    return
  }
  store.commit('purge', uri)
  return uri
}

/**
 * Attempts to permanently delete a single entity using a DELETE request to the API.
 * This function performs the following operations when given the URI of an entity E:
 * 1. Marks E in the Vuex store with the ._meta.deleting flag
 * 2. Sends a DELETE request to the API in order to delete E from the backend (in case of failure, the
 *    deleted flag is reset and the operation is aborted)
 * 3. Finds all entities [...R] in the store that reference E (e.g. find the corresponding camp when
 *    deleting an event) and reloads them from the API
 * 4. Purges E from the Vuex store
 * @param uriOrEntity URI (or instance) of an entity which should be deleted
 * @returns Promise   resolves when the DELETE request has completed and either all related entites have
 *                    been reloaded from the API, or the failed deletion has been cleaned up.
 */
const del = function (uriOrEntity) {
  const uri = normalizeEntityUri(uriOrEntity, API_ROOT)
  if (uri === null) {
    // Can't delete an unknown URI, do nothing
    return Promise.reject(new Error(`Could not perform DELETE, "${uriOrEntity}" is not an entity or URI`))
  }
  store.commit('deleting', uri)
  return markAsDoneWhenResolved(axios.delete(API_ROOT + uri)
    .then(() => deleted(uri), ({ response }) => deletingFailed(uri, response)))
}

function valueIsArrayWithReferenceTo (value, uri) {
  return Array.isArray(value) && value.some(entry => valueIsReferenceTo(entry, uri))
}

function valueIsReferenceTo (value, uri) {
  const objectKeys = Object.keys(value)
  return objectKeys.length === 1 && objectKeys[0] === 'href' && value.href === uri
}

function findEntitiesReferencing (uri) {
  return Object.values(store.state.api)
    .filter((entity) => {
      return Object.values(entity).some(propertyValue =>
        valueIsReferenceTo(propertyValue, uri) || valueIsArrayWithReferenceTo(propertyValue, uri)
      )
    })
}

/**
 * Cleans up the Vuex store after an entity is found to be deleted (HTTP status 204 or 404) from the backend.
 * @param uri       URI of an entity which is not available (anymore) in the backend
 * @returns Promise resolves when the cleanup has completed and the Vuex store is up to date again
 */
function deleted (uri) {
  return Promise.all(findEntitiesReferencing(uri).map(outdatedEntity => reload(outdatedEntity)._meta.load))
    .then(() => purge(uri))
}

/**
 * Unsets the ._meta.deleted flag when an entity failed to be deleted from the backend.
 * @param uri      URI of an entity which failed to be deleted from backend
 * @param response HTTP response returned from the DELETE call to backend
 */
function deletingFailed (uri, response) {
  if (response.status !== 204) {
    store.commit('deletingFailed', uri)
  }
}

/**
 * Normalizes raw data from the backend and stores it into the Vuex store.
 * @param data HAL JSON data received from the backend
 */
function storeHalJsonData (data) {
  const normalizedData = normalize(data, {
    camelizeKeys: false,
    metaKey: '_meta',
    normalizeUri: (uri) => normalizeEntityUri(uri, API_ROOT),
    filterReferences: true
  })
  store.commit('add', normalizedData)
}

/**
 * Sets a flag on the given promise after completion, so that users of the promise can tell whether it is still
 * pending or not. This is needed so storeValueProxy can break infinite recursion.
 * @param promise   to be marked as done once it completes
 * @returns Promise the modified argument
 */
function markAsDoneWhenResolved (promise) {
  // empty catch is important so that our then handler runs in all cases
  promise.catch(() => {}).then(() => { promise[Symbol.for('done')] = true })
  return promise
}

/**
 * Define the API store methods available in all Vue components. The methods can be called as follows:
 *
 * // In a computed or method or lifecycle hook
 * let someEntity = this.api.get('/some/endpoint')
 * this.api.reload(someEntity)
 *
 * // In the <template> part of a Vue component
 *
...

 */
Object.defineProperties(Vue.prototype, {
  api: {
    get () { return { post, get, reload, del, patch, purge, href } }
  }
})
<commit_msg>Fix the href function<commit_after>import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'
import VueAxios from 'vue-axios'
import normalize from 'hal-json-normalizer'
import { normalizeEntityUri } from '@/store/uriUtils'
import storeValueProxy from '@/store/storeValueProxy'

Vue.use(Vuex)
axios.defaults.withCredentials = true
Vue.use(VueAxios, axios)

export const API_ROOT = process.env.VUE_APP_ROOT_API

export const state = {
  api: {}
}

export const mutations = {
  /**
   * Adds a placeholder into the store that indicates that the entity with the given URI is currently being
   * fetched from the API and not yet available.
   * @param state Vuex state
   * @param uri   URI of the object that is being fetched
   */
  addEmpty (state, uri) {
    Vue.set(state.api, uri, { _meta: { self: uri, loading: true } })
  },
  /**
   * Adds entities loaded from the API to the Vuex store.
   * @param state Vuex state
   * @param data  An object mapping URIs to entities that should be merged into the Vuex state.
   */
  add (state, data) {
    Object.keys(data).forEach((uri) => {
      Vue.set(state.api, uri, data[uri])
    })
  },
  /**
   * Removes a single entity from the Vuex store.
   * @param state Vuex state
   * @param uri   URI of the entity to be removed
   */
  purge (state, uri) {
    Vue.delete(state.api, uri)
  },
  /**
   * Marks a single entity in the Vuex store as deleting, meaning the process of deletion is currently ongoing.
   * @param state Vuex state
   * @param uri   URI of the entity that is currently being deleted
   */
  deleting (state, uri) {
    Vue.set(state.api[uri]._meta, 'deleting', true)
  },
  /**
   * Marks a single entity in the Vuex store as normal again, after it has been marked as deleting before.
   * @param state Vuex state
   * @param uri   URI of the entity that failed to be deleted
   */
  deletingFailed (state, uri) {
    Vue.set(state.api[uri]._meta, 'deleting', false)
  }
}

const store = new Vuex.Store({
  state,
  mutations,
  strict: process.env.NODE_ENV !== 'production'
})
export default store

/**
 * Sends a POST request to the backend, in order to create a new entity. Note that this does not
 * reload any collections that this new entity might be in, the caller has to do that on its own.
 * @param uriOrCollection URI (or instance) of a collection in which the entity should be created
 * @param data            Payload to be sent in the POST request
 * @returns Promise       resolves when the POST request has completed and the entity is available
 *                        in the Vuex store.
 */
export const post = function (uriOrCollection, data) {
  const uri = normalizeEntityUri(uriOrCollection, API_ROOT)
  if (uri === null) {
    return Promise.reject(new Error(`Could not perform POST, "${uriOrCollection}" is not an entity or URI`))
  }
  return markAsDoneWhenResolved(axios.post(API_ROOT + uri, data).then(({ data }) => {
    storeHalJsonData(data)
    return get(data._links.self.href)
  }))
}

/**
 * Reloads an entity from the API.
 *
 * @param uriOrEntity URI (or instance) of an entity to reload from the API
 * @returns entity    Entity from the store. Note that when fetching an object for the first time, a reactive
 *                    dummy is returned, which will be replaced with the true data through Vue's reactivity
 *                    system as soon as the API request finishes.
 */
export const reload = function (uriOrEntity) {
  return get(uriOrEntity, true)
}

/**
 * Retrieves an entity from the Vuex store, or from the API in case it is not already fetched or a reload
 * is forced.
 * This function attempts to hide all backend implementation details such as pagination, linked vs.
 * embedded relations and loading state and instead provide an easy-to-use and consistent interface for
 * developing frontend components.
 *
 * Basic usage in a Vue component:
 * computed: {
 *   allCamps () { return this.api.get('/camp').items }
 *   oneSpecificCamp () { return this.api.get(`/camp/${this.campId}`) }
 *   campUri () { return this.oneSpecificCamp._meta.self }
 *   eventTypes () { return this.oneSpecificCamp.event_types() }
 *   user () { return this.api.get().profile() } // Root endpoint ('/') and navigate through self-discovery API
 * },
 * created () {
 *   this.oneSpecificCamp._meta.load.then(() => {
 *     // do something now that the camp is loaded from the API
 *   })
 * }
 *
 * @param uriOrEntity URI (or instance) of an entity to load from the store or API
 * @param forceReload If true, the entity will be fetched from the API even if it is already in the Vuex store.
 *                    Note that the function will still return the old value in this case, but you can
 *                    wait for the new value using the ._meta.load promise.
 * @returns entity    Entity from the store. Note that when fetching an object for the first time, a reactive
 *                    dummy is returned, which will be replaced with the true data through Vue's reactivity
 *                    system as soon as the API request finishes.
 */
export const get = function (uriOrEntity, forceReload = false) {
  const forceReloadingEmbeddedCollection = forceReload && uriOrEntity._meta && uriOrEntity._meta.reload && uriOrEntity._meta.reload.uri
  const uri = forceReloadingEmbeddedCollection
    ? normalizeEntityUri(uriOrEntity._meta.reload.uri, API_ROOT)
    : normalizeEntityUri(uriOrEntity, API_ROOT)
  if (uri === null) {
    if (uriOrEntity[Symbol.for('isLoadingProxy')]) {
      // A loadingProxy is safe to return without breaking the UI.
      return uriOrEntity
    }
    // We don't know anything about the requested object, something is wrong.
    throw new Error(`Could not perform GET, "${uriOrEntity}" is not an entity or URI`)
  }

  const storeData = load(uri, forceReload)
  return forceReloadingEmbeddedCollection
    ? storeValueProxy(storeData)[uriOrEntity._meta.reload.property]()
    : storeValueProxy(storeData)
}

/**
 * Loads the entity specified by the URI from the Vuex store, or from the API if necessary. If applicable,
 * sets the load promise on the entity in the Vuex store.
 * @param uri         URI of the entity to load
 * @param forceReload If true, the entity will be fetched from the API even if it is already in the Vuex store.
 * @returns entity    the current entity data from the Vuex store. Note: This may be a reactive dummy if the
 *                    backend request is still ongoing.
 */
function load (uri, forceReload) {
  const existsInStore = (uri in store.state.api)
  const isLoading = existsInStore && (store.state.api[uri]._meta || {}).loading

  if (!existsInStore) {
    store.commit('addEmpty', uri)
  }
  if (isLoading) {
    // Reuse the loading entity and load promise that is already waiting for a pending API request
    return store.state.api[uri]
  }

  let dataFinishedLoading = Promise.resolve(store.state.api[uri])
  if (!existsInStore || forceReload) {
    dataFinishedLoading = loadFromApi(uri)
  } else if (store.state.api[uri]._meta.load) {
    // reuse the existing promise from the store if possible
    dataFinishedLoading = store.state.api[uri]._meta.load
  }

  // We mutate the store state here without telling Vuex about it, so it won't complain and won't make load reactive.
  // The promise is needed in the store for some special cases when a loading entity is requested a second time with
  // this.api.get(...) or this.api.reload(...).
  store.state.api[uri]._meta.load = markAsDoneWhenResolved(dataFinishedLoading)

  return store.state.api[uri]
}

/**
 * Loads the entity specified by the URI from the API and stores it into the Vuex store. Returns a promise
 * that resolves to the raw data stored in the Vuex store (needs to be wrapped into a storeValueProxy before
 * being usable in Vue components).
 * @param uri       URI of the entity to load from the API
 * @returns Promise resolves to the raw data stored in the Vuex store after the API request completes, or
 *                  rejects when the API request fails
 */
function loadFromApi (uri) {
  return new Promise((resolve, reject) => {
    axios.get(API_ROOT + uri).then(
      ({ data }) => {
        // Workaround because API adds page parameter even to first page when it was not requested that way
        // TODO fix backend API and remove the next line
        data._links.self.href = uri
        storeHalJsonData(data)
        resolve(store.state.api[uri])
      },
      ({ response }) => {
        if (response.status === 404) {
          return deleted(uri)
        }
        reject(response)
      }
    )
  })
}

/**
 * Loads the URI of a related entity from the store, or the API in case it is not already fetched.
 *
 * @param uriOrEntity URI (or instance) of an entity from the API
 * @param relation    the name of the relation for which the URI should be retrieved
 * @returns Promise   resolves to the URI of the related entity.
 */
export const href = async function (uriOrEntity, relation) {
  const self = normalizeEntityUri(await get(uriOrEntity)._meta.load, API_ROOT)
  const href = (store.state.api[self][relation] || {}).href
  return href ? API_ROOT + href : href
}

/**
 * Sends a PATCH request to the backend, in order to update some fields in an existing entity.
 * @param uriOrEntity URI (or instance) of an entity which should be updated
 * @param data        Payload (fields to be updated) to be sent in the PATCH request
 * @returns Promise   resolves when the PATCH request has completed and the updated entity is available
 *                    in the Vuex store.
 */
const patch = function (uriOrEntity, data) {
  const uri = normalizeEntityUri(uriOrEntity, API_ROOT)
  if (uri === null) {
    return Promise.reject(new Error(`Could not perform PATCH, "${uriOrEntity}" is not an entity or URI`))
  }
  return markAsDoneWhenResolved(axios.patch(API_ROOT + uri, data).then(({ data }) => {
    // Workaround because API adds page parameter even to first page when it was not requested that way
    // TODO fix backend API and remove the next line
    data._links.self.href = uri
    storeHalJsonData(data)
    return get(uri)
  }, ({ response }) => {
    if (response.status === 404) {
      return deleted(uri)
    }
  }))
}

/**
 * Removes a single entity from the Vuex store (but does not delete it using the API). Note that if the
 * entity is currently referenced and displayed through any other entity, the reactivity system will
 * immediately re-fetch the purged entity from the API in order to re-display it.
 * @param uriOrEntity URI (or instance) of an entity which should be removed from the Vuex store
 */
const purge = function (uriOrEntity) {
  const uri = normalizeEntityUri(uriOrEntity, API_ROOT)
  if (uri === null) {
    // Can't purge an unknown URI, do nothing
    return
  }
  store.commit('purge', uri)
  return uri
}

/**
 * Attempts to permanently delete a single entity using a DELETE request to the API.
 * This function performs the following operations when given the URI of an entity E:
 * 1. Marks E in the Vuex store with the ._meta.deleting flag
 * 2. Sends a DELETE request to the API in order to delete E from the backend (in case of failure, the
 *    deleted flag is reset and the operation is aborted)
 * 3. Finds all entities [...R] in the store that reference E (e.g. find the corresponding camp when
 *    deleting an event) and reloads them from the API
 * 4. Purges E from the Vuex store
 * @param uriOrEntity URI (or instance) of an entity which should be deleted
 * @returns Promise   resolves when the DELETE request has completed and either all related entites have
 *                    been reloaded from the API, or the failed deletion has been cleaned up.
 */
const del = function (uriOrEntity) {
  const uri = normalizeEntityUri(uriOrEntity, API_ROOT)
  if (uri === null) {
    // Can't delete an unknown URI, do nothing
    return Promise.reject(new Error(`Could not perform DELETE, "${uriOrEntity}" is not an entity or URI`))
  }
  store.commit('deleting', uri)
  return markAsDoneWhenResolved(axios.delete(API_ROOT + uri)
    .then(() => deleted(uri), ({ response }) => deletingFailed(uri, response)))
}

function valueIsArrayWithReferenceTo (value, uri) {
  return Array.isArray(value) && value.some(entry => valueIsReferenceTo(entry, uri))
}

function valueIsReferenceTo (value, uri) {
  const objectKeys = Object.keys(value)
  return objectKeys.length === 1 && objectKeys[0] === 'href' && value.href === uri
}

function findEntitiesReferencing (uri) {
  return Object.values(store.state.api)
    .filter((entity) => {
      return Object.values(entity).some(propertyValue =>
        valueIsReferenceTo(propertyValue, uri) || valueIsArrayWithReferenceTo(propertyValue, uri)
      )
    })
}

/**
 * Cleans up the Vuex store after an entity is found to be deleted (HTTP status 204 or 404) from the backend.
 * @param uri       URI of an entity which is not available (anymore) in the backend
 * @returns Promise resolves when the cleanup has completed and the Vuex store is up to date again
 */
function deleted (uri) {
  return Promise.all(findEntitiesReferencing(uri).map(outdatedEntity => reload(outdatedEntity)._meta.load))
    .then(() => purge(uri))
}

/**
 * Unsets the ._meta.deleted flag when an entity failed to be deleted from the backend.
 * @param uri      URI of an entity which failed to be deleted from backend
 * @param response HTTP response returned from the DELETE call to backend
 */
function deletingFailed (uri, response) {
  if (response.status !== 204) {
    store.commit('deletingFailed', uri)
  }
}

/**
 * Normalizes raw data from the backend and stores it into the Vuex store.
 * @param data HAL JSON data received from the backend
 */
function storeHalJsonData (data) {
  const normalizedData = normalize(data, {
    camelizeKeys: false,
    metaKey: '_meta',
    normalizeUri: (uri) => normalizeEntityUri(uri, API_ROOT),
    filterReferences: true
  })
  store.commit('add', normalizedData)
}

/**
 * Sets a flag on the given promise after completion, so that users of the promise can tell whether it is still
 * pending or not. This is needed so storeValueProxy can break infinite recursion.
 * @param promise   to be marked as done once it completes
 * @returns Promise the modified argument
 */
function markAsDoneWhenResolved (promise) {
  // empty catch is important so that our then handler runs in all cases
  promise.catch(() => {}).then(() => { promise[Symbol.for('done')] = true })
  return promise
}

/**
 * Define the API store methods available in all Vue components. The methods can be called as follows:
 *
 * // In a computed or method or lifecycle hook
 * let someEntity = this.api.get('/some/endpoint')
 * this.api.reload(someEntity)
 *
 * // In the <template> part of a Vue component
 *
...

 */
Object.defineProperties(Vue.prototype, {
  api: {
    get () { return { post, get, reload, del, patch, purge, href } }
  }
})
<|endoftext|>


<commit_before>;
(function(mag) {

  "use strict";

  var render = {
    roots: [],
    templates: {},
    cache: {}
  }

  var unloaders = [],
    cached = [],
    pendingRequests = 0

    function callView(elementClone, module, i) {
      var args = module.getArgs(i)

      var mod = module.modules[i]
      var controller = module.controllers[i]

      // var controllerConstructor = mod.controller.$original || mod.controller
      // var controller = controllerConstructor === cached.controllerConstructor ? cached.controller : new(mod.controller || function() {})

      mod.view(elementClone, controller)

      // if (controller.onunload) unloaders.push({
      //   controller: controller,
      //   handler: controller.onunload
      // })
    }

  render.callOnload = function(module) {
    for (var i = 0, controller; controller = module.controllers[i]; i++) {
      // call once
      if (controller.onload && !controller.called) {
        controller.onload.call(null, module.elements[i])
        controller.called = 1
      }
    }
  }
  render.callConfigs = function(configs) {
    for (var i = 0, len = configs.length; i < len; i++) configs[i]()
  }
  var cache = []
  render.redraw = function(module, fill, WatchJS) {
    module = module || render.module || {}
    this.fun || (this.fun = debounce(function() {
      // clear existing configs
      fill.configs.splice(0, fill.configs.length)

      render.doLoop(module, fill, WatchJS)
    }))
    this.fun()
  }

  render.doLoop = function(module, fill, WatchJS) {
    for (var i = 0, root; root = render.roots[i]; i++) {
      mag.running = true

      if (module.controllers[i]) {
        if (!render.innerLoop(module, fill, i, WatchJS)) {
          continue
        }
      }
    }
  }

  render.innerLoop = function(module, fill, i, WatchJS) {
    var elementClone = module.elements[i]
    var args = module.getArgs(i)

    if (cache[i] && cache[i] === JSON.stringify(args[0])) {
      return false
    }
    callView(elementClone, module, i)
    cache[i] = JSON.stringify(args[0])

    render.setupWatch(WatchJS, args, fill, elementClone, i, module)

    fill.fill(elementClone, args[0])
    render.callConfigs(fill.configs)
  }

  render.doWatch = function(fill, ele, i, module, changeId) {
    mag.running = true
    var args = module.getArgs(i)

    // check if data changed
    if (cache[i] && cache[i] === JSON.stringify(args[0])) {
      return
    }
    callView(ele, module, i)
    cache[i] = JSON.stringify(args[0])

    fill.fill(ele, args[0])

    render.callConfigs(fill.configs)

    // call onload if present in all controllers
    render.callOnload(module)

    mag.running = false
  }

  render.setupWatch = function(WatchJS, args, fill, elementClone, i, module) {
    WatchJS.watch(args[0], debounce(render.doWatch.bind(null, fill, elementClone, i, module)))
  }
  var $cancelAnimationFrame = window.cancelAnimationFrame || window.clearTimeout;
  var $requestAnimationFrame = window.requestAnimationFrame || window.setTimeout;

  var debounce = function(fn, threshhold) {
    var lastRedrawCallTime, FRAME_BUDGET = threshhold || 16,
      deferTimer
    return function() {
      if (deferTimer) {
        if (+new Date - lastRedrawCallTime > FRAME_BUDGET || $requestAnimationFrame === window.requestAnimationFrame) {
          // hold on to it
          if (deferTimer > 0) $cancelAnimationFrame(deferTimer)

          deferTimer = $requestAnimationFrame(function() {
            lastRedrawCallTime = +new Date
            fn.apply(this, arguments);
          }, FRAME_BUDGET)
        }
      } else {
        lastRedrawCallTime = +new Date
        fn.apply(this, arguments)
        deferTimer = $requestAnimationFrame(function() {
          deferTimer = null
        }, FRAME_BUDGET)
      }
    }
  }

  mag.render = render

})(window.mag || {})
<commit_msg>version bump to 0.7<commit_after>;
(function(mag) {

  "use strict";

  var render = {
    roots: [],
    templates: {},
    cache: {}
  }

  var unloaders = [],
    cached = [],
    pendingRequests = 0

    function callView(elementClone, module, i) {
      var args = module.getArgs(i)

      var mod = module.modules[i]
      var controller = module.controllers[i]

      // var controllerConstructor = mod.controller.$original || mod.controller
      // var controller = controllerConstructor === cached.controllerConstructor ? cached.controller : new(mod.controller || function() {})

      mod.view(elementClone, controller)

      // if (controller.onunload) unloaders.push({
      //   controller: controller,
      //   handler: controller.onunload
      // })
    }

  render.callOnload = function(module) {
    for (var i = 0, controller; controller = module.controllers[i]; i++) {
      // call once
      if (controller.onload && !controller.called) {
        controller.onload.call(null, module.elements[i])
        controller.called = 1
      }
    }
  }
  render.callConfigs = function(configs) {
    for (var i = 0, len = configs.length; i < len; i++) configs[i]()
  }
  var cache = []
  render.redraw = function(module, fill, WatchJS) {
    module = module || render.module || {}
    this.fun || (this.fun = debounce(function() {
      // clear existing configs
      fill.configs.splice(0, fill.configs.length)

      render.doLoop(module, fill, WatchJS)
    }))
    this.fun()
  }

  render.doLoop = function(module, fill, WatchJS) {
    for (var i = 0, root; root = render.roots[i]; i++) {
      mag.running = true

      if (module.controllers[i]) {
        if (!render.innerLoop(module, fill, i, WatchJS)) {
          continue
        }
      }
    }
  }

  render.innerLoop = function(module, fill, i, WatchJS) {
    var elementClone = module.elements[i]
    var args = module.getArgs(i)

    if (cache[i] && cache[i] === JSON.stringify(args[0])) {
      return false
    }
    callView(elementClone, module, i)
    cache[i] = JSON.stringify(args[0])

    render.setupWatch(WatchJS, args, fill, elementClone, i, module)

    fill.fill(elementClone, args[0])
    render.callConfigs(fill.configs)

    // call onload if present in all controllers
    render.callOnload(module)
  }

  render.doWatch = function(fill, ele, i, module, changeId) {
    mag.running = true
    var args = module.getArgs(i)

    // check if data changed
    if (cache[i] && cache[i] === JSON.stringify(args[0])) {
      return
    }
    callView(ele, module, i)
    cache[i] = JSON.stringify(args[0])

    fill.fill(ele, args[0])

    render.callConfigs(fill.configs)

    mag.running = false
  }

  render.setupWatch = function(WatchJS, args, fill, elementClone, i, module) {
    WatchJS.watch(args[0], debounce(render.doWatch.bind(null, fill, elementClone, i, module)))
  }
  var $cancelAnimationFrame = window.cancelAnimationFrame || window.clearTimeout;
  var $requestAnimationFrame = window.requestAnimationFrame || window.setTimeout;

  var debounce = function(fn, threshhold) {
    var lastRedrawCallTime, FRAME_BUDGET = threshhold || 16,
      deferTimer
    return function() {
      if (+new Date - lastRedrawCallTime > FRAME_BUDGET || $requestAnimationFrame === window.requestAnimationFrame) {
        // hold on to it
        if (deferTimer > 0) $cancelAnimationFrame(deferTimer)

        deferTimer = $requestAnimationFrame(function() {
          lastRedrawCallTime = +new Date
          fn.apply(this, arguments);
        }, FRAME_BUDGET)
      } else {
        lastRedrawCallTime = +new Date
        fn.apply(this, arguments)
        deferTimer = $requestAnimationFrame(function() {
          deferTimer = null
        }, FRAME_BUDGET)
      }
    }
  }

  mag.render = render

})(window.mag || {})
<|endoftext|>


<commit_before>var express = require('express')
  , auth = require('mongoose-auth')
  , env = require('./env')
  , util = require('util')
  , port = env.port
  , secrets = env.secrets
  , EventEmitter = require('events').EventEmitter
  , Stats = require('../models/stats')
  , Twitter = require('../models/twitter')
  , rollbar = require('rollbar');

require('jadevu');

// express
var app = module.exports = express();

// some paths
app.paths = {
  public: __dirname + '/../public',
  views: __dirname + '/../views'
};

// uncaught error handling
process.on('uncaughtException', function(e) {
  util.debug(e.stack.red);
});

// utilities & hacks
require('graceful-fs');
require('colors');
require('../lib/render2');
require('../lib/underscore.shuffle');
require('../lib/regexp-extensions');
app.dynamicHelpers = require('../lib/dynamic-helpers');

flash = require("connect-flash");

// events
app.events = new EventEmitter();

// db
app.db = require('../models')(env.mongo_url);
app.db.app = app;  // sooo hacky

// stats (kinda hacky)
app.stats = new Stats(app.db, function(err) {
  if (err) throw err;
});
app.stats.on('change', function(stats) {
  app.events.emit('updateStats', stats);
});

// twitter
if (secrets.twitterUser) {
  app.twitter = new Twitter(secrets.twitterUser)
}

// state (getting pretty gross)
app.disable('splash');
app.disable('pre-registration');  // just the countdown
app.disable('registration');       // months beforehand
app.enable('pre-coding');        // week beforehand
app.disable('coding');            // coding + several hours before
app.disable('post-coding');            // coding + several hours before
app.disable('voting');            // after
app.disable('post-voting');            // coding + several hours before
app.disable('winners');        // after winners are selected

app.configure(function() {
  require('./nap');

  app.use(express.compress());

  app.use(flash());

  rollbar.init(secrets.rollbar);
  rollbar.reportMessage('Starting nko5');
  rollbar.shutdown();
  app.use(rollbar.errorHandler(secrets.rollbar));
});

app.configure('development', function() {
  app.use(express.static(app.paths.public));
  app.use(nap.middleware);
  app.locals.pretty = true;
  require('../lib/mongo-log')(app.db.mongo);
});
app.configure('production', function() {
  app.use(express.static(app.paths.public, { maxAge: 1000*60*5 }));
  app.use(function(req, res, next) {
    return next();
    if (req.connection.remoteAddress !== '127.0.0.1' && req.headers.host !== 'nodeknockout.com')
      res.redirect('http://nodeknockout.com' + req.url);
    else
      next();
  });
});

app.configure(function() {
  var RedisStore = require('connect-redis')(express);

  // setup redis client
  var redis = require('redis');
  var url = require('url');

  var redisClient;
  if (process.env.REDISCLOUD_URL) {
    var redisURL = url.parse(process.env.REDISCLOUD_URL);
    redisClient = redis.createClient(redisURL.port, redisURL.hostname, {no_ready_check: true});
    redisClient.auth(redisURL.auth.split(":")[1]);
  } else {
    redisClient = redis.createClient()
  }

  // cookies and sessions
  app.use(express.cookieParser());
  app.use(express.session({
    secret: secrets.session,
    store: new RedisStore({ client: redisClient}),
    cookie: { path: '/', httpOnly: true, maxAge: 1000*60*60*24*28 }
  }));

  app.use(express.bodyParser());
  app.use(express.methodOverride());

  // hacky solution for post commit hooks not to check csrf
  app.use(require('../controllers/ok')(app));
  app.use(require('../controllers/commits')(app));
  app.use(require('../controllers/deploys')(app));

  // csrf protection
  app.use(express.csrf());
  app.use(function(req, res, next) {
    if (req.body) delete req.body._csrf;
    next();
  });

  // auth middleware needs to be pretty high up here
  app.use(auth.middleware());
  app.use(require('../controllers/middleware').loadMyTeam);

  // helpers
  auth.helpExpress(app);
  require('../helpers')(app);

  app.use(express.logger());
  app.use(app.router);

  // error handling
  app.use(function(req, res, next){ // If it arrives here, it's because it didn't matched with the rest
    res.status(404);
    if (req.accepts('html')) {
      res.render2('errors/404', { status: 404 });
      return;
    }
    if (req.accepts('json')) {
      res.send({ error: 'Not found' });
      return;
    }
    res.type('txt').send('Not found');
  });

  // var ratchetErrorHandler = ratchetio.errorHandler(); No key for this thing around here
  // app.use(function(err, req, res, next) {
  //   if (typeof(err) !== 'number') {
  //     ratchetErrorHandler(err, req, res, next);
  //   } else {
  //     next(err, req, res);
  //   }
  // });

  require('./../controllers/errors.js')
  app.set('views', app.paths.views);
  app.set('view engine', 'jade');
});

// helpers
auth.helpExpress(app);
require('../helpers')(app);

// express 3 requires instantiating a http.Server to attach socket.io to
var server = require('http').createServer(app)
// app.ws = require('socket.io').listen(server);
server.listen(port);

// app.ws.set('log level', 1);
// app.ws.set('browser client minification', true);

server.on('listening', function() {
  require('util').log('listening on ' + ('0.0.0.0:' + port).cyan);

  // if run as root, downgrade to the owner of this file
  if (env.production && process.getuid() === 0)
    require('fs').stat(__filename, function(err, stats) {
      if (err) return util.log(err)
      process.setuid(stats.uid);
    });
});
<commit_msg>switch to coding!<commit_after>var express = require('express')
  , auth = require('mongoose-auth')
  , env = require('./env')
  , util = require('util')
  , port = env.port
  , secrets = env.secrets
  , EventEmitter = require('events').EventEmitter
  , Stats = require('../models/stats')
  , Twitter = require('../models/twitter')
  , rollbar = require('rollbar');

require('jadevu');

// express
var app = module.exports = express();

// some paths
app.paths = {
  public: __dirname + '/../public',
  views: __dirname + '/../views'
};

// uncaught error handling
process.on('uncaughtException', function(e) {
  util.debug(e.stack.red);
});

// utilities & hacks
require('graceful-fs');
require('colors');
require('../lib/render2');
require('../lib/underscore.shuffle');
require('../lib/regexp-extensions');
app.dynamicHelpers = require('../lib/dynamic-helpers');

flash = require("connect-flash");

// events
app.events = new EventEmitter();

// db
app.db = require('../models')(env.mongo_url);
app.db.app = app;  // sooo hacky

// stats (kinda hacky)
app.stats = new Stats(app.db, function(err) {
  if (err) throw err;
});
app.stats.on('change', function(stats) {
  app.events.emit('updateStats', stats);
});

// twitter
if (secrets.twitterUser) {
  app.twitter = new Twitter(secrets.twitterUser)
}

// state (getting pretty gross)
app.disable('splash');
app.disable('pre-registration');  // just the countdown
app.disable('registration');       // months beforehand
app.disable('pre-coding');        // week beforehand
app.enable('coding');            // coding + several hours before
app.disable('post-coding');            // coding + several hours before
app.disable('voting');            // after
app.disable('post-voting');            // coding + several hours before
app.disable('winners');        // after winners are selected

app.configure(function() {
  require('./nap');

  app.use(express.compress());

  app.use(flash());

  rollbar.init(secrets.rollbar);
  rollbar.reportMessage('Starting nko5');
  rollbar.shutdown();
  app.use(rollbar.errorHandler(secrets.rollbar));
});

app.configure('development', function() {
  app.use(express.static(app.paths.public));
  app.use(nap.middleware);
  app.locals.pretty = true;
  require('../lib/mongo-log')(app.db.mongo);
});
app.configure('production', function() {
  app.use(express.static(app.paths.public, { maxAge: 1000*60*5 }));
  app.use(function(req, res, next) {
    return next();
    if (req.connection.remoteAddress !== '127.0.0.1' && req.headers.host !== 'nodeknockout.com')
      res.redirect('http://nodeknockout.com' + req.url);
    else
      next();
  });
});

app.configure(function() {
  var RedisStore = require('connect-redis')(express);

  // setup redis client
  var redis = require('redis');
  var url = require('url');

  var redisClient;
  if (process.env.REDISCLOUD_URL) {
    var redisURL = url.parse(process.env.REDISCLOUD_URL);
    redisClient = redis.createClient(redisURL.port, redisURL.hostname, {no_ready_check: true});
    redisClient.auth(redisURL.auth.split(":")[1]);
  } else {
    redisClient = redis.createClient()
  }

  // cookies and sessions
  app.use(express.cookieParser());
  app.use(express.session({
    secret: secrets.session,
    store: new RedisStore({ client: redisClient}),
    cookie: { path: '/', httpOnly: true, maxAge: 1000*60*60*24*28 }
  }));

  app.use(express.bodyParser());
  app.use(express.methodOverride());

  // hacky solution for post commit hooks not to check csrf
  app.use(require('../controllers/ok')(app));
  app.use(require('../controllers/commits')(app));
  app.use(require('../controllers/deploys')(app));

  // csrf protection
  app.use(express.csrf());
  app.use(function(req, res, next) {
    if (req.body) delete req.body._csrf;
    next();
  });

  // auth middleware needs to be pretty high up here
  app.use(auth.middleware());
  app.use(require('../controllers/middleware').loadMyTeam);

  // helpers
  auth.helpExpress(app);
  require('../helpers')(app);

  app.use(express.logger());
  app.use(app.router);

  // error handling
  app.use(function(req, res, next){ // If it arrives here, it's because it didn't matched with the rest
    res.status(404);
    if (req.accepts('html')) {
      res.render2('errors/404', { status: 404 });
      return;
    }
    if (req.accepts('json')) {
      res.send({ error: 'Not found' });
      return;
    }
    res.type('txt').send('Not found');
  });

  // var ratchetErrorHandler = ratchetio.errorHandler(); No key for this thing around here
  // app.use(function(err, req, res, next) {
  //   if (typeof(err) !== 'number') {
  //     ratchetErrorHandler(err, req, res, next);
  //   } else {
  //     next(err, req, res);
  //   }
  // });

  require('./../controllers/errors.js')
  app.set('views', app.paths.views);
  app.set('view engine', 'jade');
});

// helpers
auth.helpExpress(app);
require('../helpers')(app);

// express 3 requires instantiating a http.Server to attach socket.io to
var server = require('http').createServer(app)
// app.ws = require('socket.io').listen(server);
server.listen(port);

// app.ws.set('log level', 1);
// app.ws.set('browser client minification', true);

server.on('listening', function() {
  require('util').log('listening on ' + ('0.0.0.0:' + port).cyan);

  // if run as root, downgrade to the owner of this file
  if (env.production && process.getuid() === 0)
    require('fs').stat(__filename, function(err, stats) {
      if (err) return util.log(err)
      process.setuid(stats.uid);
    });
});
<|endoftext|>


<commit_before>/* eslint-disable react/no-unknown-property */
import React, { Component } from 'react';
import Container from '../../shared/Container';
import ContainerWrapper from '../../shared/ContainerWrapper';
import AlternateContainerWrapper from '../../shared/AlternateContainerWrapper';
import Heading from '../../shared/Heading';
import SimpleEditor from '../../shared/SimpleEditor';
import styles from './styles.css';
import stickerPlugin from 'draft-js-sticker-plugin';
import { fromJS } from 'immutable';

const stickers = fromJS({
  data: {
    'b3aa388f-b9f4-45b0-bba5-d92cf2caa48b': {
      id: 'b3aa388f-b9f4-45b0-bba5-d92cf2caa48b',
      url: 'images/unicorn-4.png',
    },
    'adec3f13-823c-47c3-b4d1-be4f68dd9d6d': {
      id: 'adec3f13-823c-47c3-b4d1-be4f68dd9d6d',
      url: 'images/unicorn-1.png',
    },
    'e14b5a20-1025-4952-b731-41cd4b118ba0': {
      id: 'e14b5a20-1025-4952-b731-41cd4b118ba0',
      url: 'images/unicorn-6.png',
    },
    '659a0dbf-5f85-4f32-999d-eb9ba6b0f417': {
      id: '659a0dbf-5f85-4f32-999d-eb9ba6b0f417',
      url: 'images/unicorn-2.png',
    },
    'fab0ae95-ae95-4775-b484-72c290437602': {
      id: 'fab0ae95-ae95-4775-b484-72c290437602',
      url: 'images/unicorn-5.png',
    },
    '71853190-8acd-4d3b-b4fd-63f7b0648daa': {
      id: '71853190-8acd-4d3b-b4fd-63f7b0648daa',
      url: 'images/unicorn-3.png',
    },
  },
});

const plugins = [stickerPlugin({
  stickers,
})];
const Editor = SimpleEditor({plugins, pluginNames: ['sticker']});

export default class App extends Component {

  // description what the plugin does

  // h2 "Simple Example"
  // an simple editor with hashtag plugin (can be copied from unicorn editor)
  // simple code example

  // h2 "Custom Styling"
  // an editor with hashtag plugin & custom styles (can be copied from unicorn editor)
  // custom styles code example

  // h2 "API"
  // a table explaining what is exported

  render() {
    return (

        <AlternateContainerWrapper>
          <Container>
            <Heading level={ 2 }>Hashtag</Heading>

Hashtag

          </Container>
        </AlternateContainerWrapper>
        <ContainerWrapper>
          <Container>
            <Heading level={ 2 }>Example</Heading>
            <Editor />
          </Container>
        </ContainerWrapper>

    );
  }
}
<commit_msg>docs(Sticker): fixing image path issue<commit_after>/* eslint-disable react/no-unknown-property */
import React, { Component } from 'react';
import Container from '../../shared/Container';
import ContainerWrapper from '../../shared/ContainerWrapper';
import AlternateContainerWrapper from '../../shared/AlternateContainerWrapper';
import Heading from '../../shared/Heading';
import SimpleEditor from '../../shared/SimpleEditor';
import styles from './styles.css';
import stickerPlugin from 'draft-js-sticker-plugin';
import { fromJS } from 'immutable';

const stickers = fromJS({
  data: {
    'b3aa388f-b9f4-45b0-bba5-d92cf2caa48b': {
      id: 'b3aa388f-b9f4-45b0-bba5-d92cf2caa48b',
      url: '../images/unicorn-4.png',
    },
    'adec3f13-823c-47c3-b4d1-be4f68dd9d6d': {
      id: 'adec3f13-823c-47c3-b4d1-be4f68dd9d6d',
      url: '../images/unicorn-1.png',
    },
    'e14b5a20-1025-4952-b731-41cd4b118ba0': {
      id: 'e14b5a20-1025-4952-b731-41cd4b118ba0',
      url: '../images/unicorn-6.png',
    },
    '659a0dbf-5f85-4f32-999d-eb9ba6b0f417': {
      id: '659a0dbf-5f85-4f32-999d-eb9ba6b0f417',
      url: '../images/unicorn-2.png',
    },
    'fab0ae95-ae95-4775-b484-72c290437602': {
      id: 'fab0ae95-ae95-4775-b484-72c290437602',
      url: '../images/unicorn-5.png',
    },
    '71853190-8acd-4d3b-b4fd-63f7b0648daa': {
      id: '71853190-8acd-4d3b-b4fd-63f7b0648daa',
      url: '../images/unicorn-3.png',
    },
  },
});

const plugins = [stickerPlugin({
  stickers,
})];
const Editor = SimpleEditor({plugins, pluginNames: ['sticker']});

export default class App extends Component {

  // description what the plugin does

  // h2 "Simple Example"
  // an simple editor with hashtag plugin (can be copied from unicorn editor)
  // simple code example

  // h2 "Custom Styling"
  // an editor with hashtag plugin & custom styles (can be copied from unicorn editor)
  // custom styles code example

  // h2 "API"
  // a table explaining what is exported

  render() {
    return (

        <AlternateContainerWrapper>
          <Container>
            <Heading level={ 2 }>Hashtag</Heading>

Hashtag

          </Container>
        </AlternateContainerWrapper>
        <ContainerWrapper>
          <Container>
            <Heading level={ 2 }>Example</Heading>
            <Editor />
          </Container>
        </ContainerWrapper>

    );
  }
}
<|endoftext|>