<commit_before>var Promise = require('bluebird');
var Stripe = require('stripe');
var fetch = require('node-fetch');
var utils = require('../../lib/utils');
var Boom = require('boom');

module.exports = function handleCustomerRequest( hoodie, request, reply ) {
	var logger = request.raw.res.chrome;

	if ( request.method !== 'post' ) {
		return reply( Boom.methodNotAllowed() );
	}

	var stripeKey = hoodie.config.get('stripeKey');
	if ( !stripeKey ) {
		return reply( Boom.expectationFailed( 'Stripe key not configured') );
	}
	var stripe = Stripe(stripeKey);
	if ( !hoodie.config.get('taxamoKey') ) {
		return reply( Boom.expectationFailed( 'Taxamo key not configured') );
	}
	if ( !request.payload.args || !request.payload.args[0] ||
			!request.payload.args[0].plan ) {
		return reply( Boom.badRequest( 'plan property is mandatory') );
	}

	var promises = [ requestSession( request ) ];

	if ( !global.allStripePlans ) {
		promises.push( utils.fetchAllStripePlans( stripe ) );
	}

	Promise.all(promises)
		.then(function() {console.log('A');
			return arguments[0];
		})
		.then(function(userName) {console.log('B');
			return getUserDoc(
				stripe, hoodie, userName, request, logger );
		})
		// verify stripeToken and extract country code
		.then(function(nextDoc) {console.log('C');
			if ( request.payload.method === 'customers.create' &&
					( request.payload.args[0] || {} ).source ) {
				return stripeRetrieveToken(
					stripe, hoodie, nextDoc, request, logger );
			}

			return nextDoc;
		})
		// create palceholder taxamo transaction
		.then(function(nextDoc) {console.log('D');
			if ( request.payload.method === 'customers.create' &&
					( request.payload.args[0] || {} ).source ) {
				return taxamoTransactionCreate(
					stripe, hoodie, nextDoc, request, logger );
			}

			return nextDoc;
		})
		// build plan Id that matches VAT amount
		.then(function(nextDoc) {console.log('E');
			return buildLocalPlanId(
				stripe, hoodie, nextDoc, request, logger );
		})
		.then(function(nextDoc) {console.log('F');
			if ( !(nextDoc.localPlanId in global.allStripePlans ) ) {
				return stripePlanCreate(
					stripe, hoodie, nextDoc, request, logger );
			}

			return nextDoc;
		})
		// create stripe customer with reference to placeholder transaction
		.then(function(nextDoc) {console.log('G');
			if ( request.payload.method === 'customers.create' ) {
				return stripeCustomerCreate(
					stripe, hoodie, nextDoc, request, logger );
			}

			return nextDoc;
		})
		// create or update subscription
		.then(function( nextDoc ) {console.log('H');
			if ( request.payload.method === 'customers.update' ) {
				return stripeSubscriptionUpdate(
					stripe, hoodie, nextDoc, request, logger );
			}

			return nextDoc;
		})
		.then(function( nextDoc ) {console.log('I');
			return updateAccount( stripe, hoodie, nextDoc, request, logger );
		})
		.then(function(nextDoc) {console.log('J');
			return reply( null, { plan: nextDoc.stripe.plan });
		})
			.catch(function( error ) {console.log('K');
			console.log(error, error.stack);
			logger.error(error, error.stack);
			return reply( error );
		});
}

function requestSession( request ) {
	var sessionUri =
		'https://' +
		request.info.host +
		'/_api/_session';
console.log('request.info.remoteAddress', request.info.remoteAddress);
console.log('request.raw.req.connection.remoteAddress', request.raw.req.connection.remoteAddress);
	return fetch(sessionUri, {
			method: 'get',
			headers: {
				'authorization': request.headers.authorization,
				'accept': 'application/json',
			},
			cookie: request.headers.cookie,
		})
		.then(utils.checkStatus)
		.then(utils.parseJson)
		.then(checkSession);
}

