<commit_before>"use strict";
var fs = require("fs");
var Docker = require('dockerode');
var utils = require("soajs/lib/utils");
var Grid = require('gridfs-stream');
var fs = require('fs');
var rimraf = require('rimraf');

function checkError(error, cb, fCb) {
	if (error) {
		return cb(error, null);
	}
	return fCb();
}

function getDockerCerts(dockerConfig, certs, gfs, db, counter, cb) {
	var gs = new gfs.mongo.GridStore(db, certs[counter]._id, 'r', {
		root: 'fs',
		w: 1,
		fsync: true
	});

	gs.open(function (error, gstore) {
		checkError(error, cb, function () {
			gstore.read(function (error, filedata) {
				checkError(error, cb, function () {
					gstore.close();
					var certKey = certs[counter].filename.split(".")[0];
					dockerConfig[certKey] = filedata;

					counter++;
					if (counter === certs.length) {
						return cb(null, dockerConfig);
					}
					else {
						return getDockerCerts(dockerConfig, certs, gfs, db, counter, cb);
					}
				});
			});
		});
	});
}

var lib = {
	"getDeployer": function (deployerConfig, mongo, cb) {
		/**
			Three options:
				- local: use socket port
				- remote: get manager node record, extract ip/port and certificates
				- remote and adding a new node: in case of adding a new node, use ip/port of new node and certificates
		*/
		var config = utils.cloneObj(deployerConfig);
		var docker;
		if (config.socketPath) {
			docker = new Docker({socketPath: config.socketPath});
			return cb(null, docker);
		}
		else {
			var dockerConfig = {};
			getTargetNode(config, function (error, target) {
				checkError(error, cb, function () {
					dockerConfig.host = target.host;
					dockerConfig.port = target.port;

					getNodeCertificates(config, dockerConfig, function (error, dockerConfig) {
						checkError(error, cb, function () {
							docker = new Docker(dockerConfig);
							return cb(null, docker);
						});
					});
				});
			});
		}

		function getTargetNode(config, callback) {
			if (config.flags && (config.flags.newNode || config.flags.targetNode)) {
				if (!config.host || !config.port) {
					return callback({message: 'Missing host/port info'});
				}
				return callback(null, {host: config.host, port: config.port});
			}
			else {
				if (!config.nodes || (config.nodes && config.nodes.length === 0)) {
					return callback({message: 'No manager nodes found in this environment\'s deployer'});
				}
				var oneManagerNode = config.nodes[0]; //any manager node can be selected
				mongo.findOne('docker', {recordType: 'node', name: oneManagerNode}, function (error, nodeRecord) {
					checkError(error || !nodeRecord, callback, function () {
						return callback(null, {host: nodeRecord.ip, port: nodeRecord.dockerPort});
					});
				});
			}
		}

		function getNodeCertificates(config, dockerConfig, callback) {
			if (!config.envCode) {
				return callback({message: 'Missing environment code'});
			}

			var criteria = {};
			criteria['metadata.env.' + config.envCode.toUpperCase()] = config.selectedDriver;
			mongo.find('fs.files', criteria, function (error, certs) {
				checkError(error, callback, function () {
					if (!certs || (certs && certs.length === 0)) {
						return callback({message: 'No certificates for ' + config.envCode + ' environment found'});
					}

					mongo.getMongoSkinDB(function (error, db) {
						checkError(error, callback, function () {
							var gfs = Grid(db, mongo.mongoSkin);
							var counter = 0;
							getDockerCerts(dockerConfig, certs, gfs, db, counter, function (error, dockerConfig) {
								checkError(error, callback, function () {
									return callback(null, dockerConfig);
								});
							});
						});
					});
				});
			});
		}
	},

	"container": function (dockerInfo, action, cid, mongo, opts, cb) {
		lib.getDeployer(dockerInfo, mongo, function (error, deployer) {
			checkError(error, cb, function () {
				var container = deployer.getContainer(cid);
				container[action](opts || null, function (error, response) {

					checkError(error, cb, function () {
						if (action === 'start' || action === 'restart') {
							container.inspect(cb);
						}
						else return cb(null, response);
					});
				});
			});
		});
	}
};
var deployer = {
	"createContainer": function (deployerConfig, params, mongo, cb) {
		lib.getDeployer(deployerConfig, mongo, function (error, deployer) {
			if (error) {
				return cb(error);
			}
			deployer.createContainer(params, function (err, container) {
				checkError(err, cb, function () {
					container.inspect(cb);
				});
			});
		});
	},

	"start": function (deployerConfig, cid, mongo, cb) {
		lib.container(deployerConfig, "start", cid, mongo, null, cb);
	},

	"exec": function (deployerConfig, cid, mongo, opts, cb) {
		lib.container(deployerConfig, "exec", cid, mongo, opts, cb);
	},

	"restart": function (deployerConfig, cid, mongo, cb) {
		lib.container(deployerConfig, "restart", cid, mongo, null, cb);
	},

	"remove": function (deployerConfig, cid, mongo, cb) {
		lib.container(deployerConfig, "remove", cid, mongo, {"force": true}, cb);
	},

	"info": function (deployerConfig, cid, soajs, res, mongo) {
		lib.getDeployer(deployerConfig, mongo, function (error, deployer) {
			deployer.getContainer(cid).logs({
					stderr: true,
					stdout: true,
					timestamps: false,
					tail: 200
				},
				function (error, stream) {
					if (error) {
						soajs.log.error('logStreamContainer error: ', error);
						return res.json(soajs.buildResponse({"code": 601, "msg": error.message}));
					}
					else {
						var data = '';
						var chunk;
						stream.setEncoding('utf8');
						stream.on('readable', function () {
							var handle = this;
							while ((chunk = handle.read()) != null) {
								data += chunk.toString("utf8");
							}
						});

						stream.on('end', function () {
							stream.destroy();
							var out = soajs.buildResponse(null, {'data': data});
							return res.json(out);
						});
					}
				});
		});
	},

	"addNode": function (deployerConfig, options, mongo, cb) {
		deployerConfig.flags = {
			newNode: true
		};

		lib.getDeployer(deployerConfig, mongo, function (error, deployer) {
			checkError(error, cb, function () {
				deployer.info(function (error, nodeInfo) {
					checkError(error, cb, function () {
						deployer.swarmJoin(options, function (error) {
							checkError(error, cb, function () {
								if (options.role === 'manager') {
									var node = deployer.getNode(nodeInfo.Name);
									node.inspect(cb);
								}
								else {
									//get manager node from swarm and inspect newly added node
									delete deployerConfig.flags;
									delete deployerConfig.host;
									delete deployerConfig.port;

									lib.getDeployer(deployerConfig, mongo, function (error, deployer) {
										checkError(error, cb, function () {
											deployer.listNodes(function (error, nodes) {
												checkError(error, cb, function () {
													for (var i = 0; i < nodes.length; i++) {
														if (nodes[i].Description.Hostname === nodeInfo.Name) {
															return cb(null, nodes[i]);
														}
													}
													return cb();
												});
											});
										});
									});
								}
							});
						});
					});
				});
			});
		});
	},

	"removeNode": function (deployerConfig, options, mongo, cb, backgroundCB) {
		/*
			- get deployer for target node
			- leave swarm
			- return success response
			- get deployer of a manager node in the swarm
			- remove node
		*/

		var targetDeployerConfig = JSON.parse(JSON.stringify(deployerConfig));
		targetDeployerConfig.host = options.ip;
		targetDeployerConfig.port = options.dockerPort;
		targetDeployerConfig.flags = { targetNode: true };
		lib.getDeployer(targetDeployerConfig, mongo, function (error, targetDeployer) {
			checkError(error, cb, function () {
				targetDeployer.swarmLeave(function (error) {
					checkError(error, cb, function () {

						//return response and remove node entry from swarm in the background
						cb(null, true);

						lib.getDeployer(deployerConfig, mongo, function (error, deployer) {
							var node = deployer.getNode(options.id);
							setTimeout(function () {
								node.remove(backgroundCB);
							}, 20000);
						});
					});
				});
			});
		});
	},

	"updateNode": function (deployerConfig, options, mongo, cb) {
		lib.getDeployer(deployerConfig, mongo, function (error, deployer) {
			checkError(error, cb, function () {
				var node = deployer.getNode(options.nodeId);

				//need to inspect node in order to get its current version and pass it to update call
				node.inspect(function (error, nodeRecord) {
					checkError(error, cb, function () {
						options.version = nodeRecord.Version.Index;
						node.update(options, function (error, result) {
							checkError(error, cb, function () {
								return cb(null, true);
							});
						});
					});
				});
			});
		});
	},

	"deployHAService": function (deployerConfig, options, mongo, cb) {
		lib.getDeployer(deployerConfig, mongo, function (error, deployer) {
			checkError(error, cb, function () {
				deployer.createService(options, cb);
			});
		});
	},

	"scaleHAService": function (deployerConfig, options, mongo, cb) {
		lib.getDeployer(deployerConfig, mongo, function (error, deployer) {
			checkError(error, cb, function () {
				var service = deployer.getService(options.serviceName);
				service.inspect(function (error, serviceInfo) {
					checkError(error, cb, function () {
						//docker api does not support updating a service using its name
						service = deployer.getService(serviceInfo.ID);

						var update = serviceInfo.Spec;
						update.version = serviceInfo.Version.Index;
						update.Mode.Replicated.Replicas = options.scale;
						service.update(update, cb);
					});
				});
			});
		});
	},

	"inspectHAService": function (deployerConfig, options, mongo, cb) {
		lib.getDeployer(deployerConfig, mongo, function (error, deployer) {
			checkError(error, cb, function () {
				var output = {}, params = {};
				var service = deployer.getService(options.serviceName);
				service.inspect(function (error, serviceInfo) {
					checkError(error, cb, function () {
						output.service = serviceInfo;

						params.filters = { service: [options.serviceName] };
						deployer.listTasks(params, function (error, serviceTasks) {
							checkError(error, cb, function () {
								output.tasks = serviceTasks;
								return cb(null, output);
							});
						});
					});
				});
			});
		});
	},

	"inspectHATask": function (deployerConfig, options, mongo, cb) {
		lib.getDeployer(deployerConfig, mongo, function (error, deployer) {
			checkError(error, cb, function () {
				var params = {
					filters: {
						// name: [options.taskName], //TODO: check issue
						service: [options.taskName.split('.')[0]]
					}
				};

				deployer.listTasks(params, function (error, taskRecord) {
					checkError(error, cb, function () {
						return cb(null, taskRecord[0]);
					});
				});
			});
		});
	},

	"deleteHAService": function (deployerConfig, options, mongo, cb) {
		lib.getDeployer(deployerConfig, mongo, function (error, deployer) {
			checkError(error, cb, function () {
				var serviceId = options.serviceName;
				var service = deployer.getService(serviceId);
				service.remove(cb);
			});
		});
	},

	"inspectContainer": function (deployerConfig, options, mongo, cb) {
		mongo.findOne('docker', {recordType: 'node', id: options.nodeId}, function (error, nodeInfo) {
			checkError(error || !nodeInfo, cb, function () {
				deployerConfig.host = nodeInfo.ip;
				deployerConfig.port = nodeInfo.dockerPort;
				deployerConfig.flags = { targetNode: true };
				lib.getDeployer(deployerConfig, mongo, function (error, deployer) {
					checkError(error, cb, function () {
						var container = deployer.getContainer(options.containerId);
						container.inspect(cb);
					});
				});
			});
		});
	},

	"getContainerLogs": function (deployerConfig, options, mongo, soajs, res) {
		mongo.findOne('docker', {recordType: 'node', id: options.nodeId}, function (error, nodeInfo) {
			if (error || !nodeInfo) {
				error = ((error) ? error : {message: 'Node record not found'});
				soajs.log.error(error);
				return res.jsonp(soajs.buildResponse({code: 601, msg: error.message}));
			}

			deployerConfig.host = nodeInfo.ip;
			deployerConfig.port = nodeInfo.dockerPort;
			deployerConfig.flags = { targetNode: true };
			lib.getDeployer(deployerConfig, mongo, function (error, deployer) {
				if (error) {
					soajs.log.error(error);
					return res.jsonp(soajs.buildResponse({code: 601, msg: error.message}));
				}
				var container = deployer.getContainer(options.containerId);
				var logOptions = {
					stdout: true,
					stderr: true,
					tail: 400
				};

				container.logs(logOptions, function (error, logStream) {
					if (error) {
						soajs.log.error(error);
						return res.jsonp(soajs.buildResponse({code: 601, msg: error.message}));
					}

					var data = '';
					var chunk;
					logStream.setEncoding('utf8');
					logStream.on('readable', function () {
						var handle = this;
						while ((chunk = handle.read()) != null) {
							data += chunk.toString("utf8");
						}
					});

					logStream.on('end', function () {
						logStream.destroy();
						var out = soajs.buildResponse(null, {'data': data});
						return res.json(out);
					});
				});
			});
		});
	}
};

module.exports = deployer;
<commit_msg>SAOJ-365: testing fix for inspectHATask()<commit_after>"use strict";
var fs = require("fs");
var Docker = require('dockerode');
var utils = require("soajs/lib/utils");
var Grid = require('gridfs-stream');
var fs = require('fs');
var rimraf = require('rimraf');

function checkError(error, cb, fCb) {
	if (error) {
		return cb(error, null);
	}
	return fCb();
}

function getDockerCerts(dockerConfig, certs, gfs, db, counter, cb) {
	var gs = new gfs.mongo.GridStore(db, certs[counter]._id, 'r', {
		root: 'fs',
		w: 1,
		fsync: true
	});

	gs.open(function (error, gstore) {
		checkError(error, cb, function () {
			gstore.read(function (error, filedata) {
				checkError(error, cb, function () {
					gstore.close();
					var certKey = certs[counter].filename.split(".")[0];
					dockerConfig[certKey] = filedata;

					counter++;
					if (counter === certs.length) {
						return cb(null, dockerConfig);
					}
					else {
						return getDockerCerts(dockerConfig, certs, gfs, db, counter, cb);
					}
				});
			});
		});
	});
}

var lib = {
	"getDeployer": function (deployerConfig, mongo, cb) {
		/**
			Three options:
				- local: use socket port
				- remote: get manager node record, extract ip/port and certificates
				- remote and adding a new node: in case of adding a new node, use ip/port of new node and certificates
		*/
		var config = utils.cloneObj(deployerConfig);
		var docker;
		if (config.socketPath) {
			docker = new Docker({socketPath: config.socketPath});
			return cb(null, docker);
		}
		else {
			var dockerConfig = {};
			getTargetNode(config, function (error, target) {
				checkError(error, cb, function () {
					dockerConfig.host = target.host;
					dockerConfig.port = target.port;

					getNodeCertificates(config, dockerConfig, function (error, dockerConfig) {
						checkError(error, cb, function () {
							docker = new Docker(dockerConfig);
							return cb(null, docker);
						});
					});
				});
			});
		}

		function getTargetNode(config, callback) {
			if (config.flags && (config.flags.newNode || config.flags.targetNode)) {
				if (!config.host || !config.port) {
					return callback({message: 'Missing host/port info'});
				}
				return callback(null, {host: config.host, port: config.port});
			}
			else {
				if (!config.nodes || (config.nodes && config.nodes.length === 0)) {
					return callback({message: 'No manager nodes found in this environment\'s deployer'});
				}
				var oneManagerNode = config.nodes[0]; //any manager node can be selected
				mongo.findOne('docker', {recordType: 'node', name: oneManagerNode}, function (error, nodeRecord) {
					checkError(error || !nodeRecord, callback, function () {
						return callback(null, {host: nodeRecord.ip, port: nodeRecord.dockerPort});
					});
				});
			}
		}

		function getNodeCertificates(config, dockerConfig, callback) {
			if (!config.envCode) {
				return callback({message: 'Missing environment code'});
			}

			var criteria = {};
			criteria['metadata.env.' + config.envCode.toUpperCase()] = config.selectedDriver;
			mongo.find('fs.files', criteria, function (error, certs) {
				checkError(error, callback, function () {
					if (!certs || (certs && certs.length === 0)) {
						return callback({message: 'No certificates for ' + config.envCode + ' environment found'});
					}

					mongo.getMongoSkinDB(function (error, db) {
						checkError(error, callback, function () {
							var gfs = Grid(db, mongo.mongoSkin);
							var counter = 0;
							getDockerCerts(dockerConfig, certs, gfs, db, counter, function (error, dockerConfig) {
								checkError(error, callback, function () {
									return callback(null, dockerConfig);
								});
							});
						});
					});
				});
			});
		}
	},

	"container": function (dockerInfo, action, cid, mongo, opts, cb) {
		lib.getDeployer(dockerInfo, mongo, function (error, deployer) {
			checkError(error, cb, function () {
				var container = deployer.getContainer(cid);
				container[action](opts || null, function (error, response) {

					checkError(error, cb, function () {
						if (action === 'start' || action === 'restart') {
							container.inspect(cb);
						}
						else return cb(null, response);
					});
				});
			});
		});
	}
};
var deployer = {
	"createContainer": function (deployerConfig, params, mongo, cb) {
		lib.getDeployer(deployerConfig, mongo, function (error, deployer) {
			if (error) {
				return cb(error);
			}
			deployer.createContainer(params, function (err, container) {
				checkError(err, cb, function () {
					container.inspect(cb);
				});
			});
		});
	},

	"start": function (deployerConfig, cid, mongo, cb) {
		lib.container(deployerConfig, "start", cid, mongo, null, cb);
	},

	"exec": function (deployerConfig, cid, mongo, opts, cb) {
		lib.container(deployerConfig, "exec", cid, mongo, opts, cb);
	},

	"restart": function (deployerConfig, cid, mongo, cb) {
		lib.container(deployerConfig, "restart", cid, mongo, null, cb);
	},

	"remove": function (deployerConfig, cid, mongo, cb) {
		lib.container(deployerConfig, "remove", cid, mongo, {"force": true}, cb);
	},

	"info": function (deployerConfig, cid, soajs, res, mongo) {
		lib.getDeployer(deployerConfig, mongo, function (error, deployer) {
			deployer.getContainer(cid).logs({
					stderr: true,
					stdout: true,
					timestamps: false,
					tail: 200
				},
				function (error, stream) {
					if (error) {
						soajs.log.error('logStreamContainer error: ', error);
						return res.json(soajs.buildResponse({"code": 601, "msg": error.message}));
					}
					else {
						var data = '';
						var chunk;
						stream.setEncoding('utf8');
						stream.on('readable', function () {
							var handle = this;
							while ((chunk = handle.read()) != null) {
								data += chunk.toString("utf8");
							}
						});

						stream.on('end', function () {
							stream.destroy();
							var out = soajs.buildResponse(null, {'data': data});
							return res.json(out);
						});
					}
				});
		});
	},

	"addNode": function (deployerConfig, options, mongo, cb) {
		deployerConfig.flags = {
			newNode: true
		};

		lib.getDeployer(deployerConfig, mongo, function (error, deployer) {
			checkError(error, cb, function () {
				deployer.info(function (error, nodeInfo) {
					checkError(error, cb, function () {
						deployer.swarmJoin(options, function (error) {
							checkError(error, cb, function () {
								if (options.role === 'manager') {
									var node = deployer.getNode(nodeInfo.Name);
									node.inspect(cb);
								}
								else {
									//get manager node from swarm and inspect newly added node
									delete deployerConfig.flags;
									delete deployerConfig.host;
									delete deployerConfig.port;

									lib.getDeployer(deployerConfig, mongo, function (error, deployer) {
										checkError(error, cb, function () {
											deployer.listNodes(function (error, nodes) {
												checkError(error, cb, function () {
													for (var i = 0; i < nodes.length; i++) {
														if (nodes[i].Description.Hostname === nodeInfo.Name) {
															return cb(null, nodes[i]);
														}
													}
													return cb();
												});
											});
										});
									});
								}
							});
						});
					});
				});
			});
		});
	},

	"removeNode": function (deployerConfig, options, mongo, cb, backgroundCB) {
		/*
			- get deployer for target node
			- leave swarm
			- return success response
			- get deployer of a manager node in the swarm
			- remove node
		*/

		var targetDeployerConfig = JSON.parse(JSON.stringify(deployerConfig));
		targetDeployerConfig.host = options.ip;
		targetDeployerConfig.port = options.dockerPort;
		targetDeployerConfig.flags = { targetNode: true };
		lib.getDeployer(targetDeployerConfig, mongo, function (error, targetDeployer) {
			checkError(error, cb, function () {
				targetDeployer.swarmLeave(function (error) {
					checkError(error, cb, function () {

						//return response and remove node entry from swarm in the background
						cb(null, true);

						lib.getDeployer(deployerConfig, mongo, function (error, deployer) {
							var node = deployer.getNode(options.id);
							setTimeout(function () {
								node.remove(backgroundCB);
							}, 20000);
						});
					});
				});
			});
		});
	},

	"updateNode": function (deployerConfig, options, mongo, cb) {
		lib.getDeployer(deployerConfig, mongo, function (error, deployer) {
			checkError(error, cb, function () {
				var node = deployer.getNode(options.nodeId);

				//need to inspect node in order to get its current version and pass it to update call
				node.inspect(function (error, nodeRecord) {
					checkError(error, cb, function () {
						options.version = nodeRecord.Version.Index;
						node.update(options, function (error, result) {
							checkError(error, cb, function () {
								return cb(null, true);
							});
						});
					});
				});
			});
		});
	},

	"deployHAService": function (deployerConfig, options, mongo, cb) {
		lib.getDeployer(deployerConfig, mongo, function (error, deployer) {
			checkError(error, cb, function () {
				deployer.createService(options, cb);
			});
		});
	},

	"scaleHAService": function (deployerConfig, options, mongo, cb) {
		lib.getDeployer(deployerConfig, mongo, function (error, deployer) {
			checkError(error, cb, function () {
				var service = deployer.getService(options.serviceName);
				service.inspect(function (error, serviceInfo) {
					checkError(error, cb, function () {
						//docker api does not support updating a service using its name
						service = deployer.getService(serviceInfo.ID);

						var update = serviceInfo.Spec;
						update.version = serviceInfo.Version.Index;
						update.Mode.Replicated.Replicas = options.scale;
						service.update(update, cb);
					});
				});
			});
		});
	},

	"inspectHAService": function (deployerConfig, options, mongo, cb) {
		lib.getDeployer(deployerConfig, mongo, function (error, deployer) {
			checkError(error, cb, function () {
				var output = {}, params = {};
				var service = deployer.getService(options.serviceName);
				service.inspect(function (error, serviceInfo) {
					checkError(error, cb, function () {
						output.service = serviceInfo;

						params.filters = { service: [options.serviceName] };
						deployer.listTasks(params, function (error, serviceTasks) {
							checkError(error, cb, function () {
								output.tasks = serviceTasks;
								return cb(null, output);
							});
						});
					});
				});
			});
		});
	},

	"inspectHATask": function (deployerConfig, options, mongo, cb) {
		lib.getDeployer(deployerConfig, mongo, function (error, deployer) {
			checkError(error, cb, function () {
				var params = {
					filters: {
						name: [options.taskName],
						service: [options.taskName.split('.')[0]]
					}
				};

				deployer.listTasks(params, function (error, taskRecord) {
					checkError(error, cb, function () {
						return cb(null, taskRecord[0]);
					});
				});
			});
		});
	},

	"deleteHAService": function (deployerConfig, options, mongo, cb) {
		lib.getDeployer(deployerConfig, mongo, function (error, deployer) {
			checkError(error, cb, function () {
				var serviceId = options.serviceName;
				var service = deployer.getService(serviceId);
				service.remove(cb);
			});
		});
	},

	"inspectContainer": function (deployerConfig, options, mongo, cb) {
		mongo.findOne('docker', {recordType: 'node', id: options.nodeId}, function (error, nodeInfo) {
			checkError(error || !nodeInfo, cb, function () {
				deployerConfig.host = nodeInfo.ip;
				deployerConfig.port = nodeInfo.dockerPort;
				deployerConfig.flags = { targetNode: true };
				lib.getDeployer(deployerConfig, mongo, function (error, deployer) {
					checkError(error, cb, function () {
						var container = deployer.getContainer(options.containerId);
						container.inspect(cb);
					});
				});
			});
		});
	},

	"getContainerLogs": function (deployerConfig, options, mongo, soajs, res) {
		mongo.findOne('docker', {recordType: 'node', id: options.nodeId}, function (error, nodeInfo) {
			if (error || !nodeInfo) {
				error = ((error) ? error : {message: 'Node record not found'});
				soajs.log.error(error);
				return res.jsonp(soajs.buildResponse({code: 601, msg: error.message}));
			}

			deployerConfig.host = nodeInfo.ip;
			deployerConfig.port = nodeInfo.dockerPort;
			deployerConfig.flags = { targetNode: true };
			lib.getDeployer(deployerConfig, mongo, function (error, deployer) {
				if (error) {
					soajs.log.error(error);
					return res.jsonp(soajs.buildResponse({code: 601, msg: error.message}));
				}
				var container = deployer.getContainer(options.containerId);
				var logOptions = {
					stdout: true,
					stderr: true,
					tail: 400
				};

				container.logs(logOptions, function (error, logStream) {
					if (error) {
						soajs.log.error(error);
						return res.jsonp(soajs.buildResponse({code: 601, msg: error.message}));
					}

					var data = '';
					var chunk;
					logStream.setEncoding('utf8');
					logStream.on('readable', function () {
						var handle = this;
						while ((chunk = handle.read()) != null) {
							data += chunk.toString("utf8");
						}
					});

					logStream.on('end', function () {
						logStream.destroy();
						var out = soajs.buildResponse(null, {'data': data});
						return res.json(out);
					});
				});
			});
		});
	}
};