function checkSession( response ) {
	if ( !response.userCtx || !response.userCtx.name ) {
		throw Boom.unauthorized('Anonymous users can\'t subscribe');
	}
	else {
		return response.userCtx.name.replace(/^user\//, '');
	}
};

function getUserDoc( stripe, hoodie, userName, request, logger ) {
	return new Promise(function(resolve, reject) {

		hoodie.account.find('user', userName, function( error, userDoc ) {
			if ( error ) {
				return reject( error );
			}

			if ( !userDoc.stripe ) {
				userDoc.stripe = {};
			}

			logger.log( userDoc );
			return resolve( userDoc );
		});

	});
}

function stripeRetrieveToken( stripe, hoodie, userDoc, request, logger ) {
	return new Promise(function(resolve, reject) {

		var requestData = request.payload.args[0];

		stripe.tokens.retrieve( requestData.source, function( error, token ) {
			if ( error ) {
				return reject( error );
			}

			userDoc.stripe.tokenId = token.id;
			userDoc.stripe.country = token.card.country;
console.log('client_ip', token.client_ip);
			logger.log( userDoc );
			return resolve( userDoc );
		});

	});
}

function taxamoTransactionCreate( stripe, hoodie, userDoc, request, logger ) {
	var customer = userDoc.stripe;
	var requestData = request.payload.args[0];

	var payload = {
		transaction: {
			'transaction_lines': [
				{
					'custom_id': 'dontRemoveThisProp',
					'amount': 0,
				},
			],
			'currency_code': requestData.currencyCode || 'USD',
			'description': 'placeholder transaction',
			'status': 'C',
			'buyer_credit_card_prefix': requestData.cardPrefix,
			'force_country_code': customer.country,
		},
		'private_token': hoodie.config.get('taxamoKey'),
	};

	if (request.info.remoteAddress !== '127.0.0.1') {
		payload['buyer_ip'] = request.info.remoteAddress;
	}
	if (requestData.taxNumber) {
		payload['buyer_tax_number'] = requestData.taxNumber;
	}

	return fetch('https://api.taxamo.com/api/v1/transactions', {
		method: 'post',
		headers: {
			'Content-Type': 'application/json',
			'Accept': 'application/json',
			'Private-Token': hoodie.config.get('taxamoKey'),
		},
		body: JSON.stringify(payload),
	})
	.then(utils.checkStatus)
	.then(utils.parseJson)
	.then(function(body) {
		userDoc.taxamo = {
			id: body.transaction.key,
			taxNumber: body.transaction['buyer_tax_number'],
			taxRate: body.transaction['transaction_lines'][0]['tax_rate'],
			taxRegion: body.transaction['tax_region'],
			taxCountryCode: body.transaction['tax_region'],
			taxDeducted: body.transaction['tax_deducted'],
		};

		logger.log( userDoc, body );
		return userDoc;
	});
}

function buildLocalPlanId( stripe, hoodie, userDoc, request, logger ) {
	return new Promise(function(resolve, reject) {
		var requestData = request.payload.args[0];

		if ( requestData.plan.split('_').indexOf('taxfree') === -1 ) {
			return reject( Boom.forbidden('Base plan isn\'t taxfree.') );
		}

		var basePlan = global.allStripePlans[requestData.plan];
		if ( !basePlan ) {
			return reject( Boom.forbidden('Base plan doesn\'t exist.') );
		}

		if ( !userDoc.stripe || !userDoc.taxamo ) {
			return reject( Boom.forbidden(
				'Customer doesn\'t exist. Cannot update subscription.') );
		}

		userDoc.localPlanId = requestData.plan;
		if ( userDoc.taxamo.taxRegion !== 'EU' ) {
			logger.log( userDoc );
			return resolve( userDoc );
		}

		// User is in the EU. Let's find a plan that matches local VAT
		if ( userDoc.taxamo.taxRate !== 0 ) {
			userDoc.localPlanId =
				userDoc.localPlanId
					.replace('USD', 'EUR')
					.replace(/taxfree/, userDoc.taxamo.taxRate + 'VAT');

			logger.log( userDoc );
			return resolve( userDoc );
		}

		// if taxRate was null, we need to
		fetch('https://api.taxamo.com/api/v1/tax/calculate', {
			method: 'post',
			headers: {
				'Content-Type': 'application/json',
				'Accept': 'application/json',
				'Private-Token': hoodie.config.get('taxamoKey'),
			},
			body: JSON.stringify({
				'currency_code': 'EUR',
				'amount': 0,
				'force_country_code': userDoc.stripe.country,
			}),
		})
		.then(utils.checkStatus)
		.then(utils.parseJson)
		.then(function(body) {
			userDoc.taxamo.taxRate =
				body.transaction['transaction_lines'][0]['tax_rate'];
			userDoc.localPlanId =
				userDoc.localPlanId
					.replace('USD', 'EUR')
					.replace(/taxfree/, userDoc.taxamo.taxRate + 'VAT');

			logger.log( userDoc, body );
			return resolve( userDoc );
		})
		.catch(function(error) {
			return reject( error );
		});
	});
}

function stripePlanCreate( stripe, hoodie, userDoc, request, logger ) {
	return new Promise(function(resolve, reject) {
		var requestData = request.payload.args[0];

		var basePlan = global.allStripePlans[requestData.plan];
		if ( !basePlan ) {
			return reject( Boom.forbidden('Base plan doesn\'t exist.') );
		}

		var newPlan = {
			'id': userDoc.localPlanId,
			'name': userDoc.localPlanId,
			'interval': basePlan.interval,
			'amount': Math.floor(
				( basePlan.amount ) / ( userDoc.taxamo.taxRate / 100 + 1 ) ),
			'currency': 'EUR',
		};

		if ( basePlan['interval_count'] ) {
			newPlan['interval_count'] = basePlan['interval_count'];
		}
		if ( basePlan['trial_period_days'] ) {
			newPlan['trial_period_days'] = basePlan['trial_period_days'];
		}
		if ( basePlan.metadata ) {
			newPlan.metadata = basePlan.metadata;
		}
		if ( basePlan['statement_descriptor'] ) {
			newPlan['statement_descriptor'] = basePlan['statement_descriptor'];
		}

		stripe.plans.create(newPlan, function(error, plan) {
			if ( error ) {
				return reject( error );
			}

			global.allStripePlans[plan.id] = plan;

			logger.log( userDoc, plan );
			return resolve( userDoc );
		});
	});
}

function stripeCustomerCreate( stripe, hoodie, userDoc, request, logger ) {
	return new Promise(function(resolve, reject) {

		var customer = userDoc.stripe;
		var taxamo = userDoc.taxamo;
		var requestData = request.payload.args[0];

		if ( customer.customerId ) {
			return reject( Boom.forbidden(
				'User is already a customer. Cannot create customer.') );
		}

		stripe.customers.create({
			'description': 'Customer for ' + userDoc.name.split('/')[1],
			'source': requestData.source,
			'plan': userDoc.localPlanId,
			'tax_percent': taxamo.taxRate,
			'metadata': {
				'hoodieId': userDoc.id,
				'taxamo_transaction_key': taxamo.id,
			},

		}, function( error, body ) {
			if ( error ) {
				return reject( error );
			}

			var subscription = body.subscriptions.data[0];

			delete userDoc.localPlanId;
			customer.customerId = body.id;
			customer.subscriptionId = subscription.id;
			customer.plan = subscription.plan.id;
			userDoc.roles.push( 'stripe:plan:' + subscription.plan.id );

			logger.log( userDoc, body );
			return resolve( userDoc );
		});

	});
}

// Update the subscription info on the userDoc
function stripeSubscriptionUpdate( stripe, hoodie, userDoc, request, logger ) {
	return new Promise(function(resolve, reject) {
		var customer = userDoc.stripe;

		if ( !customer || !customer.customerId ) {
			return reject( Boom.forbidden(
				'Customer doesn\'t exist. Cannot update subscription.') );
		}
		if ( !customer || !customer.subscriptionId ) {
			return reject( Boom.forbidden(
				'Subscription doesn\'t exist. Cannot update subscription.') );
		}

		stripe.customers.updateSubscription(
			customer.customerId,
			customer.subscriptionId,
			{
				plan: userDoc.localPlanId,
			},
			function( error, body ) {
				if ( error ) {
					return reject( err );
				}

				delete userDoc.localPlanId;
				customer.plan = body.plan.id;
				userDoc.roles.forEach(function( role, i ) {
					if ( role.indexOf('stripe:plan:') === 0 ) {
						userDoc.roles[i] = 'stripe:plan:' + customer.plan;
					}
				});

				logger.log( userDoc, body )
				return resolve( userDoc, body );
			}
		);
	});
}

function updateAccount( stripe, hoodie, userDoc ) {
	return new Promise(function(resolve, reject) {

		hoodie.account.update('user', userDoc.id, userDoc, function(error) {
			if ( error ) {
				return reject( error );
			}

			return resolve( userDoc );
		});

	});
}
<commit_msg>debug appback 8<commit_after>var Promise = require('bluebird');
var Stripe = require('stripe');
var fetch = require('node-fetch');
var utils = require('../../lib/utils');
var Boom = require('boom');

module.exports = function handleCustomerRequest( hoodie, request, reply ) {
	var logger = request.raw.res.chrome;

	if ( request.method !== 'post' ) {
		return reply( Boom.methodNotAllowed() );
	}

	var stripeKey = hoodie.config.get('stripeKey');
	if ( !stripeKey ) {
		return reply( Boom.expectationFailed( 'Stripe key not configured') );
	}
	var stripe = Stripe(stripeKey);
	if ( !hoodie.config.get('taxamoKey') ) {
		return reply( Boom.expectationFailed( 'Taxamo key not configured') );
	}
	if ( !request.payload.args || !request.payload.args[0] ||
			!request.payload.args[0].plan ) {
		return reply( Boom.badRequest( 'plan property is mandatory') );
	}

	var promises = [ requestSession( request ) ];

	if ( !global.allStripePlans ) {
		promises.push( utils.fetchAllStripePlans( stripe ) );
	}

	Promise.all(promises)
		.then(function() {console.log('A');
			return arguments[0];
		})
		.then(function(userName) {console.log('B');
			return getUserDoc(
				stripe, hoodie, userName, request, logger );
		})
		// verify stripeToken and extract country code
		.then(function(nextDoc) {console.log('C');
			if ( request.payload.method === 'customers.create' &&
					( request.payload.args[0] || {} ).source ) {
				return stripeRetrieveToken(
					stripe, hoodie, nextDoc, request, logger );
			}

			return nextDoc;
		})
		// create palceholder taxamo transaction
		.then(function(nextDoc) {console.log('D');
			if ( request.payload.method === 'customers.create' &&
					( request.payload.args[0] || {} ).source ) {
				return taxamoTransactionCreate(
					stripe, hoodie, nextDoc, request, logger );
			}

			return nextDoc;
		})
		// build plan Id that matches VAT amount
		.then(function(nextDoc) {console.log('E');
			return buildLocalPlanId(
				stripe, hoodie, nextDoc, request, logger );
		})
		.then(function(nextDoc) {console.log('F');
			if ( !(nextDoc.localPlanId in global.allStripePlans ) ) {
				return stripePlanCreate(
					stripe, hoodie, nextDoc, request, logger );
			}

			return nextDoc;
		})
		// create stripe customer with reference to placeholder transaction
		.then(function(nextDoc) {console.log('G');
			if ( request.payload.method === 'customers.create' ) {
				return stripeCustomerCreate(
					stripe, hoodie, nextDoc, request, logger );
			}

			return nextDoc;
		})
		// create or update subscription
		.then(function( nextDoc ) {console.log('H');
			if ( request.payload.method === 'customers.update' ) {
				return stripeSubscriptionUpdate(
					stripe, hoodie, nextDoc, request, logger );
			}

			return nextDoc;
		})
		.then(function( nextDoc ) {console.log('I');
			return updateAccount( stripe, hoodie, nextDoc, request, logger );
		})
		.then(function(nextDoc) {console.log('J');
			return reply( null, { plan: nextDoc.stripe.plan });
		})
			.catch(function( error ) {console.log('K');
			console.log(error, error.stack);
			logger.error(error, error.stack);
			return reply( error );
		});
}

function requestSession( request ) {
	var sessionUri =
		'https://' +
		request.info.host +
		'/_api/_session';
console.log('request.info.remoteAddress', request.info.remoteAddress);
console.log('request.raw.req.connection.remoteAddress', request.raw.req.connection.remoteAddress);
	return fetch(sessionUri, {
			method: 'get',
			headers: {
				'authorization': request.headers.authorization,
				'accept': 'application/json',
			},
			cookie: request.headers.cookie,
			// session shouldn't take longer than that
			timeout: 1000,
		})
		.then(utils.checkStatus)
		.then(utils.parseJson)
		.then(checkSession);
}

function checkSession( response ) {
	if ( !response.userCtx || !response.userCtx.name ) {
		throw Boom.unauthorized('Anonymous users can\'t subscribe');
	}
	else {
		return response.userCtx.name.replace(/^user\//, '');
	}
};

function getUserDoc( stripe, hoodie, userName, request, logger ) {
	return new Promise(function(resolve, reject) {

		hoodie.account.find('user', userName, function( error, userDoc ) {
			if ( error ) {
				return reject( error );
			}

			if ( !userDoc.stripe ) {
				userDoc.stripe = {};
			}

			logger.log( userDoc );
			return resolve( userDoc );
		});

	});
}

function stripeRetrieveToken( stripe, hoodie, userDoc, request, logger ) {
	return new Promise(function(resolve, reject) {

		var requestData = request.payload.args[0];

		stripe.tokens.retrieve( requestData.source, function( error, token ) {
			if ( error ) {
				return reject( error );
			}

			userDoc.stripe.tokenId = token.id;
			userDoc.stripe.country = token.card.country;
console.log('client_ip', token.client_ip);
			logger.log( userDoc );
			return resolve( userDoc );
		});

	});
}

function taxamoTransactionCreate( stripe, hoodie, userDoc, request, logger ) {
	var customer = userDoc.stripe;
	var requestData = request.payload.args[0];

	var payload = {
		transaction: {
			'transaction_lines': [
				{
					'custom_id': 'dontRemoveThisProp',
					'amount': 0,
				},
			],
			'currency_code': requestData.currencyCode || 'USD',
			'description': 'placeholder transaction',
			'status': 'C',
			'buyer_credit_card_prefix': requestData.cardPrefix,
			'force_country_code': customer.country,
		},
		'private_token': hoodie.config.get('taxamoKey'),
	};

	if (request.info.remoteAddress !== '127.0.0.1') {
		payload['buyer_ip'] = request.info.remoteAddress;
	}
	if (requestData.taxNumber) {
		payload['buyer_tax_number'] = requestData.taxNumber;
	}

	return fetch('https://api.taxamo.com/api/v1/transactions', {
		method: 'post',
		headers: {
			'Content-Type': 'application/json',
			'Accept': 'application/json',
			'Private-Token': hoodie.config.get('taxamoKey'),
		},
		body: JSON.stringify(payload),
	})
	.then(utils.checkStatus)
	.then(utils.parseJson)
	.then(function(body) {
		userDoc.taxamo = {
			id: body.transaction.key,
			taxNumber: body.transaction['buyer_tax_number'],
			taxRate: body.transaction['transaction_lines'][0]['tax_rate'],
			taxRegion: body.transaction['tax_region'],
			taxCountryCode: body.transaction['tax_region'],
			taxDeducted: body.transaction['tax_deducted'],
		};

		logger.log( userDoc, body );
		return userDoc;
	});
}

function buildLocalPlanId( stripe, hoodie, userDoc, request, logger ) {
	return new Promise(function(resolve, reject) {
		var requestData = request.payload.args[0];

		if ( requestData.plan.split('_').indexOf('taxfree') === -1 ) {
			return reject( Boom.forbidden('Base plan isn\'t taxfree.') );
		}

		var basePlan = global.allStripePlans[requestData.plan];
		if ( !basePlan ) {
			return reject( Boom.forbidden('Base plan doesn\'t exist.') );
		}

		if ( !userDoc.stripe || !userDoc.taxamo ) {
			return reject( Boom.forbidden(
				'Customer doesn\'t exist. Cannot update subscription.') );
		}

		userDoc.localPlanId = requestData.plan;
		if ( userDoc.taxamo.taxRegion !== 'EU' ) {
			logger.log( userDoc );
			return resolve( userDoc );
		}

		// User is in the EU. Let's find a plan that matches local VAT
		if ( userDoc.taxamo.taxRate !== 0 ) {
			userDoc.localPlanId =
				userDoc.localPlanId
					.replace('USD', 'EUR')
					.replace(/taxfree/, userDoc.taxamo.taxRate + 'VAT');

			logger.log( userDoc );
			return resolve( userDoc );
		}

		// if taxRate was null, we need to
		fetch('https://api.taxamo.com/api/v1/tax/calculate', {
			method: 'post',
			headers: {
				'Content-Type': 'application/json',
				'Accept': 'application/json',
				'Private-Token': hoodie.config.get('taxamoKey'),
			},
			body: JSON.stringify({
				'currency_code': 'EUR',
				'amount': 0,
				'force_country_code': userDoc.stripe.country,
			}),
		})
		.then(utils.checkStatus)
		.then(utils.parseJson)
		.then(function(body) {
			userDoc.taxamo.taxRate =
				body.transaction['transaction_lines'][0]['tax_rate'];
			userDoc.localPlanId =
				userDoc.localPlanId
					.replace('USD', 'EUR')
					.replace(/taxfree/, userDoc.taxamo.taxRate + 'VAT');

			logger.log( userDoc, body );
			return resolve( userDoc );
		})
		.catch(function(error) {
			return reject( error );
		});
	});
}

function stripePlanCreate( stripe, hoodie, userDoc, request, logger ) {
	return new Promise(function(resolve, reject) {
		var requestData = request.payload.args[0];

		var basePlan = global.allStripePlans[requestData.plan];
		if ( !basePlan ) {
			return reject( Boom.forbidden('Base plan doesn\'t exist.') );
		}

		var newPlan = {
			'id': userDoc.localPlanId,
			'name': userDoc.localPlanId,
			'interval': basePlan.interval,
			'amount': Math.floor(
				( basePlan.amount ) / ( userDoc.taxamo.taxRate / 100 + 1 ) ),
			'currency': 'EUR',
		};

		if ( basePlan['interval_count'] ) {
			newPlan['interval_count'] = basePlan['interval_count'];
		}
		if ( basePlan['trial_period_days'] ) {
			newPlan['trial_period_days'] = basePlan['trial_period_days'];
		}
		if ( basePlan.metadata ) {
			newPlan.metadata = basePlan.metadata;
		}
		if ( basePlan['statement_descriptor'] ) {
			newPlan['statement_descriptor'] = basePlan['statement_descriptor'];
		}

		stripe.plans.create(newPlan, function(error, plan) {
			if ( error ) {
				return reject( error );
			}

			global.allStripePlans[plan.id] = plan;

			logger.log( userDoc, plan );
			return resolve( userDoc );
		});
	});
}

function stripeCustomerCreate( stripe, hoodie, userDoc, request, logger ) {
	return new Promise(function(resolve, reject) {

		var customer = userDoc.stripe;
		var taxamo = userDoc.taxamo;
		var requestData = request.payload.args[0];

		if ( customer.customerId ) {
			return reject( Boom.forbidden(
				'User is already a customer. Cannot create customer.') );
		}

		stripe.customers.create({
			'description': 'Customer for ' + userDoc.name.split('/')[1],
			'source': requestData.source,
			'plan': userDoc.localPlanId,
			'tax_percent': taxamo.taxRate,
			'metadata': {
				'hoodieId': userDoc.id,
				'taxamo_transaction_key': taxamo.id,
			},

		}, function( error, body ) {
			if ( error ) {
				return reject( error );
			}

			var subscription = body.subscriptions.data[0];

			delete userDoc.localPlanId;
			customer.customerId = body.id;
			customer.subscriptionId = subscription.id;
			customer.plan = subscription.plan.id;
			userDoc.roles.push( 'stripe:plan:' + subscription.plan.id );

			logger.log( userDoc, body );
			return resolve( userDoc );
		});

	});
}

// Update the subscription info on the userDoc
function stripeSubscriptionUpdate( stripe, hoodie, userDoc, request, logger ) {
	return new Promise(function(resolve, reject) {
		var customer = userDoc.stripe;

		if ( !customer || !customer.customerId ) {
			return reject( Boom.forbidden(
				'Customer doesn\'t exist. Cannot update subscription.') );
		}
		if ( !customer || !customer.subscriptionId ) {
			return reject( Boom.forbidden(
				'Subscription doesn\'t exist. Cannot update subscription.') );
		}

		stripe.customers.updateSubscription(
			customer.customerId,
			customer.subscriptionId,
			{
				plan: userDoc.localPlanId,
			},
			function( error, body ) {
				if ( error ) {
					return reject( err );
				}

				delete userDoc.localPlanId;
				customer.plan = body.plan.id;
				userDoc.roles.forEach(function( role, i ) {
					if ( role.indexOf('stripe:plan:') === 0 ) {
						userDoc.roles[i] = 'stripe:plan:' + customer.plan;
					}
				});

				logger.log( userDoc, body )
				return resolve( userDoc, body );
			}
		);
	});
}

function updateAccount( stripe, hoodie, userDoc ) {
	return new Promise(function(resolve, reject) {

		hoodie.account.update('user', userDoc.id, userDoc, function(error) {
			if ( error ) {
				return reject( error );
			}

			return resolve( userDoc );
		});

	});
}
<|endoftext|>


<commit_before>
$(document).ready(function () {
    $('#datetimepicker').datetimepicker();
    $("#action_officers_pq_action_officer_id").select2({ width: '250px' });

    $('#allocation_response_response_accept').click(function (){
        $('#reason-textarea').addClass('hide');
    })
    $('#allocation_response_response_reject').click(function (){
        $('#reason-textarea').removeClass('hide');
    })
});

<commit_msg>fixed again the element in javascript<commit_after>
$(document).ready(function () {
    $('#datetimepicker').datetimepicker();
    $("#action_officers_pq_action_officer_id").select2({ width: '250px' });

    $('#allocation_response_response_action_accept').click(function (){
        $('#reason-textarea').addClass('hide');
    })
    $('#allocation_response_response_action_reject').click(function (){
        $('#reason-textarea').removeClass('hide');
    })
});

<|endoftext|>


<commit_before>// import components
import Instance from '../../components/Instance';
import Button from '../../components/Button';
import TextBox from '../../components/TextBox';
import CheckBox from '../../components/CheckBox';
import Dialog from '../../components/Dialog';

// import template engine
import DOMTransform from '../../tools/DOMTransform';
import DON from '../../tools/DON';

// import templates
import button from '../../templates/button.js';
import textbox from '../../templates/textbox.js';
import checkbox from '../../templates/checkbox.js';
import dialog from '../../templates/dialog.js';

// create application components
class TodoApp extends Instance {
    constructor(element) {
        super(element);
        element.appendChild(DON.toDOM(domTransform.apply({ element : 'todoapp' })));
        element.querySelector('form').addEventListener('submit', this.onSubmit.bind(this));
        this.list = element.querySelector('ul');
        this.attach(Button, TextBox, CheckBox);
    }
    attach(...components) {
        components.forEach(Component => Component.attachTo(this.element));
    }
    onSubmit(event) {
        const textbox = this.find(TextBox);
        const text = textbox.value.trim();
        if(text) {
            const todoitem = DON.toDOM(domTransform.apply({ element : 'todoitem', text }));
            TodoItem.getInstance(this.list.appendChild(todoitem));
            textbox.value = '';
        } else textbox.focus();
        event.preventDefault();
    }
}
class TodoItem extends Instance {
    constructor(element) {
        super(element);
        this.dialog = Dialog.getInstance(document.getElementById('removeitemconfirm'));
        this.find(Button).on('click', this.onButtonClick, this);
        this.onDialogSubmit = this.onDialogSubmit.bind(this);
    }
    get expanded() {
        return this.element.getAttribute('aria-expanded') || 'false';
    }
    set expanded(expanded) {
        if(expanded !== this.expanded) {
            this.element.setAttribute('aria-expanded', expanded);
            if(expanded === 'true') {
                this.dialog.element.addEventListener('submit', this.onDialogSubmit);
                this.dialog.hidden = 'false';
            } else {
                this.dialog.element.removeEventListener('submit', this.onDialogSubmit);
                this.dialog.hidden = 'true';
            }
        }
    }
    onDialogSubmit(event) {
        this.expanded = 'false';
        this.remove();
        event.preventDefault();
    }
    onButtonClick() {
        if(this.find(CheckBox).checked === 'true') this.remove();
        else {
            this.dialog.trigger = this;
            this.expanded = 'true';
        }
    }
    remove() {
        this.element.parentElement.removeChild(this.element);
    }
    focus() {
        this.find(CheckBox).focus();
    }
}

// create template engine instance
const domTransform = new DOMTransform;

// connect lib templates
[button, textbox, checkbox, dialog].forEach(template => template(domTransform));

// write application templates
domTransform.element('todoapp', function() {
    return this.apply({
        element : 'main',
        content : [
            { element : 'h2', content : 'TODO list' },
            { element : 'ul' },
            {
                element : 'form',
                content : [
                    {
                        element : 'textbox',
                        attributes : { placeholder : 'What are you going to do?' }
                    },
                    {
                        element : 'button',
                        attributes : { type : 'submit' },
                        content : 'Remember'
                    }
                ]
            },
            {
                element : 'confirmdialog',
                attributes : {
                    text : 'Item is not marked as "done". Are you sure?',
                    id : 'removeitemconfirm',
                    submitLabel : 'Remove item'
                }
            }
        ]
    });
});
domTransform.element('confirmdialog', function({ attributes, content }) {
    return this.apply({
        element : 'dialog',
        attributes : { modal : 'true', id : attributes.id },
        content : content || {
            element : 'form',
            content : [
                { element : 'p', content : attributes.text },
                {
                    element : 'button',
                    attributes : { type: 'submit', mix : 'accent' },
                    content : attributes.submitLabel || 'Ok'
                },
                {
                    element : 'button',
                    attributes : { type: 'close' },
                    content : attributes.dismissLabel || 'Cancel'
                }
            ]
        }
    });
});
domTransform.element('todoitem', function({ text }) {
    return {
        element : 'li',
        attributes : { 'data-instance' : 'TodoItem', 'aria-haspopup' : 'true' },
        content : this.apply([
            {
                element : 'checkbox',
                attributes : { title : 'Mark as "done"' }
            },
            {
                element : 'span',
                attributes : { 'class' : 'text' },
                content : text
            },
            {
                element : 'button',
                attributes : { view : 'removebutton', type : 'remove', title : 'Remove' },
                content : '×'
            }
        ])
    };
});

// init app
new TodoApp(document.body);
<commit_msg>TodoApp: save and load app state<commit_after>// import components
import Instance from '../../components/Instance';
import Button from '../../components/Button';
import TextBox from '../../components/TextBox';
import CheckBox from '../../components/CheckBox';
import Dialog from '../../components/Dialog';

// import template engine
import DOMTransform from '../../tools/DOMTransform';
import DON from '../../tools/DON';

// import templates
import button from '../../templates/button.js';
import textbox from '../../templates/textbox.js';
import checkbox from '../../templates/checkbox.js';
import dialog from '../../templates/dialog.js';

// create application components
class TodoApp extends Instance {
    constructor(element) {
        super(element);
        element.dataset.instance = this.constructor.name;
        element.appendChild(DON.toDOM(domTransform.apply({
            element : 'todoapp',
            items : this.load()
        })));
        element.querySelector('form').addEventListener('submit', this.onSubmit.bind(this));
        this.list = element.querySelector('ul');
        this.findAll(TodoItem);
        this.attach(Button, TextBox, CheckBox);
    }
    attach(...components) {
        components.forEach(Component => Component.attachTo(this.element));
    }
    onSubmit(event) {
        const textbox = this.find(TextBox);
        const text = textbox.value.trim();
        event.preventDefault();
        if(text) {
            const element = DON.toDOM(domTransform.apply({ element : 'todoitem', text }));
            const todoitem = TodoItem.getInstance(this.list.appendChild(element));
            textbox.value = '';
            this.save();
        } else textbox.focus();
    }
    save() {
        localStorage.setItem('TodoApp', JSON.stringify({
            items : this.findAll(TodoItem).map(({ text, done }) => ({ text, done }))
        }));
    }
    load() {
        const storage = localStorage.getItem('TodoApp');
        return storage? JSON.parse(storage).items : [];
    }
}
class TodoItem extends Instance {
    constructor(element) {
        super(element);
        this.app = this.closest(TodoApp);
        this.dialog = Dialog.getInstance(document.getElementById('removeitemconfirm'));
        this.checkbox = this.find(CheckBox);
        this.checkbox.on('change', () => this.app.save());
        this.find(Button).on('click', this.onButtonClick, this);
        this.onDialogSubmit = this.onDialogSubmit.bind(this);
    }
    get expanded() {
        return this.element.getAttribute('aria-expanded') || 'false';
    }
    set expanded(expanded) {
        if(expanded !== this.expanded) {
            this.element.setAttribute('aria-expanded', expanded);
            if(expanded === 'true') {
                this.dialog.element.addEventListener('submit', this.onDialogSubmit);
                this.dialog.hidden = 'false';
            } else {
                this.dialog.element.removeEventListener('submit', this.onDialogSubmit);
                this.dialog.hidden = 'true';
            }
        }
    }
    get text() {
        return this.element.querySelector('.text').textContent;
    }
    get done() {
        return this.checkbox.checked;
    }
    onDialogSubmit(event) {
        this.expanded = 'false';
        this.remove();
        event.preventDefault();
    }
    onButtonClick() {
        if(this.done === 'true') this.remove();
        else {
            this.dialog.trigger = this;
            this.expanded = 'true';
        }
    }
    remove() {
        this.element.parentElement.removeChild(this.element);
        this.app.save();
    }
    focus() {
        this.checkbox.focus();
    }
}

// create template engine instance
const domTransform = new DOMTransform;

// connect lib templates
[button, textbox, checkbox, dialog].forEach(template => template(domTransform));

// write application templates
domTransform.element('todoapp', function({ items }) {
    return this.apply({
        element : 'main',
        content : [
            { element : 'h2', content : 'TODO list' },
            {
                element : 'ul',
                content : items.map(({ text, done }) => ({ element : 'todoitem', text, done }))
            },
            {
                element : 'form',
                content : [
                    {
                        element : 'textbox',
                        attributes : { placeholder : 'What are you going to do?' }
                    },
                    {
                        element : 'button',
                        attributes : { type : 'submit' },
                        content : 'Remember'
                    }
                ]
            },
            {
                element : 'confirmdialog',
                attributes : {
                    text : 'Item is not marked as "done". Are you sure?',
                    id : 'removeitemconfirm',
                    submitLabel : 'Remove item'
                }
            }
        ]
    });
});
domTransform.element('confirmdialog', function({ attributes, content }) {
    return this.apply({
        element : 'dialog',
        attributes : { modal : 'true', id : attributes.id },
        content : content || {
            element : 'form',
            content : [
                { element : 'p', content : attributes.text },
                {
                    element : 'button',
                    attributes : { type: 'submit', mix : 'accent' },
                    content : attributes.submitLabel || 'Ok'
                },
                {
                    element : 'button',
                    attributes : { type: 'close' },
                    content : attributes.dismissLabel || 'Cancel'
                }
            ]
        }
    });
});
domTransform.element('todoitem', function({ text, done }) {
    return {
        element : 'li',
        attributes : { 'data-instance' : 'TodoItem', 'aria-haspopup' : 'true' },
        content : this.apply([
            {
                element : 'checkbox',
                attributes : { checked : done, title : 'Mark as "done"' }
            },
            {
                element : 'span',
                attributes : { 'class' : 'text' },
                content : text
            },
            {
                element : 'button',
                attributes : { view : 'removebutton', type : 'remove', title : 'Remove' },
                content : '×'
            }
        ])
    };
});

// init app
new TodoApp(document.body);
<|endoftext|>


<commit_before>/////////////
// Routing //
/////////////

'use strict';

var validators = require('./controllers/validators');
var validator  = require('validator');

module.exports = function (router, db, config) {
    var controllers = require('./controllers')(db, config);
    var auth       = require('./lib/auth')(db, config);

    // Main auth
    // Checks if there is a token every request
    router.use(auth.checkToken);

    ////////////
    // Events //
    ////////////

    router.route('/events').
        // Gets all events
        get(
            auth.noAuth,
            controllers.events.getAll
        )
        // Creates an event
        .post(
            auth.isFundationAccount,
            validators.createEvent,
            controllers.events.create
        )
        // Updates an event
        .put(
            auth.isInEvent('admin'),
            validators.editEvent,
            controllers.events.edit
        );

    router.route('/events/:eventId')
        // Deletes an event
        .delete(
            auth.isInEvent('admin'),
            controllers.events.remove
        )
        // Gets an event's details
        .get(
            auth.checkAuth,
            auth.isInEvent('admin'),
            controllers.events.getOne
        );

    /////////////////////
    // Event's tickets //
    /////////////////////

    router.route('/events/:eventId/tickets')
        // Gets all tickets from an event (stats)
        .get(
            auth.isInEvent('admin'),
            controllers.tickets.getAllFromEvent
        );

    ////////////////////
    // Event's prices //
    ////////////////////

    router.route('/events/:eventId/prices')
        // Gets all prices from an event
        // Can be used by no-auth clients to buy a ticket (and they need the price)
        .put(
            auth.noAuth,
            validators.createPrice,
            controllers.events.createPrice
        );

    router.route('/events/:eventId/prices/:priceId')
        // Updates an event price
        .post(
            auth.isInEvent("admin"),
            validators.editPrice,
            controllers.events.editPrice
        );

    //////////////////////
    // Event's accounts //
    //////////////////////

    router.route('/etu/search/')
        // Searches among users list
        .get(
            auth.isInEvent('admin'),
            controllers.etu.searchUsers
        );

    router.route('/events/:eventId/accounts')
        // Create an account
        .post(
            auth.isFundationAccount,
            validators.createAccount,
            controllers.accounts.create
        )
        // Get all the accounts
        .get(
            auth.isFundationAccount,
            controllers.accounts.getAll
        );
    router.route('/accounts/:accountId')
        // Deletes an account
        .delete(
            auth.isFundationAccount,
            controllers.accounts.remove
        );
    router.route('/accounts/:userId')
        // Gets all accounts with a given user id
        .get(
            auth.noAuth,
            controllers.accounts.getAllFromUserId
        );

    /////////////
    // Tickets //
    /////////////

    router.route('/tickets')
        // Gets all tickets from tickets list
        .get(
            auth.noAuth,
            controllers.tickets.getAll
        );

    //////////////
    // Auth etu //
    //////////////

    router.route('/etu/login')
        .post(
            auth.noAuth,
            validators.etuLogin,
            controllers.etu.login,
            auth.addAuth
        );

    ////////////////////
    // School domains //
    ////////////////////

    router.route('/domains/')
        // Gets all domains
        .get(
            auth.isSuperAdmin,
            controllers.domains.getAll
        )
        // Creates a domain
        .post(
            auth.isSuperAdmin,
            validators.createDomain,
            controllers.domains.create
        );
    router.route('/domains/:domainId')
        // Deletes a domain
        .delete(
            auth.isSuperAdmin,
            controllers.domains.remove
        );

    // Bank Price
    router.route('/bankPrice/')
        .get(
            auth.isSuperAdmin,
            controllers.bankPrice.get
        )
        .post(
            auth.isSuperAdmin,
            validators.editBankprice,
            controllers.bankPrice.edit
        );

    //////////
    // User //
    //////////

    router.route('/print/')
        // Ticket printer
        .get(
            auth.checkAuth,
            controllers.tickets.print
        );

    router.route('/forgot/:mail')
        // Forgot tickets
        .get(
            auth.checkAuth,
            controllers.tickets.forgot
        );

    router.route('/purchases/')
        // Buckutt History - Purchases
        .get(
            auth.checkAuth,
            controllers.buckuttHistory.getPurchasesHistory
        );
    router.route('/reloads/')
        // Buckutt History - Reloads
        .get(
            auth.checkAuth,
            controllers.buckuttHistory.getReloadsHistory
        );

    /* Params filters */
    var justIds = ['eventId', 'priceId', 'domainId', 'accountId', 'userId'];
    justIds.forEach(function (idName) {
        router.param(idName, function (req, res, next, id) {
            if (Number.isPositiveNumeric(id)) {
                req.body[idName] = id;
                next();
            } else {
                Error.emit(res, 400, '400 - Bad Request');
            }
        });
    });

    router.param('token', function (req, res, next, token) {
        var reg = /^.+$/;
        if (reg.test(token)) {
            req.params.token = token;
            next();
        } else {
            Error.emit(res, 400, '400 - Bad Request');
        }
    });

    router.param('mail', function (req, res, next, mail) {
        if (validator.isEmail(mail)) {
            req.params.mail = mail;
            next();
        } else {
            Error.emit(res, 400, '400 - Bad Request');
        }
    });
};
<commit_msg>Useless checkAuth<commit_after>/////////////
// Routing //
/////////////

'use strict';

var validators = require('./controllers/validators');
var validator  = require('validator');

module.exports = function (router, db, config) {
    var controllers = require('./controllers')(db, config);
    var auth       = require('./lib/auth')(db, config);

    // Main auth
    // Checks if there is a token every request
    router.use(auth.checkToken);

    ////////////
    // Events //
    ////////////

    router.route('/events').
        // Gets all events
        get(
            auth.noAuth,
            controllers.events.getAll
        )
        // Creates an event
        .post(
            auth.isFundationAccount,
            validators.createEvent,
            controllers.events.create
        )
        // Updates an event
        .put(
            auth.isInEvent('admin'),
            validators.editEvent,
            controllers.events.edit
        );

    router.route('/events/:eventId')
        // Deletes an event
        .delete(
            auth.isInEvent('admin'),
            controllers.events.remove
        )
        // Gets an event's details
        .get(
            auth.isInEvent('admin'),
            controllers.events.getOne
        );

    /////////////////////
    // Event's tickets //
    /////////////////////

    router.route('/events/:eventId/tickets')
        // Gets all tickets from an event (stats)
        .get(
            auth.isInEvent('admin'),
            controllers.tickets.getAllFromEvent
        );

    ////////////////////
    // Event's prices //
    ////////////////////

    router.route('/events/:eventId/prices')
        // Gets all prices from an event
        // Can be used by no-auth clients to buy a ticket (and they need the price)
        .put(
            auth.noAuth,
            validators.createPrice,
            controllers.events.createPrice
        );

    router.route('/events/:eventId/prices/:priceId')
        // Updates an event price
        .post(
            auth.isInEvent("admin"),
            validators.editPrice,
            controllers.events.editPrice
        );

    //////////////////////
    // Event's accounts //
    //////////////////////

    router.route('/etu/search/')
        // Searches among users list
        .get(
            auth.isInEvent('admin'),
            controllers.etu.searchUsers
        );

    router.route('/events/:eventId/accounts')
        // Create an account
        .post(
            auth.isFundationAccount,
            validators.createAccount,
            controllers.accounts.create
        )
        // Get all the accounts
        .get(
            auth.isFundationAccount,
            controllers.accounts.getAll
        );
    router.route('/accounts/:accountId')
        // Deletes an account
        .delete(
            auth.isFundationAccount,
            controllers.accounts.remove
        );
    router.route('/accounts/:userId')
        // Gets all accounts with a given user id
        .get(
            auth.noAuth,
            controllers.accounts.getAllFromUserId
        );

    /////////////
    // Tickets //
    /////////////

    router.route('/tickets')
        // Gets all tickets from tickets list
        .get(
            auth.noAuth,
            controllers.tickets.getAll
        );

    ////////////////////
    // School domains //
    ////////////////////

    router.route('/domains/')
        // Gets all domains
        .get(
            auth.isSuperAdmin,
            controllers.domains.getAll
        )
        // Creates a domain
        .post(
            auth.isSuperAdmin,
            validators.createDomain,
            controllers.domains.create
        );
    router.route('/domains/:domainId')
        // Deletes a domain
        .delete(
            auth.isSuperAdmin,
            controllers.domains.remove
        );

    // Bank Price
    router.route('/bankPrice/')
        .get(
            auth.isSuperAdmin,
            controllers.bankPrice.get
        )
        .post(
            auth.isSuperAdmin,
            validators.editBankprice,
            controllers.bankPrice.edit
        );

    //////////
    // User //
    //////////

    router.route('/etu/login')
        // Auth etu
        .post(
            auth.noAuth,
            validators.etuLogin,
            controllers.etu.login,
            auth.addAuth
        );

    router.route('/etu/block')
        .put(
            auth.isAuth,
            controllers.etu.block
        );

    router.route('/print/')
        // Ticket printer
        .get(
            auth.isAuth,
            controllers.tickets.print
        );

    router.route('/forgot/:mail')
        // Forgot tickets
        .get(
            auth.isAuth,
            controllers.tickets.forgot
        );

    router.route('/purchases/')
        // Buckutt History - Purchases
        .get(
            auth.isAuth,
            controllers.buckuttHistory.getPurchasesHistory
        );
    router.route('/reloads/')
        // Buckutt History - Reloads
        .get(
            auth.isAuth,
            controllers.buckuttHistory.getReloadsHistory
        );

    /* Params filters */
    var justIds = ['eventId', 'priceId', 'domainId', 'accountId', 'userId'];
    justIds.forEach(function (idName) {
        router.param(idName, function (req, res, next, id) {
            if (Number.isPositiveNumeric(id)) {
                req.body[idName] = id;
                next();
            } else {
                Error.emit(res, 400, '400 - Bad Request');
            }
        });
    });

    router.param('token', function (req, res, next, token) {
        var reg = /^.+$/;
        if (reg.test(token)) {
            req.params.token = token;
            next();
        } else {
            Error.emit(res, 400, '400 - Bad Request');
        }
    });

    router.param('mail', function (req, res, next, mail) {
        if (validator.isEmail(mail)) {
            req.params.mail = mail;
            next();
        } else {
            Error.emit(res, 400, '400 - Bad Request');
        }
    });
};
<|endoftext|>


<commit_before>'use strict';

app.service('exportService', ['constantService', function(constants){

                var seconds = new Date().getTime() / 1000; //for timing the export

                var dataExport = {};

                dataExport.downloadCSV = function(volume){

                    volume.get(['records', 'containers']).then(function(data){
                        createCSV(data, volume);

                    });

                };

                function createCSV(data, volume){

                    var input = data;

                    var payload = '';

                    var containers = input.containers;
                    var records = input.records;

                    var baseHeaders = [ //these are static, tied to the volume
                        'session id',
                        'session date'
                    ];

                    /*helper object and arrays*/
                    var catCounts = getCategoryCounts(containers, records);
                    var headerIncludes = makeHeaderIndex(records, constants.metric); //object of unique categories and metrics - {cat:{metric#:metricName},...}
                    var headerRef = sortHeaderIdx(makeHeaderIndex(records, constants.metric)); //sorted array version of headerIdx
                    var colCoords = makeHeaderCoords(headerRef, catCounts); //object array that represents the headers as category,metric coords in order - [{cat:metric},...]

                    var moreHeaders = makeHeadersText(headerRef, catCounts); //turn header index into column names

                    var header = baseHeaders.concat(moreHeaders).join(',');

                    /*create CSV body data*/
                    var body = createCSVBody(containers, records, colCoords, headerIncludes);

                    payload = header + '\n' + body;

                    createPayload(payload, volume);

                }

                function createCSVBody(containers, records, headerReference, headerIndex){

                  var headerCats = Object.keys(headerIndex);

                  var body = '';

                  for(var k in containers){
                    if(containers[k].top !== true && containers[k].records.length !== 0){ //exclude materials for now

                      var ssRow = [];

                      ssRow.push('"'+containers[k].id+'"');
                      ssRow.push('"'+containers[k].date+'"');
                      var recIdArr = [];
                       for (var j in containers[k].records){ //get an array of the record IDs for each container in advance

                        recIdArr.push(containers[k].records[j].id);
                       }

                      var idx = 0; //create and index for the record loop so we have more control over the loop logic
                      for(var l = 0; l < headerReference.length; l++ ){

                       var cellCat = headerReference[l].category;
                       var cellMet = headerReference[l].metric.toString();

                       for (var j = idx; idx < recIdArr.length;){

                        var recID = recIdArr[j].toString();
                        var recMetrics = Object.keys(records[recID].measures).sort().reverse();

                        if(records[recID].category !== cellCat){

                            ssRow.push('');
                            break;

                        } else {

                            var input;
                            if(records[recID].measures[cellMet] !== undefined){
                              input = records[recID].measures[cellMet];
                            } else {

                              input = '';

                            }

                            console.log(input);

                            if(input.contains('"') || input.contains(',') || input.contains('\n')){

                              input = '"'+input+'"'; //escape CSV formatting in cells
                            }

                            if(recMetrics.length > 1){

                                ssRow.push(input);
                                if(cellMet === recMetrics[recMetrics.length-1]){
                                    idx++; //only advance if we are done with all the metrics in this record
                                }

                                break;
                            }else{

                                ssRow.push(input);
                                idx++;
                                break;
                            }
                        }

                       }

                      }

                    body += ssRow.join(',') + '\n';
                  }
                }
                return body;
              }

              function createPayload(payload, volume){

                    var volTitle = volume.id + "-" + volume.name.split(' ').join('_');
                    var filename = volTitle + '.csv';
                    var uri = 'data:text/csv;charset=utf-8,' + encodeURI(payload);

                    var link = document.createElement('a');
                    link.href = uri;

                    //link.style = "visibility:hidden"; //check this on safari, throws assign on readonly error
                    link.download = filename;

                    document.body.appendChild(link);
                    link.click();
                    document.body.removeChild(link);

                    var timenow = new Date().getTime() / 1000;
                    var timeto = (timenow - seconds);

                    console.log("Export process took: " + timeto + " seconds");
                }

                /*--------------functions for creating helper objects and arrays above-----------------*/

                String.prototype.contains = function(item){ return this.indexOf(item) !== -1;};

                function makeHeaderCoords(headerIndexArr, categoryCountsObj){

                  var output = [];

                  headerIndexArr.forEach(function(item){

                    var catID = constants.category[item.category].id;

                    for(var z = 0; z < categoryCountsObj[catID]; z++){
                      if(item.metrics.length < 1){ //this is not future proof, may want to visit the data model.
                        output.push({"category": catID, "metric": ''});
                      }

                      for(var m = 0; m < item.metrics.length; m++){
                        var metID = constants.metric[item.metrics[m]].id;

                        output.push({"category": catID, "metric": metID});
                      }
                    }

                  });

                  return output;

                }

                 function makeHeadersText(headerIndexArr, categoryCountsObj){

                  var output = [];

                  headerIndexArr.forEach(function(item){

                    var catID = constants.category[item.category].id;
                    var catText = constants.category[item.category].name;

                    for(var z = 0; z < categoryCountsObj[catID]; z++){
                      if(item.metrics.length < 1){ //this is not future proof, may want to visit the data model.
                        output.push(catText);
                      }

                      for(var m = 0; m < item.metrics.length; m++){
                        var metText = constants.metric[item.metrics[m]].name;

                        if(z>0){
                          output.push(catText + ' ' + (z + 1).toString() + ' ' + metText);
                        } else {
                          output.push(catText + ' ' + metText);
                        }

                      }
                    }

                  });

                  return output;

                }

                function makeHeaderIndex(recObj, metrics){

                    var tableObj = {};

                    for(var key in recObj){

                      var cat = recObj[key].category;

                      if (!(cat in tableObj)){
                        tableObj[cat] = {};
                      }

                      for(var i in recObj[key].measures){

                        tableObj[cat][i] = metrics[i].name;

                      }

                    }
                    return tableObj;

                }

                function sortHeaderIdx(headerIdx){

                  var newIdx = [];

                  var catKeysSorted = Object.keys(headerIdx).sort().reverse();

                  for(var v = 0; v < catKeysSorted.length; v++){

                    var metKeysSorted = Object.keys(headerIdx[catKeysSorted[v]]).sort().reverse();
                    var metricsArrSorted = [];

                    for(var m = 0; m < metKeysSorted.length; m++){

                      metricsArrSorted.push(metKeysSorted[m]);

                    }
                     newIdx.push({"category": catKeysSorted[v], "metrics": metricsArrSorted});
                  }

                  return newIdx;

                }

                function getCategoryCounts(contObj, recObj){
                  /*count the categories per record and post the largest for each*/
                  var maxCatCounts = {};

                  for(var i in contObj){

                    var container = contObj[i].id;

                    if(!(container in maxCatCounts)){

                      maxCatCounts[container] = {};

                    }

                    var recArr = [];
                    for(var j in contObj[i].records){

                      recArr.push(contObj[i].records[j].id);

                    }

                    if(recArr.length > 0){
                      for(var k=0; k<recArr.length; k++){
                        var category = recObj[recArr[k]].category;
                        if( maxCatCounts[container][category] > 0){
                          maxCatCounts[container][category] += 1;
                        } else {
                          maxCatCounts[container][category] = 1;
                        }
                      }
                    }
                  }

                  /*for maxCatCounts, get all categories into an array for each category */
                  var topCats = {};

                  for (var l in maxCatCounts){
                    for(var m in maxCatCounts[l]){
                       var cat = m;
                       if(!(cat in topCats)){
                            topCats[m] = [];
                       }

                       topCats[m].push(maxCatCounts[l][m]);

                    }

                  }

                  /* get the max # of iterations for each category*/
                  for(var n in topCats){
                    topCats[n] = Math.max.apply(null, topCats[n]);
                  }

                  return topCats;

                }

               return dataExport;
            }

    ]);
<commit_msg>tweak csv escaping for double quoted strings<commit_after>'use strict';

app.service('exportService', ['constantService', function(constants){

                var seconds = new Date().getTime() / 1000; //for timing the export

                var dataExport = {};

                dataExport.downloadCSV = function(volume){

                    volume.get(['records', 'containers']).then(function(data){
                        createCSV(data, volume);

                    });

                };

                function createCSV(data, volume){

                    var input = data;

                    var payload = '';

                    var containers = input.containers;
                    var records = input.records;

                    var baseHeaders = [ //these are static, tied to the volume
                        'session id',
                        'session date'
                    ];

                    /*helper object and arrays*/
                    var catCounts = getCategoryCounts(containers, records);
                    var headerIncludes = makeHeaderIndex(records, constants.metric); //object of unique categories and metrics - {cat:{metric#:metricName},...}
                    var headerRef = sortHeaderIdx(makeHeaderIndex(records, constants.metric)); //sorted array version of headerIdx
                    var colCoords = makeHeaderCoords(headerRef, catCounts); //object array that represents the headers as category,metric coords in order - [{cat:metric},...]

                    var moreHeaders = makeHeadersText(headerRef, catCounts); //turn header index into column names

                    var header = baseHeaders.concat(moreHeaders).join(',');

                    /*create CSV body data*/
                    var body = createCSVBody(containers, records, colCoords, headerIncludes);

                    payload = header + '\n' + body;

                    createPayload(payload, volume);

                }

                function createCSVBody(containers, records, headerReference, headerIndex){

                  var headerCats = Object.keys(headerIndex);

                  var body = '';

                  for(var k in containers){
                    if(containers[k].top !== true && containers[k].records.length !== 0){ //exclude materials for now

                      var ssRow = [];

                      ssRow.push('"'+containers[k].id+'"');
                      ssRow.push('"'+containers[k].date+'"');
                      var recIdArr = [];
                       for (var j in containers[k].records){ //get an array of the record IDs for each container in advance

                        recIdArr.push(containers[k].records[j].id);
                       }

                      var idx = 0; //create and index for the record loop so we have more control over the loop logic
                      for(var l = 0; l < headerReference.length; l++ ){

                       var cellCat = headerReference[l].category;
                       var cellMet = headerReference[l].metric.toString();

                       for (var j = idx; idx < recIdArr.length;){

                        var recID = recIdArr[j].toString();
                        var recMetrics = Object.keys(records[recID].measures).sort().reverse();

                        if(records[recID].category !== cellCat){

                            ssRow.push('');
                            break;

                        } else {

                            var input;
                            if(records[recID].measures[cellMet] !== undefined){
                              input = records[recID].measures[cellMet];
                            } else {

                              input = '';

                            }

                            if(input.contains('"') || input.contains(',') || input.contains('\n')){
                              input.split('"').join('""');
                              input = '"'+input+'"'; //escape CSV formatting in cells
                            }

                            if(recMetrics.length > 1){

                                ssRow.push(input);
                                if(cellMet === recMetrics[recMetrics.length-1]){
                                    idx++; //only advance if we are done with all the metrics in this record
                                }

                                break;
                            }else{

                                ssRow.push(input);
                                idx++;
                                break;
                            }
                        }

                       }

                      }

                    body += ssRow.join(',') + '\n';
                  }
                }
                return body;
              }

              function createPayload(payload, volume){

                    var volTitle = volume.id + "-" + volume.name.split(' ').join('_');
                    var filename = volTitle + '.csv';
                    var uri = 'data:text/csv;charset=utf-8,' + encodeURI(payload);

                    var link = document.createElement('a');
                    link.href = uri;

                    //link.style = "visibility:hidden"; //check this on safari, throws assign on readonly error
                    link.download = filename;

                    document.body.appendChild(link);
                    link.click();
                    document.body.removeChild(link);

                    var timenow = new Date().getTime() / 1000;
                    var timeto = (timenow - seconds);

                    console.log("Export process took: " + timeto + " seconds");
                }

                /*--------------functions for creating helper objects and arrays above-----------------*/

                String.prototype.contains = function(item){ return this.indexOf(item) !== -1;};

                function makeHeaderCoords(headerIndexArr, categoryCountsObj){

                  var output = [];

                  headerIndexArr.forEach(function(item){

                    var catID = constants.category[item.category].id;

                    for(var z = 0; z < categoryCountsObj[catID]; z++){
                      if(item.metrics.length < 1){ //this is not future proof, may want to visit the data model.
                        output.push({"category": catID, "metric": ''});
                      }

                      for(var m = 0; m < item.metrics.length; m++){
                        var metID = constants.metric[item.metrics[m]].id;

                        output.push({"category": catID, "metric": metID});
                      }
                    }

                  });

                  return output;

                }

                 function makeHeadersText(headerIndexArr, categoryCountsObj){

                  var output = [];

                  headerIndexArr.forEach(function(item){

                    var catID = constants.category[item.category].id;
                    var catText = constants.category[item.category].name;

                    for(var z = 0; z < categoryCountsObj[catID]; z++){
                      if(item.metrics.length < 1){ //this is not future proof, may want to visit the data model.
                        output.push(catText);
                      }

                      for(var m = 0; m < item.metrics.length; m++){
                        var metText = constants.metric[item.metrics[m]].name;

                        if(z>0){
                          output.push(catText + ' ' + (z + 1).toString() + ' ' + metText);
                        } else {
                          output.push(catText + ' ' + metText);
                        }

                      }
                    }

                  });

                  return output;

                }

                function makeHeaderIndex(recObj, metrics){

                    var tableObj = {};

                    for(var key in recObj){

                      var cat = recObj[key].category;

                      if (!(cat in tableObj)){
                        tableObj[cat] = {};
                      }

                      for(var i in recObj[key].measures){

                        tableObj[cat][i] = metrics[i].name;

                      }

                    }
                    return tableObj;

                }

                function sortHeaderIdx(headerIdx){

                  var newIdx = [];

                  var catKeysSorted = Object.keys(headerIdx).sort().reverse();

                  for(var v = 0; v < catKeysSorted.length; v++){

                    var metKeysSorted = Object.keys(headerIdx[catKeysSorted[v]]).sort().reverse();
                    var metricsArrSorted = [];

                    for(var m = 0; m < metKeysSorted.length; m++){

                      metricsArrSorted.push(metKeysSorted[m]);

                    }
                     newIdx.push({"category": catKeysSorted[v], "metrics": metricsArrSorted});
                  }

                  return newIdx;

                }

                function getCategoryCounts(contObj, recObj){
                  /*count the categories per record and post the largest for each*/
                  var maxCatCounts = {};

                  for(var i in contObj){

                    var container = contObj[i].id;

                    if(!(container in maxCatCounts)){

                      maxCatCounts[container] = {};

                    }

                    var recArr = [];
                    for(var j in contObj[i].records){

                      recArr.push(contObj[i].records[j].id);

                    }

                    if(recArr.length > 0){
                      for(var k=0; k<recArr.length; k++){
                        var category = recObj[recArr[k]].category;
                        if( maxCatCounts[container][category] > 0){
                          maxCatCounts[container][category] += 1;
                        } else {
                          maxCatCounts[container][category] = 1;
                        }
                      }
                    }
                  }

                  /*for maxCatCounts, get all categories into an array for each category */
                  var topCats = {};

                  for (var l in maxCatCounts){
                    for(var m in maxCatCounts[l]){
                       var cat = m;
                       if(!(cat in topCats)){
                            topCats[m] = [];
                       }

                       topCats[m].push(maxCatCounts[l][m]);

                    }

                  }

                  /* get the max # of iterations for each category*/
                  for(var n in topCats){
                    topCats[n] = Math.max.apply(null, topCats[n]);
                  }

                  return topCats;

                }

               return dataExport;
            }

    ]);
<|endoftext|>


<commit_before>import React from 'react'
import Title from '../template/Title'
import Colors from '../../constants/JendelaColors'
import Youtube from '../template/Youtube'

import Author from './Author'

const authors = [
    {
        avatar: "alsa.png",
        name: "Alsasian Atmopawiro",
        title: "Software Engineer",
        location: "Paris, Perancis",
        twitter: "alsa"
    },
    {
        avatar: "cyndy.png",
        name: "Cyndy Messah",
        title: "Visual Designer",
        location: "Singapura",
        twitter: "cyndymessah"
    },
    {
        avatar: "ikhsan.png",
        name: "Ikhsan Assaat",
        title: "Software Engineer",
        location: "London, Inggris",
        twitter: "ixnixnixn"
    },
    {
        avatar: "yoel.png",
        name: "Yoel Sumitro",
        title: "UX Designer",
        location: "Nuremberg, Jerman",
        twitter: "yoelkrisnanda"
    },
]

class AuthorSection extends React.Component {
    render() {
        return (

Tim Jendela

                {authors.map((author, idx) => {
                    return (

                            <Author
                                avatar={author.avatar}
                                name={author.name}
                                title={author.title}
                                location={author.location}
                                twitter={author.twitter} />

                    )
                })}

        )
    }
}

class Team extends React.Component {
    render() {

        return (

                    <Title
                        iconPath="img/icon-jendela-info.png"
                        text="Tentang Jendela"
                        color="#2d4771" />

Jendela adalah sebuah inisiatif untuk meningkatkan transparansi pelayanan publik dan melawan praktek suap dengan cara mengajak partisipasi masyarakat untuk menceritakan pengalaman mereka di portal ini.

Setiap orang bisa memberikan penilaian dan ulasan atas layanan publik yang diterimanya seperti proses pembuatan KTP, Kartu Keluarga, SIM, dll. Bahkan, masyarakat juga bisa melaporkan praktek korupsi yang dialaminya secara anonim.

Kemudian, Jendela akan mengkompilasi semua data yang diterima dan menampilkannya  dalam bentuk visualisasi data. Visualisasi data dan ulasan lengkap tentang pelayanan public ini bisa digunakan bagi:

Masyarakat umum untuk melihat kualitas layanan publik di daerahnya

Para kepala daerah dan pejabat publik untuk mengawasi layanan publik di daerahnya dan  melakukan aksi akan setiap praktek suap yang dilaporkan oleh masyarakat

Nantinya kami berharap dengan adanya transparansi data maka layanan publik bisa ditingkatkan.

                    <Youtube videoID="gg_wcLIH4RA" />

                    <AuthorSection />

        )
    }
}

export default Team
<commit_msg>fixing #99<commit_after>import React from 'react'
import Title from '../template/Title'
import Colors from '../../constants/JendelaColors'
import Youtube from '../template/Youtube'

import Author from './Author'

const authors = [
    {
        avatar: "alsa.png",
        name: "Alsasian Atmopawiro",
        title: "Software Engineer",
        location: "Paris, Perancis",
        twitter: "alsa"
    },
    {
        avatar: "cyndy.png",
        name: "Cyndy Messah",
        title: "Visual Designer",
        location: "Singapura",
        twitter: "cyndymessah"
    },
    {
        avatar: "ikhsan.png",
        name: "Ikhsan Assaat",
        title: "Software Engineer",
        location: "London, Inggris",
        twitter: "ixnixnixn"
    },
    {
        avatar: "yoel.png",
        name: "Yoel Sumitro",
        title: "UX Designer",
        location: "Nuremberg, Jerman",
        twitter: "yoel_krisnanda"
    },
]

class AuthorSection extends React.Component {
    render() {
        return (

Tim Jendela

                {authors.map((author, idx) => {
                    return (

                            <Author
                                avatar={author.avatar}
                                name={author.name}
                                title={author.title}
                                location={author.location}
                                twitter={author.twitter} />

                    )
                })}

        )
    }
}

class Team extends React.Component {
    render() {

        return (

                    <Title
                        iconPath="img/icon-jendela-info.png"
                        text="Tentang Jendela"
                        color="#2d4771" />

Jendela adalah sebuah inisiatif untuk meningkatkan transparansi pelayanan publik dan melawan praktek suap dengan cara mengajak partisipasi masyarakat untuk menceritakan pengalaman mereka di portal ini.

Setiap orang bisa memberikan penilaian dan ulasan atas layanan publik yang diterimanya seperti proses pembuatan KTP, Kartu Keluarga, SIM, dll. Bahkan, masyarakat juga bisa melaporkan praktek korupsi yang dialaminya secara anonim.

Kemudian, Jendela akan mengkompilasi semua data yang diterima dan menampilkannya  dalam bentuk visualisasi data. Visualisasi data dan ulasan lengkap tentang pelayanan public ini bisa digunakan bagi:

Masyarakat umum untuk melihat kualitas layanan publik di daerahnya

Para kepala daerah dan pejabat publik untuk mengawasi layanan publik di daerahnya dan  melakukan aksi akan setiap praktek suap yang dilaporkan oleh masyarakat

Nantinya kami berharap dengan adanya transparansi data maka layanan publik bisa ditingkatkan.

                    <Youtube videoID="gg_wcLIH4RA" />

                    <AuthorSection />

        )
    }
}

export default Team
<|endoftext|>


<commit_before>'use babel'

const Path = require('path')
import {CompositeDisposable} from 'atom'

export default {

  Hack: null,
  config: {
    hackExecutablePath: {
      type: 'string',
      default: 'hh_client'
    }
  },

  activate: function() {
    this.subscriptions = new CompositeDisposable
    this.Hack = require('./base')
    require('atom-package-deps').install('Atom-Hack')

    this.subscriptions.add(atom.commands.add('atom-text-editor[data-grammar~=hack]', 'symbols-view:go-to-declaration', () => {
      if (!this.Hack.connected) {
        return // No-Op
      }
      const textEditor = atom.workspace.getActiveTextEditor()
      if (!textEditor || !textEditor.getPath()) {
        return // Ignore
      }
      const position = textEditor.getCursorBufferPosition()
      const exePath = atom.config.get('Atom-Hack.hackExecutablePath')
      this.Hack.exec(exePath, ['--identify-function', (position.row + 1) + ':' + (position.column + 1)], {stdin: textEditor.getText(), cwd: Path.dirname(textEditor.getPath())})
        .then(contents => {
          if (!contents) {
            return // It must be an invalid position
          }
          this.Hack.exec(exePath, ['--search', contents, '--json'], {cwd: Path.dirname(textEditor.getPath())})
            .then(JSON.parse).then(contents => {
              if (!contents.length) {
                return // Nothing found
              }
              atom.workspace.open(this.Hack.convertPath(contents[0].filename, false)).then(function() {
                atom.workspace.getActiveTextEditor().setCursorBufferPosition([contents[0].line - 1, contents[0].char_start - 1])
              })
            })
        })
    }))
  },
  deactivate: function() {
    this.subscriptions.dispose()
  },
  provideHack: function() {
    return this.Hack
  },
  provideLinter: function() {
    const Linter = {
      grammarScopes: ['source.hack', 'source.html.hack'],
      scope: 'project',
      lintOnFly: false,
      name: 'Atom-Hack',
      formatError: (error) => {
        return {
          type: 'Error',
          filePath: this.Hack.convertPath(error.path, false),
          text: error.descr,
          range: [[error.line - 1, error.start - 1 ],[error.line - 1, error.end]]
        }
      },
      formatErrors: function(errors) {
        const toReturn = []
        errors.errors.forEach(error => {
          const errorEntry = Linter.formatError(error.message.shift())
          errorEntry.trace = error.message.map(Linter.formatError)
          errorEntry.trace.forEach(e => e.type = 'Trace')
          toReturn.push(errorEntry)
        })
        return toReturn
      },
      lint: textEditor => {
        const filePath = textEditor.getPath()
        if (!this.Hack.connected) {
          return []
        }
        return this.Hack.update(filePath).then(() => {
          return this.Hack.exec(atom.config.get('Atom-Hack.hackExecutablePath'), ['--json'], {cwd: Path.dirname(filePath), stream: 'both'})
        }).then(function(streams) {
          const contents = streams.stdout || streams.stderr || ''
          try {
            return Linter.formatErrors(JSON.parse(contents))
          } catch (err) {
            console.error(err)
            return []
          }
        })
      }
    }
    return Linter
  }
}
<commit_msg>:new: Add a 300ms delay in linting<commit_after>'use babel'

const Path = require('path')
import {CompositeDisposable} from 'atom'

export default {

  Hack: null,
  config: {
    hackExecutablePath: {
      type: 'string',
      default: 'hh_client'
    }
  },

  activate: function() {
    this.subscriptions = new CompositeDisposable
    this.Hack = require('./base')
    require('atom-package-deps').install('Atom-Hack')

    this.subscriptions.add(atom.commands.add('atom-text-editor[data-grammar~=hack]', 'symbols-view:go-to-declaration', () => {
      if (!this.Hack.connected) {
        return // No-Op
      }
      const textEditor = atom.workspace.getActiveTextEditor()
      if (!textEditor || !textEditor.getPath()) {
        return // Ignore
      }
      const position = textEditor.getCursorBufferPosition()
      const exePath = atom.config.get('Atom-Hack.hackExecutablePath')
      this.Hack.exec(exePath, ['--identify-function', (position.row + 1) + ':' + (position.column + 1)], {stdin: textEditor.getText(), cwd: Path.dirname(textEditor.getPath())})
        .then(contents => {
          if (!contents) {
            return // It must be an invalid position
          }
          this.Hack.exec(exePath, ['--search', contents, '--json'], {cwd: Path.dirname(textEditor.getPath())})
            .then(JSON.parse).then(contents => {
              if (!contents.length) {
                return // Nothing found
              }
              atom.workspace.open(this.Hack.convertPath(contents[0].filename, false)).then(function() {
                atom.workspace.getActiveTextEditor().setCursorBufferPosition([contents[0].line - 1, contents[0].char_start - 1])
              })
            })
        })
    }))
  },
  deactivate: function() {
    this.subscriptions.dispose()
  },
  provideHack: function() {
    return this.Hack
  },
  provideLinter: function() {
    const Linter = {
      grammarScopes: ['source.hack', 'source.html.hack'],
      scope: 'project',
      lintOnFly: false,
      name: 'Atom-Hack',
      formatError: (error) => {
        return {
          type: 'Error',
          filePath: this.Hack.convertPath(error.path, false),
          text: error.descr,
          range: [[error.line - 1, error.start - 1 ],[error.line - 1, error.end]]
        }
      },
      formatErrors: function(errors) {
        const toReturn = []
        errors.errors.forEach(error => {
          const errorEntry = Linter.formatError(error.message.shift())
          errorEntry.trace = error.message.map(Linter.formatError)
          errorEntry.trace.forEach(e => e.type = 'Trace')
          toReturn.push(errorEntry)
        })
        return toReturn
      },
      lint: textEditor => {
        const filePath = textEditor.getPath()
        if (!this.Hack.connected) {
          return []
        }
        return this.Hack.update(filePath)
        .then(function() {
          return new Promise(function(resolve) {
            setTimeout(resolve, 300)
          })
        })
        .then(() => {
          return this.Hack.exec(atom.config.get('Atom-Hack.hackExecutablePath'), ['--json'], {cwd: Path.dirname(filePath), stream: 'both'})
        }).then(function(streams) {
          const contents = streams.stdout || streams.stderr || ''
          try {
            return Linter.formatErrors(JSON.parse(contents))
          } catch (err) {
            console.error(err)
            return []
          }
        })
      }
    }
    return Linter
  }
}
<|endoftext|>