module.exports = deployer;
<|endoftext|>


<commit_before>/**
 * Created by truthlighting on 9/20/2016.
 */
/**
 * This guys does the other half of energy collection. The miner gets it from the source, and the helper does the
 * transportation. We don't want them just going for the nearest source, as that means that if we have more than one
 * miner, all the helpers will only go for the first miner. To counter this, we assign them to a miner the same way
 * we assign a miner to a source
 */

var helper = {
    parts: [
        [MOVE, CARRY],
        [MOVE, CARRY, MOVE, CARRY],
        [MOVE, CARRY, MOVE, CARRY, MOVE, CARRY]
    ],

    assignMiner: function () {
        var creep = this.creep;

        var miner = creep.pos.findClosestByPath(FIND_MY_CREEPS, {
            filter: function (miner) {
                if (miner.memory.role == 'miner' && miner.memory.helpers.length < miner.memory.helpersNeeded)
                    return true;

                return false;
            }
        });

        if (miner == undefined)
            return;

        creep.memory.miner = miner.id;
        miner.memory.helpers.push(creep.id);
    },

    /**
     * @TODO: Make helpers smarter about avoiding miners, instead of just waiting till they're 5 tiles away
     * @TODO: When spawns are at .25, and extensions have >= 200, help builders before filling shit up
     */
    action: function () {
        var creep = this.creep;

        if (creep.memory.courier !== undefined && creep.memory.courier == true) {
            creep.memory.courier = false;
            return;
        }

        //If this helper isn't assigned to a miner, find one and assign him to it. If it is assigned to a miner,
        //then find that miner by his id
        if (creep.memory.miner == undefined)
            this.assignMiner();

        var miner = Game.getObjectById(creep.memory.miner);

        if (miner == null) {
            creep.suicide();
            return;
        }
        //console.log("i'm here1");
        //If we can still pick up energy, let's do that
        //console.log("miner: " + miner);
        //console.log("creep:" + creep);
        //console.log("creep.carry: (" + _.sum(creep.carry) + ") < creep.carryCapacity (" + creep.carryCapacity + ")");
        //console.log(_.sum(creep.carry) < creep.carryCapacity);
        if (_.sum(creep.carry) < creep.carryCapacity) {
            //console.log("i'm here");
            if (creep.pos.isNearTo(miner)) {
                var energy = creep.pos.findInRange(FIND_DROPPED_ENERGY, 1)[0];
                //console.log("energy: " + energy);
                creep.pickup(energy);
            }
            else {
                if (miner.memory.isNearSource)
                    creep.moveTo(miner);
            }

            return;
        }

        var target = null;
        console.log("I'm here");
        //Okay, everything below is for dropping energy off

        if (!target) {
            //var spawn = creep.pos.findClosestByPath(FIND_MY_SPAWNS);
            var energyStructures = creep.room.find(FIND_STRUCTURES, {
                            filter: (structure) => {
                                return (structure.structureType == STRUCTURE_EXTENSION ||
                                structure.structureType == STRUCTURE_SPAWN) &&
                //            structure.structureType == STRUCTURE_TOWER) &&
                                (structure.energy < structure.energyCapacity);
                            }
            })
            var energyStructure = creep.room.findClosestByPath(energyStructures);
            console.log("energyStructure: " + energyStructure);
            //If we found it, set it as our target
            if (energyStructure)
                target = energyStructure;
        }
        console.log(energyStructure);
        console.log(target);
        //console.log(" creep.pos.findPathTo: " + creep.pos.findPathTo(target));
        //Let's get the direction we want to go in
        var targetDirection = creep.pos.findPathTo(target, { ignoreCreeps: true })[0].direction;
        //console.log("I'm here 3");
        //Let's look for a courier in that direction. We'll check on making sure they're the right
        //role, if they can hold any energy, if they're in range and if they're in the same direction
        var leftDir = targetDirection - 1;
        var rightDir = targetDirection + 1;

        if (leftDir < 1)
            leftDir += 8;
        if (leftDir > 8)
            leftDir -= 8;

        if (rightDir < 1)
            rightDir += 8;
        if (rightDir > 8)
            rightDir -= 8;
        console.log("I'm here 2");
        var courier = creep.pos.findClosestByPath(FIND_MY_CREEPS, {
            filter: function (possibleTarget) {
                return (
                    possibleTarget.memory.role == creep.memory.role
//					&& possibleTarget.memory.miner == creep.memory.miner
                    && _.sum(possibleTarget.carry) < possibleTarget.carryCapacity
                    && creep.pos.inRangeTo(possibleTarget, 1)
                    && (
                        creep.pos.getDirectionTo(possibleTarget) == targetDirection
                        || creep.pos.getDirectionTo(possibleTarget) == leftDir
                        || creep.pos.getDirectionTo(possibleTarget) == rightDir
                    )
                );
            }
        });
        console.log("im here");
        //If we found a courier, make that courier our new target
        if (courier !== null && !creep.pos.isNearTo(target)) {
            target = courier;
            target.memory.courier = true;
        }
        //console.log("target: " + target);
        //If we're near to the target, either give it our energy or drop it
        if (creep.pos.isNearTo(target)) {
            if (_.sum(target.carry) < target.carryCapacity) {
                creep.transfer(target, RESOURCE_ENERGY);
            }
            else
                creep.drop(RESOURCE_ENERGY);
        }
        //Let's do the moving
        else {
            creep.moveTo(target);
        }
    }
};

module.exports = helper;<commit_msg>fixing miner<commit_after>/**
 * Created by truthlighting on 9/20/2016.
 */
/**
 * This guys does the other half of energy collection. The miner gets it from the source, and the helper does the
 * transportation. We don't want them just going for the nearest source, as that means that if we have more than one
 * miner, all the helpers will only go for the first miner. To counter this, we assign them to a miner the same way
 * we assign a miner to a source
 */

var helper = {
    parts: [
        [MOVE, CARRY],
        [MOVE, CARRY, MOVE, CARRY],
        [MOVE, CARRY, MOVE, CARRY, MOVE, CARRY]
    ],

    assignMiner: function () {
        var creep = this.creep;

        var miner = creep.pos.findClosestByPath(FIND_MY_CREEPS, {
            filter: function (miner) {
                if (miner.memory.role == 'miner' && miner.memory.helpers.length < miner.memory.helpersNeeded)
                    return true;

                return false;
            }
        });

        if (miner == undefined)
            return;

        creep.memory.miner = miner.id;
        miner.memory.helpers.push(creep.id);
    },

    /**
     * @TODO: Make helpers smarter about avoiding miners, instead of just waiting till they're 5 tiles away
     * @TODO: When spawns are at .25, and extensions have >= 200, help builders before filling shit up
     */
    action: function () {
        var creep = this.creep;

        if (creep.memory.courier !== undefined && creep.memory.courier == true) {
            creep.memory.courier = false;
            return;
        }

        //If this helper isn't assigned to a miner, find one and assign him to it. If it is assigned to a miner,
        //then find that miner by his id
        if (creep.memory.miner == undefined)
            this.assignMiner();

        var miner = Game.getObjectById(creep.memory.miner);

        if (miner == null) {
            creep.suicide();
            return;
        }
        //console.log("i'm here1");
        //If we can still pick up energy, let's do that
        //console.log("miner: " + miner);
        //console.log("creep:" + creep);
        //console.log("creep.carry: (" + _.sum(creep.carry) + ") < creep.carryCapacity (" + creep.carryCapacity + ")");
        //console.log(_.sum(creep.carry) < creep.carryCapacity);
        if (_.sum(creep.carry) < creep.carryCapacity) {
            //console.log("i'm here");
            if (creep.pos.isNearTo(miner)) {
                var energy = creep.pos.findInRange(FIND_DROPPED_ENERGY, 1)[0];
                //console.log("energy: " + energy);
                creep.pickup(energy);
            }
            else {
                if (miner.memory.isNearSource)
                    creep.moveTo(miner);
            }

            return;
        }

        var target = null;
        //console.log("I'm here");
        //Okay, everything below is for dropping energy off

        if (!target) {
            //var spawn = creep.pos.findClosestByPath(FIND_MY_SPAWNS);
            var energyStructures = creep.room.find(FIND_STRUCTURES, {
                            filter: (structure) => {
                                return (structure.structureType == STRUCTURE_EXTENSION ||
                                structure.structureType == STRUCTURE_SPAWN) &&
                //            structure.structureType == STRUCTURE_TOWER) &&
                                (structure.energy < structure.energyCapacity);
                            }
            })
            var energyStructure = creep.room.findClosestByPath(energyStructures);
            console.log("energyStructure: " + energyStructure);
            //If we found it, set it as our target
            if (energyStructure)
                target = energyStructure;
        }
        console.log(energyStructure);
        console.log(target);
        //console.log(" creep.pos.findPathTo: " + creep.pos.findPathTo(target));
        //Let's get the direction we want to go in
        var targetDirection = creep.pos.findPathTo(target, { ignoreCreeps: true })[0].direction;
        //console.log("I'm here 3");
        //Let's look for a courier in that direction. We'll check on making sure they're the right
        //role, if they can hold any energy, if they're in range and if they're in the same direction
        var leftDir = targetDirection - 1;
        var rightDir = targetDirection + 1;

        if (leftDir < 1)
            leftDir += 8;
        if (leftDir > 8)
            leftDir -= 8;

        if (rightDir < 1)
            rightDir += 8;
        if (rightDir > 8)
            rightDir -= 8;
        console.log("I'm here 2");
        var courier = creep.pos.findClosestByPath(FIND_MY_CREEPS, {
            filter: function (possibleTarget) {
                return (
                    possibleTarget.memory.role == creep.memory.role
//					&& possibleTarget.memory.miner == creep.memory.miner
                    && _.sum(possibleTarget.carry) < possibleTarget.carryCapacity
                    && creep.pos.inRangeTo(possibleTarget, 1)
                    && (
                        creep.pos.getDirectionTo(possibleTarget) == targetDirection
                        || creep.pos.getDirectionTo(possibleTarget) == leftDir
                        || creep.pos.getDirectionTo(possibleTarget) == rightDir
                    )
                );
            }
        });
        console.log("im here");
        //If we found a courier, make that courier our new target
        if (courier !== null && !creep.pos.isNearTo(target)) {
            target = courier;
            target.memory.courier = true;
        }
        //console.log("target: " + target);
        //If we're near to the target, either give it our energy or drop it
        if (creep.pos.isNearTo(target)) {
            if (_.sum(target.carry) < target.carryCapacity) {
                creep.transfer(target, RESOURCE_ENERGY);
            }
            else
                creep.drop(RESOURCE_ENERGY);
        }
        //Let's do the moving
        else {
            creep.moveTo(target);
        }
    }
};

module.exports = helper;<|endoftext|>


<commit_before>require('../../support/test_helper');

const assert = require('../../support/assert');
const TestClient = require('../../support/test-client');
const  mapnik = require('windshaft').mapnik;

describe('authorization', function() {
    it('should create a layergroup with regular apikey token', function(done) {
        const apikeyToken = 'regular1';
        const mapConfig = {
            version: '1.7.0',
            layers: [
                {
                    options: {
                        sql: 'select * FROM test_table_localhost_regular1',
                        cartocss: TestClient.CARTOCSS.POINTS,
                        cartocss_version: '2.3.0'
                    }
                }
            ]
        };
        const testClient = new TestClient(mapConfig, apikeyToken);

        testClient.getLayergroup(function (err, layergroupResult) {
            assert.ifError(err);

            assert.ok(layergroupResult.layergroupid);

            testClient.drain(done);
        });
    });

    it('should fail creating a layergroup with default apikey token', function (done) {
        const apikeyToken = 'default_public';
        const mapConfig = {
            version: '1.7.0',
            layers: [
                {
                    options: {
                        sql: 'select * FROM test_table_localhost_regular1',
                        cartocss: TestClient.CARTOCSS.POINTS,
                        cartocss_version: '2.3.0'
                    }
                }
            ]
        };
        const testClient = new TestClient(mapConfig, apikeyToken);

        testClient.getLayergroup({ response: { status: 403 } }, function (err, layergroupResult) {
            assert.ifError(err);

            assert.ok(layergroupResult.hasOwnProperty('errors'));
            assert.equal(layergroupResult.errors.length, 1);
            assert.ok(layergroupResult.errors[0].match(/permission denied/), layergroupResult.errors[0]);

            testClient.drain(done);
        });
    });

    it('should create a layergroup with default apikey token', function (done) {
        const apikeyToken = 'default_public';
        const mapConfig = {
            version: '1.7.0',
            layers: [
                {
                    options: {
                        sql: 'select * FROM test_table',
                        cartocss: TestClient.CARTOCSS.POINTS,
                        cartocss_version: '2.3.0'
                    }
                }
            ]
        };
        const testClient = new TestClient(mapConfig, apikeyToken);

        testClient.getLayergroup(function (err, layergroupResult) {
            assert.ifError(err);

            assert.ok(layergroupResult.layergroupid);

            testClient.drain(done);
        });
    });

    it('should fail if apikey does not grant access to table', function (done) {
        const mapConfig = {
            version: '1.7.0',
            layers: [
                {
                    options: {
                        sql: 'select * FROM test_table_localhost_regular1',
                        cartocss: TestClient.CARTOCSS.POINTS,
                        cartocss_version: '2.3.0'
                    }
                }
            ]
        };
        const testClient = new TestClient(mapConfig); //no apikey provided, using default

        testClient.getLayergroup({ response: { status: 403 } }, function (err, layergroupResult) { //TODO 401
            assert.ifError(err);

            assert.ok(layergroupResult.hasOwnProperty('errors'));
            assert.equal(layergroupResult.errors.length, 1);
            assert.ok(layergroupResult.errors[0].match(/permission denied/), layergroupResult.errors[0]);

            testClient.drain(done);
        });
    });

    it('should forbide access to API if API key does not grant access', function (done) {
        const apikeyToken = 'regular2';
        const mapConfig = {
            version: '1.7.0',
            layers: [
                {
                    options: {
                        sql: 'select * FROM test_table_localhost_regular1',
                        cartocss: TestClient.CARTOCSS.POINTS,
                        cartocss_version: '2.3.0'
                    }
                }
            ]
        };
        const testClient = new TestClient(mapConfig, apikeyToken);

        testClient.getLayergroup({ response: { status: 403 } }, function (err, layergroupResult) {
            assert.ifError(err);

            assert.ok(layergroupResult.hasOwnProperty('errors'));
            assert.equal(layergroupResult.errors.length, 1);
            assert.ok(layergroupResult.errors[0].match(/Forbidden/), layergroupResult.errors[0]);

            testClient.drain(done);
        });
    });

    it('should create a layergroup with a source analysis using a default apikey token', function (done) {
        const apikeyToken = 'default_public';
        const mapConfig = {
            version: '1.7.0',
            layers: [
                {
                    type: 'cartodb',
                    options: {
                        source: {
                            id: 'HEAD'
                        },
                        cartocss: TestClient.CARTOCSS.POINTS,
                        cartocss_version: '2.3.0'
                    }
                }
            ],
            analyses: [
                {
                    id: 'HEAD',
                    type: 'source',
                    params: {
                        query: 'select * from populated_places_simple_reduced'
                    }
                }
            ]
        };
        const testClient = new TestClient(mapConfig, apikeyToken);

        testClient.getLayergroup(function (err, layergroupResult) {
            assert.ifError(err);

            assert.ok(layergroupResult.layergroupid);

            testClient.drain(done);
        });
    });

    it('should create a layergroup with a source analysis using a regular apikey token', function (done) {
        const apikeyToken = 'regular1';
        const mapConfig = {
            version: '1.7.0',
            layers: [
                {
                    type: 'cartodb',
                    options: {
                        source: {
                            id: 'HEAD'
                        },
                        cartocss: TestClient.CARTOCSS.POINTS,
                        cartocss_version: '2.3.0'
                    }
                }
            ],
            analyses: [
                {
                    id: 'HEAD',
                    type: 'source',
                    params: {
                        query: 'select * from test_table_localhost_regular1'
                    }
                }
            ]
        };
        const testClient = new TestClient(mapConfig, apikeyToken);

        testClient.getLayergroup(function (err, layergroupResult) {
            assert.ifError(err);

            assert.ok(layergroupResult.layergroupid);

            testClient.drain(done);
        });
    });

    // Warning: TBA
    it('should create a layergroup with a buffer analysis using a regular apikey token', function (done) {
        const apikeyToken = 'regular1';
        const mapConfig = {
            version: '1.7.0',
            layers: [
                {
                    type: 'cartodb',
                    options: {
                        source: {
                            id: 'HEAD1'
                        },
                        cartocss: TestClient.CARTOCSS.POINTS,
                        cartocss_version: '2.3.0'
                    }
                }
            ],
            analyses: [
                {
                    id: "HEAD1",
                    type: "buffer",
                    params: {
                        source: {
                            id: 'HEAD2',
                            type: 'source',
                            params: {
                                query: 'select * from test_table_localhost_regular1'
                            }
                        },
                        radius: 50000
                    }
                }
            ]
        };
        const testClient = new TestClient(mapConfig, apikeyToken);

        testClient.getLayergroup(function (err, layergroupResult) {
            assert.ifError(err);

            assert.ok(layergroupResult.layergroupid);

            testClient.drain(done);
        });
    });

    it('should create and get a named map tile using a regular apikey token', function (done) {
        const apikeyToken = 'regular1';

        const template = {
            version: '0.0.1',
            name: 'auth-api-template',
            placeholders: {
                buffersize: {
                    type: 'number',
                    default: 0
                }
            },
            layergroup: {
                version: '1.7.0',
                layers: [{
                    type: 'cartodb',
                    options: {
                        sql: 'select * from test_table_localhost_regular1',
                        cartocss: TestClient.CARTOCSS.POINTS,
                        cartocss_version: '2.3.0',
                    }
                }]
            }
        };

        const testClient = new TestClient(template, apikeyToken);

        testClient.getTile(0, 0, 0, function (err, res, tile) {
            assert.ifError(err);

            assert.equal(res.statusCode, 200);
            assert.ok(tile instanceof mapnik.Image);

            testClient.drain(done);
        });
    });

    it('should fail creating a named map using a regular apikey token and a private table', function (done) {
        const apikeyToken = 'regular1';

        const template = {
            version: '0.0.1',
            name: 'auth-api-template-private',
            placeholders: {
                buffersize: {
                    type: 'number',
                    default: 0
                }
            },
            layergroup: {
                version: '1.7.0',
                layers: [{
                    type: 'cartodb',
                    options: {
                        sql: 'select * from populated_places_simple_reduced_private',
                        cartocss: TestClient.CARTOCSS.POINTS,
                        cartocss_version: '2.3.0',
                    }
                }]
            }
        };

        const testClient = new TestClient(template, apikeyToken);

        const PERMISSION_DENIED_RESPONSE = {
            status: 403,
            headers: {
                'Content-Type': 'application/json; charset=utf-8'
            }
        };
        testClient.getTile(0, 0, 0, { response: PERMISSION_DENIED_RESPONSE }, function (err, res, body) {
            assert.ifError(err);

            assert.ok(body.hasOwnProperty('errors'));
            assert.equal(body.errors.length, 1);
            assert.ok(body.errors[0].match(/permission denied/), body.errors[0]);

            testClient.drain(done);
        });
    });
});
<commit_msg>add auth test for getting tiles<commit_after>require('../../support/test_helper');

const assert = require('../../support/assert');
const TestClient = require('../../support/test-client');
const  mapnik = require('windshaft').mapnik;

const PERMISSION_DENIED_RESPONSE = {
    status: 403,
    headers: {
        'Content-Type': 'application/json; charset=utf-8'
    }
};

describe('authorization', function() {
    it('should create a layergroup with regular apikey token', function(done) {
        const apikeyToken = 'regular1';
        const mapConfig = {
            version: '1.7.0',
            layers: [
                {
                    options: {
                        sql: 'select * FROM test_table_localhost_regular1',
                        cartocss: TestClient.CARTOCSS.POINTS,
                        cartocss_version: '2.3.0'
                    }
                }
            ]
        };
        const testClient = new TestClient(mapConfig, apikeyToken);

        testClient.getLayergroup(function (err, layergroupResult) {
            assert.ifError(err);

            assert.ok(layergroupResult.layergroupid);

            testClient.drain(done);
        });
    });

    it('should create and get a named map tile using a regular apikey token', function (done) {
        const apikeyToken = 'regular1';
        const mapConfig = {
            version: '1.7.0',
            layers: [
                {
                    options: {
                        sql: 'select * FROM test_table_localhost_regular1',
                        cartocss: TestClient.CARTOCSS.POINTS,
                        cartocss_version: '2.3.0'
                    }
                }
            ]
        };
        const testClient = new TestClient(mapConfig, apikeyToken);

        testClient.getTile(0, 0, 0, function (err, res, tile) {
            assert.ifError(err);

            assert.equal(res.statusCode, 200);
            assert.ok(tile instanceof mapnik.Image);

            testClient.drain(done);
        });
    });

    it('should fail getting a named map tile with default apikey token', function (done) {
        const apikeyTokenCreate = 'regular1';
        const apikeyTokenGet = 'default_public';
        const mapConfig = {
            version: '1.7.0',
            layers: [
                {
                    options: {
                        sql: 'select * FROM test_table_localhost_regular1',
                        cartocss: TestClient.CARTOCSS.POINTS,
                        cartocss_version: '2.3.0'
                    }
                }
            ]
        };

        const testClientCreate = new TestClient(mapConfig, apikeyTokenCreate);
        testClientCreate.getLayergroup(function (err, layergroupResult) {
            assert.ifError(err);
            const layergroupId = layergroupResult.layergroupid;

            const testClientGet = new TestClient({}, apikeyTokenGet);

            testClientGet.getTile(0, 0, 0, { layergroupid: layergroupId, response: PERMISSION_DENIED_RESPONSE}, function(err, res, body) {

                assert.ifError(err);

                assert.ok(body.hasOwnProperty('errors'));
                assert.equal(body.errors.length, 1);
                assert.ok(body.errors[0].match(/permission denied/), body.errors[0]);

                testClientGet.drain(done);
            });
        });
    });

    it('should fail creating a layergroup with default apikey token', function (done) {
        const apikeyToken = 'default_public';
        const mapConfig = {
            version: '1.7.0',
            layers: [
                {
                    options: {
                        sql: 'select * FROM test_table_localhost_regular1',
                        cartocss: TestClient.CARTOCSS.POINTS,
                        cartocss_version: '2.3.0'
                    }
                }
            ]
        };
        const testClient = new TestClient(mapConfig, apikeyToken);

        testClient.getLayergroup({ response: { status: 403 } }, function (err, layergroupResult) {
            assert.ifError(err);

            assert.ok(layergroupResult.hasOwnProperty('errors'));
            assert.equal(layergroupResult.errors.length, 1);
            assert.ok(layergroupResult.errors[0].match(/permission denied/), layergroupResult.errors[0]);

            testClient.drain(done);
        });
    });

    it('should create a layergroup with default apikey token', function (done) {
        const apikeyToken = 'default_public';
        const mapConfig = {
            version: '1.7.0',
            layers: [
                {
                    options: {
                        sql: 'select * FROM test_table',
                        cartocss: TestClient.CARTOCSS.POINTS,
                        cartocss_version: '2.3.0'
                    }
                }
            ]
        };
        const testClient = new TestClient(mapConfig, apikeyToken);

        testClient.getLayergroup(function (err, layergroupResult) {
            assert.ifError(err);

            assert.ok(layergroupResult.layergroupid);

            testClient.drain(done);
        });
    });

    it('should create and get a tile with default apikey token', function (done) {
        const apikeyToken = 'default_public';
        const mapConfig = {
            version: '1.7.0',
            layers: [
                {
                    options: {
                        sql: 'select * FROM test_table',
                        cartocss: TestClient.CARTOCSS.POINTS,
                        cartocss_version: '2.3.0'
                    }
                }
            ]
        };
        const testClient = new TestClient(mapConfig, apikeyToken);

        testClient.getTile(0, 0, 0, function (err, res, tile) {
            assert.ifError(err);

            assert.equal(res.statusCode, 200);
            assert.ok(tile instanceof mapnik.Image);

            testClient.drain(done);
        });
    });

    it('should fail if apikey does not grant access to table', function (done) {
        const mapConfig = {
            version: '1.7.0',
            layers: [
                {
                    options: {
                        sql: 'select * FROM test_table_localhost_regular1',
                        cartocss: TestClient.CARTOCSS.POINTS,
                        cartocss_version: '2.3.0'
                    }
                }
            ]
        };
        const testClient = new TestClient(mapConfig); //no apikey provided, using default

        testClient.getLayergroup({ response: { status: 403 } }, function (err, layergroupResult) { //TODO 401
            assert.ifError(err);

            assert.ok(layergroupResult.hasOwnProperty('errors'));
            assert.equal(layergroupResult.errors.length, 1);
            assert.ok(layergroupResult.errors[0].match(/permission denied/), layergroupResult.errors[0]);

            testClient.drain(done);
        });
    });

    it('should forbide access to API if API key does not grant access', function (done) {
        const apikeyToken = 'regular2';
        const mapConfig = {
            version: '1.7.0',
            layers: [
                {
                    options: {
                        sql: 'select * FROM test_table_localhost_regular1',
                        cartocss: TestClient.CARTOCSS.POINTS,
                        cartocss_version: '2.3.0'
                    }
                }
            ]
        };
        const testClient = new TestClient(mapConfig, apikeyToken);

        testClient.getLayergroup({ response: { status: 403 } }, function (err, layergroupResult) {
            assert.ifError(err);

            assert.ok(layergroupResult.hasOwnProperty('errors'));
            assert.equal(layergroupResult.errors.length, 1);
            assert.ok(layergroupResult.errors[0].match(/Forbidden/), layergroupResult.errors[0]);

            testClient.drain(done);
        });
    });

    it('should create a layergroup with a source analysis using a default apikey token', function (done) {
        const apikeyToken = 'default_public';
        const mapConfig = {
            version: '1.7.0',
            layers: [
                {
                    type: 'cartodb',
                    options: {
                        source: {
                            id: 'HEAD'
                        },
                        cartocss: TestClient.CARTOCSS.POINTS,
                        cartocss_version: '2.3.0'
                    }
                }
            ],
            analyses: [
                {
                    id: 'HEAD',
                    type: 'source',
                    params: {
                        query: 'select * from populated_places_simple_reduced'
                    }
                }
            ]
        };
        const testClient = new TestClient(mapConfig, apikeyToken);

        testClient.getLayergroup(function (err, layergroupResult) {
            assert.ifError(err);

            assert.ok(layergroupResult.layergroupid);

            testClient.drain(done);
        });
    });

    it('should create a layergroup with a source analysis using a regular apikey token', function (done) {
        const apikeyToken = 'regular1';
        const mapConfig = {
            version: '1.7.0',
            layers: [
                {
                    type: 'cartodb',
                    options: {
                        source: {
                            id: 'HEAD'
                        },
                        cartocss: TestClient.CARTOCSS.POINTS,
                        cartocss_version: '2.3.0'
                    }
                }
            ],
            analyses: [
                {
                    id: 'HEAD',
                    type: 'source',
                    params: {
                        query: 'select * from test_table_localhost_regular1'
                    }
                }
            ]
        };
        const testClient = new TestClient(mapConfig, apikeyToken);

        testClient.getLayergroup(function (err, layergroupResult) {
            assert.ifError(err);

            assert.ok(layergroupResult.layergroupid);

            testClient.drain(done);
        });
    });

    // Warning: TBA
    it('should create a layergroup with a buffer analysis using a regular apikey token', function (done) {
        const apikeyToken = 'regular1';
        const mapConfig = {
            version: '1.7.0',
            layers: [
                {
                    type: 'cartodb',
                    options: {
                        source: {
                            id: 'HEAD1'
                        },
                        cartocss: TestClient.CARTOCSS.POINTS,
                        cartocss_version: '2.3.0'
                    }
                }
            ],
            analyses: [
                {
                    id: "HEAD1",
                    type: "buffer",
                    params: {
                        source: {
                            id: 'HEAD2',
                            type: 'source',
                            params: {
                                query: 'select * from test_table_localhost_regular1'
                            }
                        },
                        radius: 50000
                    }
                }
            ]
        };
        const testClient = new TestClient(mapConfig, apikeyToken);

        testClient.getLayergroup(function (err, layergroupResult) {
            assert.ifError(err);

            assert.ok(layergroupResult.layergroupid);

            testClient.drain(done);
        });
    });

    it('should create and get a named map tile using a regular apikey token', function (done) {
        const apikeyToken = 'regular1';

        const template = {
            version: '0.0.1',
            name: 'auth-api-template',
            placeholders: {
                buffersize: {
                    type: 'number',
                    default: 0
                }
            },
            layergroup: {
                version: '1.7.0',
                layers: [{
                    type: 'cartodb',
                    options: {
                        sql: 'select * from test_table_localhost_regular1',
                        cartocss: TestClient.CARTOCSS.POINTS,
                        cartocss_version: '2.3.0',
                    }
                }]
            }
        };

        const testClient = new TestClient(template, apikeyToken);

        testClient.getTile(0, 0, 0, function (err, res, tile) {
            assert.ifError(err);

            assert.equal(res.statusCode, 200);
            assert.ok(tile instanceof mapnik.Image);

            testClient.drain(done);
        });
    });

    it('should fail creating a named map using a regular apikey token and a private table', function (done) {
        const apikeyToken = 'regular1';

        const template = {
            version: '0.0.1',
            name: 'auth-api-template-private',
            placeholders: {
                buffersize: {
                    type: 'number',
                    default: 0
                }
            },
            layergroup: {
                version: '1.7.0',
                layers: [{
                    type: 'cartodb',
                    options: {
                        sql: 'select * from populated_places_simple_reduced_private',
                        cartocss: TestClient.CARTOCSS.POINTS,
                        cartocss_version: '2.3.0',
                    }
                }]
            }
        };

        const testClient = new TestClient(template, apikeyToken);

        testClient.getTile(0, 0, 0, { response: PERMISSION_DENIED_RESPONSE }, function (err, res, body) {
            assert.ifError(err);

            assert.ok(body.hasOwnProperty('errors'));
            assert.equal(body.errors.length, 1);
            assert.ok(body.errors[0].match(/permission denied/), body.errors[0]);

            testClient.drain(done);
        });
    });
});
<|endoftext|>


<commit_before>/*!
 * jStorage
 * https://github.com/flowertwig-org/jStorage
 *
 * Copyright 2013 Mattias Blomqvist and other contributors
 * Released under the MIT license
 * https://github.com/flowertwig-org/jStorage/blob/master/LICENSE
 *
 * Date: 21:09 2013-12-30
 */
(function (window, undefined) {
    var rootjStorage;
    var jStorage = function (storageName, callback) {
        // The jStorage object is actually just the init constructor 'enhanced'
        return new jStorage.fn.init(storageName, callback, rootjStorage);
    };
    jStorage.fn = jStorage.prototype = {
        init: function (config) {
            this._provider = false;

            // TODO: do a config and config.name check..

            if (jStorage.providers[config.name]) {
                var provider = jStorage.providers[config.name];
                this._provider = provider;
                provider.init(this, config);
            } else {
                //console.log('Storage provider ' + config.name + ' was not loaded.');
                throw 'Storage provider ' + config.name + ' was not loaded.';
            }
        },
        get: function (name, callback) {
            if (this._provider) {
                this._provider.get(name, callback);
            }
        },
        set: function(name, content, callback) {
            if (this._provider) {
                this._provider.set(name, content, callback);
            }
        },
        del: function (name, callback) {
            if (this._provider) {
                this._provider.del(name, callback);
            }
        },
        list: function (name, callback) {
            if (this._provider) {
                this._provider.list(name, callback);
            }
        },
        exists: function (name, callback) {
            //console.log('exists');
            if (this._provider) {
                this._provider.exists();
            }
        }
    };

    jStorage.providers = jStorage.prototype = {

    };

    // Give the init function the jStorage prototype for later instantiation
    jStorage.fn.init.prototype = jStorage.fn;

    rootjStorage = jStorage;
    window.jStorage = jStorage;

}(window));<commit_msg>Fixed some confusing missmatch between init functions<commit_after>/*!
 * jStorage
 * https://github.com/flowertwig-org/jStorage
 *
 * Copyright 2013 Mattias Blomqvist and other contributors
 * Released under the MIT license
 * https://github.com/flowertwig-org/jStorage/blob/master/LICENSE
 *
 * Date: 21:09 2013-12-30
 */
(function (window, undefined) {
    var rootjStorage;
    var jStorage = function (config) {
        // The jStorage object is actually just the init constructor 'enhanced'
        return new jStorage.fn.init(config);
    };
    jStorage.fn = jStorage.prototype = {
        init: function (config) {
            this._provider = false;

            // TODO: do a config and config.name check..

            if (jStorage.providers[config.name]) {
                var provider = jStorage.providers[config.name];
                this._provider = provider;
                provider.init(this, config);
            } else {
                //console.log('Storage provider ' + config.name + ' was not loaded.');
                throw 'Storage provider ' + config.name + ' was not loaded.';
            }
        },
        get: function (name, callback) {
            if (this._provider) {
                this._provider.get(name, callback);
            }
        },
        set: function(name, content, callback) {
            if (this._provider) {
                this._provider.set(name, content, callback);
            }
        },
        del: function (name, callback) {
            if (this._provider) {
                this._provider.del(name, callback);
            }
        },
        list: function (name, callback) {
            if (this._provider) {
                this._provider.list(name, callback);
            }
        },
        exists: function (name, callback) {
            //console.log('exists');
            if (this._provider) {
                this._provider.exists();
            }
        }
    };

    jStorage.providers = jStorage.prototype = {

    };

    // Give the init function the jStorage prototype for later instantiation
    jStorage.fn.init.prototype = jStorage.fn;

    rootjStorage = jStorage;
    window.jStorage = jStorage;

}(window));<|endoftext|>


<commit_before>(function() {
  // returns how much "power" a user's votes have
  var getVotePower = function(user){
    return (user && user.isAdmin) ? 5 : 1;
  };

  var modifyKarma = function(user_id, karma){
    Meteor.users.update({_id: user_id}, {$inc: {karma: karma}});
  };

  var hasUpvotedItem= function(user, collection, id){
    // see http://www.mongodb.org/display/DOCS/MongoDB+Data+Modeling+and+Rails
    // 'is there an item with this id which contains this userId in its upvoters?'
    // if such an item  exists, it means we have voted.
    return collection.findOne({_id: id, upvoters: user._id}) !== undefined;
  }

  var hasDownvotedItem= function(user, collection, id){
    return collection.findOne({_id: id, downvoters: user._id}) !== undefined;
  }

  var upvote = function(collection, id, user) {
    // if no user is specified, use current user by default
    var user = (typeof user === 'undefined') ? Meteor.user() : user;

    if (!user || !canUpvote(user, collection, true)  || hasUpvotedItem(user, collection, id))
      return false;

    var votePower=getVotePower(user);
    var votedItem = collection.findOne(id);

    // Votes & Score
    collection.update({_id: id},{
      $addToSet: {upvoters: user._id},
      $inc: {votes: 1, baseScore: votePower},
      $set: {inactive: false}
    });
    if(!this.isSimulation)
      updateScore(collection, id, true);

    // Karma
    // user's posts and comments do not impact his own karma:
    if (votedItem.user_id != user._id) {
      modifyKarma(votedItem.user_id, votePower);
    }

    return true;
  };

  var downvote = function(collection, id, user) {
    // if no user is specified, use current user by default
    var user = (typeof user === 'undefined') ? Meteor.user() : user;

    if (! user || !canDownvote(user, collection, 'redirect') || hasDownvotedItem(user, collection, id))
      return false;

    var votePower=getVotePower(user);
    var votedItem = collection.findOne(id);

    // Votes & Score
    collection.update({_id: id},{
      $addToSet: {downvoters: user._id},
      $inc: {votes: -1, baseScore: -votePower},
      $set: {inactive: false}
    });
    if(!this.isSimulation)
      updateScore(collection, id, true);

    // Karma
    // user's posts and comments do not impact his own karma:
    if (votedItem.user_id != user._id) {
      modifyKarma(votedItem.user_id, -votePower);
    }

    return true;
  };

  var cancelUpvote = function(collection, id, user) {
    // if no user is specified, use current user by default
    var user = (typeof user === 'undefined') ? Meteor.user() : user;

    if (! user || !canUpvote(user, collection, 'redirect') || ! hasUpvotedItem(user, collection, id))
      return false

    var votePower=getVotePower(user);
    var votedItem = collection.findOne(id);

    // Votes & Score
    collection.update({_id: id},{
      $pull: {upvoters: user._id},
      $inc: {votes: -1, baseScore: -votePower},
      $set: {inactive: false}
    });
    if(!this.isSimulation)
      updateScore(collection, id, true);

    // Karma
    // user's posts and comments do not impact his own karma:
    if (votedItem.user_id != user._id) {
      modifyKarma(votedItem.user_id, -votePower);
    }

    return true;
  };

  var cancelDownvote = function(collection, id, user) {
    // if no user is specified, use current user by default
    var user = (typeof user === 'undefined') ? Meteor.user() : user;

    if (! user || !canDownvote(user, collection, 'redirect') || ! hasDownvotedItem(user, collection, id))
      return false

    var votePower=getVotePower(user);
    var votedItem = collection.findOne(id);

    // Votes & Score
    collection.update({_id: id},{
      $pull: {downvoters: user._id},
      $inc: {votes: 1, baseScore: votePower},
      $set: {inactive: false}
    });
    if(!this.isSimulation)
      updateScore(collection, id, true);

    // Karma
    // user's posts and comments do not impact his own karma:
    if (votedItem.user_id != user._id) {
      modifyKarma(votedItem.user_id, votePower);
    }

    return true;
  };

  // TO-DO: refactor this

  Meteor.methods({
    upvotePost: function(postId, user){
      // if no user is specified, use current user by default
      var user = (typeof user === 'undefined') ? Meteor.user() : user;
      return upvote.call(this, Posts, postId, user);
    },
    downvotePost: function(postId, user){
      // if no user is specified, use current user by default
      var user = (typeof user === 'undefined') ? Meteor.user() : user;
      return downvote.call(this, Posts, postId, user);
    },
    cancelUpvotePost: function(postId, user){
      // if no user is specified, use current user by default
      var user = (typeof user === 'undefined') ? Meteor.user() : user;
      return cancelUpvote.call(this, Posts, postId, user);
    },
    cancelDownvotePost: function(postId, user){
      // if no user is specified, use current user by default
      var user = (typeof user === 'undefined') ? Meteor.user() : user;
      return cancelDownvote.call(this, Posts, postId, user);
    },

    upvoteComment: function(commentId, user){
      // if no user is specified, use current user by default
      var user = (typeof user === 'undefined') ? Meteor.user() : user;
      return upvote.call(this, Comments, commentId, user);
    },
    downvoteComment: function(commentId, user){
      // if no user is specified, use current user by default
      var user = (typeof user === 'undefined') ? Meteor.user() : user;
      return downvote.call(this, Comments, commentId, user);
    },
    cancelUpvoteComment: function(commentId, user){
      // if no user is specified, use current user by default
      var user = (typeof user === 'undefined') ? Meteor.user() : user;
      return cancelUpvote.call(this, Comments, commentId, user);
    },
    cancelDownvoteComment: function(commentId, user){
      // if no user is specified, use current user by default
      var user = (typeof user === 'undefined') ? Meteor.user() : user;
      return cancelDownvote.call(this, Comments, commentId, user);
    }
  });

})();<commit_msg>fixed vote security issue<commit_after>(function() {
  // returns how much "power" a user's votes have
  var getVotePower = function(user){
    return (user && user.isAdmin) ? 5 : 1;
  };

  var modifyKarma = function(user_id, karma){
    Meteor.users.update({_id: user_id}, {$inc: {karma: karma}});
  };

  var hasUpvotedItem= function(user, collection, id){
    // see http://www.mongodb.org/display/DOCS/MongoDB+Data+Modeling+and+Rails
    // 'is there an item with this id which contains this userId in its upvoters?'
    // if such an item  exists, it means we have voted.
    return collection.findOne({_id: id, upvoters: user._id}) !== undefined;
  }

  var hasDownvotedItem= function(user, collection, id){
    return collection.findOne({_id: id, downvoters: user._id}) !== undefined;
  }

  var upvote = function(collection, id, user) {
    // if no user is specified, use current user by default
    var user = (typeof user === 'undefined') ? Meteor.user() : user;

    if (!user || !canUpvote(user, collection, true)  || hasUpvotedItem(user, collection, id))
      return false;

    var votePower=getVotePower(user);
    var votedItem = collection.findOne(id);

    // Votes & Score
    collection.update({_id: id},{
      $addToSet: {upvoters: user._id},
      $inc: {votes: 1, baseScore: votePower},
      $set: {inactive: false}
    });
    if(!this.isSimulation)
      updateScore(collection, id, true);

    // Karma
    // user's posts and comments do not impact his own karma:
    if (votedItem.user_id != user._id) {
      modifyKarma(votedItem.user_id, votePower);
    }

    return true;
  };

  var downvote = function(collection, id, user) {
    // if no user is specified, use current user by default
    var user = (typeof user === 'undefined') ? Meteor.user() : user;

    if (! user || !canDownvote(user, collection, 'redirect') || hasDownvotedItem(user, collection, id))
      return false;

    var votePower=getVotePower(user);
    var votedItem = collection.findOne(id);

    // Votes & Score
    collection.update({_id: id},{
      $addToSet: {downvoters: user._id},
      $inc: {votes: -1, baseScore: -votePower},
      $set: {inactive: false}
    });
    if(!this.isSimulation)
      updateScore(collection, id, true);

    // Karma
    // user's posts and comments do not impact his own karma:
    if (votedItem.user_id != user._id) {
      modifyKarma(votedItem.user_id, -votePower);
    }

    return true;
  };

  var cancelUpvote = function(collection, id, user) {
    // if no user is specified, use current user by default
    var user = (typeof user === 'undefined') ? Meteor.user() : user;

    if (! user || !canUpvote(user, collection, 'redirect') || ! hasUpvotedItem(user, collection, id))
      return false

    var votePower=getVotePower(user);
    var votedItem = collection.findOne(id);

    // Votes & Score
    collection.update({_id: id},{
      $pull: {upvoters: user._id},
      $inc: {votes: -1, baseScore: -votePower},
      $set: {inactive: false}
    });
    if(!this.isSimulation)
      updateScore(collection, id, true);

    // Karma
    // user's posts and comments do not impact his own karma:
    if (votedItem.user_id != user._id) {
      modifyKarma(votedItem.user_id, -votePower);
    }

    return true;
  };

  var cancelDownvote = function(collection, id, user) {
    // if no user is specified, use current user by default
    var user = (typeof user === 'undefined') ? Meteor.user() : user;

    if (! user || !canDownvote(user, collection, 'redirect') || ! hasDownvotedItem(user, collection, id))
      return false

    var votePower=getVotePower(user);
    var votedItem = collection.findOne(id);

    // Votes & Score
    collection.update({_id: id},{
      $pull: {downvoters: user._id},
      $inc: {votes: 1, baseScore: votePower},
      $set: {inactive: false}
    });
    if(!this.isSimulation)
      updateScore(collection, id, true);

    // Karma
    // user's posts and comments do not impact his own karma:
    if (votedItem.user_id != user._id) {
      modifyKarma(votedItem.user_id, votePower);
    }

    return true;
  };

  var getUser = function(user){
    // only let admins specify different users for voting
    // if no user is specified, use current user by default
    return (isAdmin(Meteor.user()) && typeof user !== 'undefined') ? user : Meteor.user();
  }

  Meteor.methods({
    upvotePost: function(postId, user){
      var user = getUser(user);
      return upvote.call(this, Posts, postId, user);
    },
    downvotePost: function(postId, user){
      var user = getUser(user);
      return downvote.call(this, Posts, postId, user);
    },
    cancelUpvotePost: function(postId, user){
      var user = getUser(user);
      return cancelUpvote.call(this, Posts, postId, user);
    },
    cancelDownvotePost: function(postId, user){
      var user = getUser(user);
      return cancelDownvote.call(this, Posts, postId, user);
    },

    upvoteComment: function(commentId, user){
      var user = getUser(user);
      return upvote.call(this, Comments, commentId, user);
    },
    downvoteComment: function(commentId, user){
      var user = getUser(user);
      return downvote.call(this, Comments, commentId, user);
    },
    cancelUpvoteComment: function(commentId, user){
      var user = getUser(user);
      return cancelUpvote.call(this, Comments, commentId, user);
    },
    cancelDownvoteComment: function(commentId, user){
      var user = getUser(user);
      return cancelDownvote.call(this, Comments, commentId, user);
    }
  });

})();<|endoftext|>


<commit_before>(function(root, factory) {
    if(typeof define === 'function' && define.amd) {
        // AMD

        define(['underscore', 'backbone', 'exports'], function(_, Backbone, exports) {
            // Export global even in AMD case in case this script is loaded with
            // others that may still expect a global Backbone.
            root.Backbone = factory(root, exports, _, Backbone);
        });

    } else if(typeof exports !== 'undefined') {
        // for Node.js or CommonJS

        var _ = require('underscore'),
            Backbone = require('backbone');

        factory(root, exports, _, Backbone);
    } else {
        // as a browser global

        root.Backbone = factory(root, {}, root._, root.Backbone);
    }

}(this, function(root, exports, _, BackboneBase) {

    var Backbone = _.extend({}, BackboneBase);

    // Helper function calculating an object containing only those attributes from newAttrs
    // that have different values compared to originalAttrs
    var getChangedAttrs = function(originalAttrs, newAttrs) {
        var result = {};
        for(var key in newAttrs) {
            if(!_.isEqual(originalAttrs[key], newAttrs[key])) {
                result[key] = newAttrs[key];
            }
        }
        return result;
    };

    Backbone.Model = BackboneBase.Model.extend({

        // Attributes listed in this array will always be included in the
        // model's partial JSON representation, also if they have not been
        // changed since the latest sync
        partialAttributesCore: [ 'id' ],

        // Symmetric to Backbone's `model.changedAttributes()`,
        // except that this returns a hash of the attributes that have
        // changed since the last sync, or `false` if there are none.
        // Like `changedAttributes`, an external attributes hash can be
        // passed in, returning the attributes in that hash which differ
        // from the model's attributes at the time of the last sync.
        unsavedAttributes: function(attrs) {
            attrs = attrs || this.attributes;
            if(!this._syncedAttributes) return attrs;

            return getChangedAttrs(this._syncedAttributes, attrs);
        },

        // Has the model changes, that have not been synced to the server?
        hasUnsavedChanges: function() {
            return !_.isEmpty(this.unsavedAttributes());
        },

        // Override #save to make sure that only the partial JSON representation is submitted
        // and that the server response to POST and PUT requests is always parsed to contain
        // only attributes that have been changed on the server
        save: function(key, val, options) {
            var model = this;
            var attrs;

            // Handle both `"key", value` and `{key: value}` -style arguments.
            if(key === null || typeof key === 'object') {
                attrs = key;
                options = val;
            } else {
                (attrs = {})[key] = val;
            }

            options || (options = {});

            // If there are no unsaved changes, do not dispatch a request,
            // but immediately call the `success` callback
            if(!this.isNew() && !this.hasUnsavedChanges(options)) {
                if(options.success) options.success({});
                return;
            }

            if(options.partial === void 0) options.partial = true;
            if(options.partial) options.partialBaseline = _.clone(this.attributes);

            // This callback will be executed after the attributes from the response
            // have been set to the model, before `sync` is triggered
            var success = options.success;
            options.success = function(resp) {
                // Reset synced attributes again, so that changes coming from the server are
                // considered to be saved.
                model._resetSyncedAttributes();
                if(success) {
                    success(resp);
                }
            };

            // In case of error set the synced attributes back to the values before the object
            // was pro-actively reset when the save request was dispatched.
            var error = options.error;
            options.error = function(resp) {
                // Only restore original value if no other concurrent save requests
                // have reset the synced attributes in the meantime.
                if(_.isEqual(model._syncedAttributes, options.partialBaseline)) {
                    model._syncedAttributes = previousSyncedAttributes;
                }
                if(error) {
                    error(resp);
                }
            };

            var result = BackboneBase.Model.prototype.save.call(this, attrs, options);

            // Pro-actively reset _syncedAttributes so that concurrent saves will only go through
            // if attributes have actually been changed
            var previousSyncedAttributes = this._syncedAttributes;
            this._resetSyncedAttributes();

            return result;
        },

        // Override #fetch to set the `partial` flag in the options
        fetch: function(options) {
            var model = this;

            options || (options = {});
            //if(options.partial === void 0) options.partial = !options.reset;

            // This callback will be executed after the attributes from the response
            // have been set to the model, before `sync` is triggered
            var success = options.success;
            options.success = function(resp) {

                model._resetSyncedAttributes();

                if(success) {
                    success(resp);
                }

            };

            return BackboneBase.Model.prototype.fetch.call(this, options);
        },

        // Override #set to add support for `clear` option. If `clear` is set to true,
        // the current attributes hash will be cleared before the new attributes passed
        // in the arguments are set.
        set: function(key, val, options) {
            var keyDiff, i, l, result;

            if(key === null) return this;

            // Handle both `"key", value` and `{key: value}` -style arguments.
            if(typeof key === 'object') {
                attrs = key;
                options = val;
            } else {
                (attrs = {})[key] = val;
            }

            options || (options = {});

            // If `clear` is set, we first set all attrs to be cleared to undefined,
            // thus letting Backbone's native #set trigger all appropriate attr change events.
            if(options.clear) {
                keyDiff = _.difference(_.keys(this.attributes), _.keys(attrs));
                for(i=0, l=keyDiff.length; i<l; ++i) {
                    attrs[keyDiff[i]] = void 0;
                }
            }

            result = BackboneBase.Model.prototype.set.apply(this, [attrs, options]);

            // Make sure to properly delete the attributes
            if(result && options.clear) {
                for(i=0, l=keyDiff.length; i<l; ++i) {
                    delete this.attributes[keyDiff[i]];
                }
            }

            return result;
        },

        // If a snapshot of the attributes hash is passed in option `partialBaseline`,
        // this will return only those attributes from the parsed response, which have
        // changed in comparison to the snapshop baseline
        parse: function(resp, options) {
            var result = BackboneBase.Model.prototype.parse.apply(this, arguments);

            options || (options = {});
            if(options.partial && options.partialBaseline) {
                result = getChangedAttrs(
                    options.partialBaseline,
                    result
                );

                // important: remove the partial baseline after the first parse,
                // otherwise nested constructor calls, might use a wrong baseline in their
                // parse() invocations
                delete options.partialBaseline;
            }

            return result;
        },

        // Override to add support for the `partial` option:
        // When partial is set to true, the JSON will contain only the unsaved
        // attributes in addition to the ID attribute
        toJSON: function(options) {
            var attributes, result, id;

            options || (options = {});
            if(options.partial) {
                id = this.get(this.idAttribute || "id");
                attributes = this.attributes;

                // Calculate the partial JSON representation
                // and make sure the core attributes are included
                this.attributes = _.extend(
                    this.unsavedAttributes(),
                    _.pick(attributes, this.partialAttributesCore)
                );
                // Addtionally ensure the id attribute is included
                this.attributes[this.idAttribute || "id"] = id;
                result = BackboneBase.Model.prototype.toJSON.apply(this, arguments);

                // Restore attributes
                this.attributes = attributes;
            } else {
                result = BackboneBase.Model.prototype.toJSON.apply(this, arguments);
            }
            return result;
        },

        // Internal method that is called directly after attributes have been set from a server response
        _resetSyncedAttributes: function() {
            this._syncedAttributes = _.clone(this.attributes);
        }

    });

    _.extend(exports, Backbone);
    return Backbone;

}));
<commit_msg>fixin partialput implementation<commit_after>(function(root, factory) {
    if(typeof define === 'function' && define.amd) {
        // AMD

        define(['underscore', 'backbone', 'exports'], function(_, Backbone, exports) {
            // Export global even in AMD case in case this script is loaded with
            // others that may still expect a global Backbone.
            root.Backbone = factory(root, exports, _, Backbone);
        });

    } else if(typeof exports !== 'undefined') {
        // for Node.js or CommonJS

        var _ = require('underscore'),
            Backbone = require('backbone');

        factory(root, exports, _, Backbone);
    } else {
        // as a browser global

        root.Backbone = factory(root, {}, root._, root.Backbone);
    }

}(this, function(root, exports, _, BackboneBase) {

    var Backbone = _.extend({}, BackboneBase);

    // Helper function calculating an object containing only those attributes from newAttrs
    // that have different values compared to originalAttrs
    var getChangedAttrs = function(originalAttrs, newAttrs) {
        var result = {};
        for(var key in newAttrs) {
            if(!_.isEqual(originalAttrs[key], newAttrs[key])) {
                result[key] = newAttrs[key];
            }
        }
        return result;
    };

    Backbone.Model = BackboneBase.Model.extend({

        // Attributes listed in this array will always be included in the
        // model's partial JSON representation, also if they have not been
        // changed since the latest sync
        partialAttributesCore: [ 'id' ],

        // Symmetric to Backbone's `model.changedAttributes()`,
        // except that this returns a hash of the attributes that have
        // changed since the last sync, or `false` if there are none.
        // Like `changedAttributes`, an external attributes hash can be
        // passed in, returning the attributes in that hash which differ
        // from the model's attributes at the time of the last sync.
        unsavedAttributes: function(attrs) {
            attrs = attrs || this.attributes;
            if(!this._syncedAttributes) return attrs;

            return getChangedAttrs(this._syncedAttributes, attrs);
        },

        // Has the model changes, that have not been synced to the server?
        hasUnsavedChanges: function() {
            return !_.isEmpty(this.unsavedAttributes());
        },

        // Override #save to make sure that only the partial JSON representation is submitted
        // and that the server response to POST and PUT requests is always parsed to contain
        // only attributes that have been changed on the server
        save: function(key, val, options) {
            var model = this;
            var attrs;

            // Handle both `"key", value` and `{key: value}` -style arguments.
            if(_.isUndefined(key) || _.isNull(key) || typeof key === 'object') {
                attrs = key;
                options = val;
            } else {
                (attrs = {})[key] = val;
            }

            options || (options = {});

            // If there are no unsaved changes, do not dispatch a request,
            // but immediately call the `success` callback
            if(!this.isNew() && !this.hasUnsavedChanges(options)) {
                if(options.success) options.success({});
                return;
            }

            if(options.partial === void 0) options.partial = true;
            if(options.partial) options.partialBaseline = _.clone(this.attributes);

            // This callback will be executed after the attributes from the response
            // have been set to the model, before `sync` is triggered
            var success = options.success;
            options.success = function(resp) {
                // Reset synced attributes again, so that changes coming from the server are
                // considered to be saved.
                model._resetSyncedAttributes();
                if(success) {
                    success(resp);
                }
            };

            // In case of error set the synced attributes back to the values before the object
            // was pro-actively reset when the save request was dispatched.
            var error = options.error;
            options.error = function(resp) {
                // Only restore original value if no other concurrent save requests
                // have reset the synced attributes in the meantime.
                if(_.isEqual(model._syncedAttributes, options.partialBaseline)) {
                    model._syncedAttributes = previousSyncedAttributes;
                }
                if(error) {
                    error(resp);
                }
            };

            var result = BackboneBase.Model.prototype.save.call(this, attrs, options);

            // Pro-actively reset _syncedAttributes so that concurrent saves will only go through
            // if attributes have actually been changed
            var previousSyncedAttributes = this._syncedAttributes;
            this._resetSyncedAttributes();

            return result;
        },

        // Override #fetch to set the `partial` flag in the options
        fetch: function(options) {
            var model = this;

            options || (options = {});
            //if(options.partial === void 0) options.partial = !options.reset;

            // This callback will be executed after the attributes from the response
            // have been set to the model, before `sync` is triggered
            var success = options.success;
            options.success = function(resp) {

                model._resetSyncedAttributes();

                if(success) {
                    success(resp);
                }

            };

            return BackboneBase.Model.prototype.fetch.call(this, options);
        },

        // Override #set to add support for `clear` option. If `clear` is set to true,
        // the current attributes hash will be cleared before the new attributes passed
        // in the arguments are set.
        set: function(key, val, options) {
            var keyDiff, i, l, result;

            if(key === null) return this;

            // Handle both `"key", value` and `{key: value}` -style arguments.
            if(typeof key === 'object') {
                attrs = key;
                options = val;
            } else {
                (attrs = {})[key] = val;
            }

            options || (options = {});

            // If `clear` is set, we first set all attrs to be cleared to undefined,
            // thus letting Backbone's native #set trigger all appropriate attr change events.
            if(options.clear) {
                keyDiff = _.difference(_.keys(this.attributes), _.keys(attrs));
                for(i=0, l=keyDiff.length; i<l; ++i) {
                    attrs[keyDiff[i]] = void 0;
                }
            }

            result = BackboneBase.Model.prototype.set.apply(this, [attrs, options]);

            // Make sure to properly delete the attributes
            if(result && options.clear) {
                for(i=0, l=keyDiff.length; i<l; ++i) {
                    delete this.attributes[keyDiff[i]];
                }
            }

            return result;
        },

        // If a snapshot of the attributes hash is passed in option `partialBaseline`,
        // this will return only those attributes from the parsed response, which have
        // changed in comparison to the snapshop baseline
        parse: function(resp, options) {
            var result = BackboneBase.Model.prototype.parse.apply(this, arguments);

            options || (options = {});
            if(options.partial && options.partialBaseline) {
                result = getChangedAttrs(
                    options.partialBaseline,
                    result
                );

                // important: remove the partial baseline after the first parse,
                // otherwise nested constructor calls, might use a wrong baseline in their
                // parse() invocations
                delete options.partialBaseline;
            }

            return result;
        },

        // Override to add support for the `partial` option:
        // When partial is set to true, the JSON will contain only the unsaved
        // attributes in addition to the ID attribute
        toJSON: function(options) {
            var attributes, result, id;

            options || (options = {});
            if(options.partial) {
                id = this.get(this.idAttribute || "id");
                attributes = this.attributes;

                // Calculate the partial JSON representation
                // and make sure the core attributes are included
                this.attributes = _.extend(
                    this.unsavedAttributes(),
                    _.pick(attributes, this.partialAttributesCore)
                );
                // Addtionally ensure the id attribute is included
                this.attributes[this.idAttribute || "id"] = id;
                result = BackboneBase.Model.prototype.toJSON.apply(this, arguments);

                // Restore attributes
                this.attributes = attributes;
            } else {
                result = BackboneBase.Model.prototype.toJSON.apply(this, arguments);
            }
            return result;
        },

        // Internal method that is called directly after attributes have been set from a server response
        _resetSyncedAttributes: function() {
            this._syncedAttributes = _.clone(this.attributes);
        }

    });

    _.extend(exports, Backbone);
    return Backbone;

}));
<|endoftext|>