<commit_before>var HIGHESTSTAT = 99;

// limits number inputs
function limit (num, minNumber) {
	"use strict";
	// check if value is outside of the limits
	if (!$.isNumeric(num.value) || num.value < minNumber) {
		num.value = minNumber;
	} else if (num.value > HIGHESTSTAT) {
		num.value = HIGHESTSTAT;
	} else if (Math.floor(num.value) !== num.value) {
		num.value = Math.floor(num.value);
	}
}

// put options in the stat selects
function setupStats () {
	"use strict";
	$(".stat-bonus").html("<option>0</option><option>2</option><option>3</option><option>4</option><option>5</option><option>6</option><option>7</option>");
	$(".stat-penalty").html("<option>0</option><option>-3</option><option>-4</option><option>-5</option><option>-6</option><option>-7</option>");
	$(".stat-spur").html("<option>0</option><option>2</option><option>3</option><option>4</option>");
}

// put options in the character selects
function setupChars() {
	"use strict";

	// stores all character options
	var options = "";

	// retrieve characters and add them to the list of options
	$.getJSON("http://localhost/feh/data/char.json", function(info) {
		for (var key in info) {
			if (info.hasOwnProperty(key)) {
				options += "<option>" + key + "</option>";
			}
		}

		// add to html
		$(".char-selector").html(options);
	});
}

// setup inital page
$(document).ready( function () {
	"use strict";
	// setup show/hide buttons
	$(".collapse-button").on("click", function() {
		// toggle a section
		$("#" + $(this).data("section")).toggle(700);
	});

	// setup number input changes
	$(".more-than-zero").on("change", function () {
		limit(this, 1);
	});
	$(".zero-or-more").on("change", function () {
		limit(this, 0);
	});

	// setup hp value updates
	$(".hp-stat").on("change", function() {
		// update hp value in rest of the page
		$("." + this.id + "-read").text(this.value);
	});
	$(".curr-hp-val").on("change", function () {
		// current hp cannot be greater than base hp
		var baseHP = $("#hp-" + $(this).data("charnum"))[0].value;
		if (this.value > baseHP) {
			this.value = baseHP;
		}
	});

	setupStats();
	setupChars();
});
<commit_msg>Changed json path<commit_after>var HIGHESTSTAT = 99;

// limits number inputs
function limit (num, minNumber) {
	"use strict";
	// check if value is outside of the limits
	if (!$.isNumeric(num.value) || num.value < minNumber) {
		num.value = minNumber;
	} else if (num.value > HIGHESTSTAT) {
		num.value = HIGHESTSTAT;
	} else if (Math.floor(num.value) !== num.value) {
		num.value = Math.floor(num.value);
	}
}

// put options in the stat selects
function setupStats () {
	"use strict";
	$(".stat-bonus").html("<option>0</option><option>2</option><option>3</option><option>4</option><option>5</option><option>6</option><option>7</option>");
	$(".stat-penalty").html("<option>0</option><option>-3</option><option>-4</option><option>-5</option><option>-6</option><option>-7</option>");
	$(".stat-spur").html("<option>0</option><option>2</option><option>3</option><option>4</option>");
}

// put options in the character selects
function setupChars() {
	"use strict";

	// stores all character options
	var options = "";

	// retrieve characters and add them to the list of options
	$.getJSON("https://rocketmo.github.io/feh-damage-calc/data/char.json", function(info) {
		for (var key in info) {
			if (info.hasOwnProperty(key)) {
				options += "<option>" + key + "</option>";
			}
		}

		// add to html
		$(".char-selector").html(options);
	});
}

// setup inital page
$(document).ready( function () {
	"use strict";
	// setup show/hide buttons
	$(".collapse-button").on("click", function() {
		// toggle a section
		$("#" + $(this).data("section")).toggle(700);
	});

	// setup number input changes
	$(".more-than-zero").on("change", function () {
		limit(this, 1);
	});
	$(".zero-or-more").on("change", function () {
		limit(this, 0);
	});

	// setup hp value updates
	$(".hp-stat").on("change", function() {
		// update hp value in rest of the page
		$("." + this.id + "-read").text(this.value);
	});
	$(".curr-hp-val").on("change", function () {
		// current hp cannot be greater than base hp
		var baseHP = $("#hp-" + $(this).data("charnum"))[0].value;
		if (this.value > baseHP) {
			this.value = baseHP;
		}
	});

	setupStats();
	setupChars();
});
<|endoftext|>


<commit_before>/**
 * This class describes the player's character.
 */
Player = function (theGame) {
    // Properties
    this.dashing,
    this.mDashTimer;
    this.mHealth;
    this.mFlickeringTimer;
    this.mTouchingFloor;
    this.mVelocity;
    this.mAcceleration;
    this.mDustEmitter;

    // Constructor
    Phaser.Sprite.call(this, theGame, theGame.width * 0.15, theGame.world.centerY + 10, 'player');
    this.init();
};

// Lovely pants-in-the-head javascript boilerplate for OOP.
Player.prototype = Object.create(Phaser.Sprite.prototype);
Player.prototype.constructor = Player;

// Public methods

Player.prototype.init = function() {
    this.animations.add('run', [0, 1, 2, 3, 4, 5], 10, true);
    this.animations.add('jump', [6, 7, 8, 8, 9, 10], 5, true);
    this.animations.add('dash', [11], 5, true);

    this.game.physics.enable(this, Phaser.Physics.ARCADE);
    this.body.collideWorldBounds = true;

    this.dashing = false;
    this.mDashTimer = 0;
    this.mTouchingFloor = false;
    this.mHealth = Constants.GAME_HEALTH_MAX;
    this.mVelocity = new Phaser.Point();
    this.mAcceleration = new Phaser.Point(0, 1);

    this.run();
};

Player.prototype.update = function() {
    this.x = this.game.width * Constants.PLAYER_POSITION_X;

    if(this.dashing) {
        this.mDashTimer -= this.game.time.elapsedMS;

        this.mDustEmitter.x = this.x + 40;
        this.mDustEmitter.y = this.y + this.body.height;

        if(this.mDashTimer <= 0) {
            this.dashing = false;
            this.run();
        }
    } else if(this.mDustEmitter.on) {
        this.mDustEmitter.on = false;
    }

    if(this.mFlickeringTimer > 0) {
        this.mFlickeringTimer -= this.game.time.elapsedMS;

        this.visible = !this.visible;

        if(this.mFlickeringTimer <= 0) {
            this.visible = true;
        }
    }
};

Player.prototype.adjustPosition = function(theCurrentFloor) {
    // Euler interpolation
    this.mVelocity.y += this.mAcceleration.y;
    this.mVelocity.y = this.mVelocity.y > 200 ? 200 : this.mVelocity.y;

    this.y += this.mVelocity.y;

    // Are we touching the floor?
    if(this.y + this.body.height > theCurrentFloor.y) {
        // Yep, we are. Is it a slope?
        this.mTouchingFloor = true;

        // Check it the floor is a slope
        if(theCurrentFloor.key == 'slope-up' || theCurrentFloor.key == 'slope-down') {
            // Yep, handle the up/down movement.
            this.processMoveOnSlope(theCurrentFloor);

        } else {
            // No, it's a flat platform.
    		this.y = theCurrentFloor.y - this.body.height;

            if(!this.dashing) {
                this.run();
            }
        }
    } else {
        this.mTouchingFloor = false;
    }
};

Player.prototype.processMoveOnSlope = function(theSlope) {
    var aScale;

    aScale = (this.x - theSlope.x) / theSlope.width;

    if(theSlope.key == 'slope-up') {
        if(aScale <= 0.5) {
            this.run();
            this.y = theSlope.y - (theSlope.height / 2 + aScale * theSlope.height * 0.7);
        } else {
            this.y = theSlope.y - this.body.height;
        }
    } else {
        if(aScale <= 0.6) {
            this.run();
            this.y = theSlope.y - theSlope.height / 2 - 40 + aScale * (theSlope.height / 2);
        } else {
            this.y = theSlope.y - 60;
        }
    }
};

Player.prototype.run = function() {
    if(this.animations.currentAnim.name != "run") {
        this.body.setSize(20, 120, 30, 0);
        this.anchor.set(0);
        this.angle = 0;
        this.animations.play('run');
    }
};

Player.prototype.jump = function() {
    if(this.mTouchingFloor ) {
        this.run(); // to prevent any dash rotation/animation
        this.mTouchingFloor = false;
        this.animations.play('jump');
        this.mVelocity.y = -15;
    }
};

Player.prototype.dash = function() {
    if(!this.dashing && this.mTouchingFloor) {
        this.dashing = true;
        this.animations.play('dash');
        this.body.setSize(100, 30, 0, 0);
        this.anchor.set(0.5);
        this.angle = -90;
        this.y += this.height;

        this.mDashTimer = 500;

        // Emit some dust \o/
        this.mDustEmitter.start(false, 1000, 50);
    }
};

Player.prototype.heal = function() {
    this.mHealth += Constants.GAME_CORRECT_HEALTH;

    // Prevent overfeeding health
    if(this.mHealth >= Constants.GAME_HEALTH_MAX) {
        this.mHealth = Constants.GAME_HEALTH_MAX;
    }
};

Player.prototype.hurt = function() {
    if(!this.isFlickering()) {
        this.mHealth -= Constants.GAME_MISTAKE_HEALTH;

        if(this.mHealth < 0) {
            this.mHealth = 0;
        }

        this.flicker(1000);
    }
};

Player.prototype.flicker = function(theDuration) {
    this.mFlickeringTimer = theDuration
};

Player.prototype.isFlickering = function() {
    return this.mFlickeringTimer > 0;
};

Player.prototype.getHealth = function() {
    return this.mHealth;
};

Player.prototype.getHealthPercentage = function() {
    return this.mHealth / Constants.GAME_HEALTH_MAX;
};

Player.prototype.setDustEmitter = function(theEmitter) {
    this.mDustEmitter = theEmitter;
};
<commit_msg>More dust!<commit_after>/**
 * This class describes the player's character.
 */
Player = function (theGame) {
    // Properties
    this.dashing,
    this.mDashTimer;
    this.mHealth;
    this.mFlickeringTimer;
    this.mTouchingFloor;
    this.mVelocity;
    this.mAcceleration;
    this.mDustEmitter;

    // Constructor
    Phaser.Sprite.call(this, theGame, theGame.width * 0.15, theGame.world.centerY + 10, 'player');
    this.init();
};

// Lovely pants-in-the-head javascript boilerplate for OOP.
Player.prototype = Object.create(Phaser.Sprite.prototype);
Player.prototype.constructor = Player;

// Public methods

Player.prototype.init = function() {
    this.animations.add('run', [0, 1, 2, 3, 4, 5], 10, true);
    this.animations.add('jump', [6, 7, 8, 8, 9, 10], 5, true);
    this.animations.add('dash', [11], 5, true);

    this.game.physics.enable(this, Phaser.Physics.ARCADE);
    this.body.collideWorldBounds = true;

    this.dashing = false;
    this.mDashTimer = 0;
    this.mTouchingFloor = false;
    this.mHealth = Constants.GAME_HEALTH_MAX;
    this.mVelocity = new Phaser.Point();
    this.mAcceleration = new Phaser.Point(0, 1);

    this.run();
};

Player.prototype.update = function() {
    this.x = this.game.width * Constants.PLAYER_POSITION_X;

    this.adjustDustEmitterPosition();

    if(this.dashing) {
        this.mDashTimer -= this.game.time.elapsedMS;

        if(this.mDashTimer <= 0) {
            this.dashing = false;
            this.mDustEmitter.on = false;
            this.run();
        }
    }

    if(this.mFlickeringTimer > 0) {
        this.mFlickeringTimer -= this.game.time.elapsedMS;

        this.visible = !this.visible;

        if(this.mFlickeringTimer <= 0) {
            this.visible = true;
        }
    }
};

Player.prototype.adjustDustEmitterPosition = function() {
    this.mDustEmitter.x = this.x + 40;
    this.mDustEmitter.y = this.y + this.body.height;
};

Player.prototype.adjustPosition = function(theCurrentFloor) {
    // Euler interpolation
    this.mVelocity.y += this.mAcceleration.y;
    this.mVelocity.y = this.mVelocity.y > 200 ? 200 : this.mVelocity.y;

    this.y += this.mVelocity.y;

    // Are we touching the floor?
    if(this.y + this.body.height > theCurrentFloor.y) {
        // Yep, we are. Is it a slope?
        if(this.mTouchingFloor == false) {
            // About to land from jump, let's emit some dust
            this.adjustDustEmitterPosition();
            this.mDustEmitter.start(false, 1000, 500, 2, 2);
        }
        this.mTouchingFloor = true;

        // Check it the floor is a slope
        if(theCurrentFloor.key == 'slope-up' || theCurrentFloor.key == 'slope-down') {
            // Yep, handle the up/down movement.
            this.processMoveOnSlope(theCurrentFloor);

        } else {
            // No, it's a flat platform.
    		this.y = theCurrentFloor.y - this.body.height;

            if(!this.dashing) {
                this.run();
            }
        }
    } else {
        this.mTouchingFloor = false;
    }
};

Player.prototype.processMoveOnSlope = function(theSlope) {
    var aScale;

    aScale = (this.x - theSlope.x) / theSlope.width;

    if(theSlope.key == 'slope-up') {
        if(aScale <= 0.5) {
            this.run();
            this.y = theSlope.y - (theSlope.height / 2 + aScale * theSlope.height * 0.7);
        } else {
            this.y = theSlope.y - this.body.height;
        }
    } else {
        if(aScale <= 0.6) {
            this.run();
            this.y = theSlope.y - theSlope.height / 2 - 40 + aScale * (theSlope.height / 2);
        } else {
            this.y = theSlope.y - 60;
        }
    }
};

Player.prototype.run = function() {
    if(this.animations.currentAnim.name != "run") {
        this.body.setSize(20, 120, 30, 0);
        this.anchor.set(0);
        this.angle = 0;
        this.animations.play('run');
    }
};

Player.prototype.jump = function() {
    if(this.mTouchingFloor) {
        this.run(); // to prevent any dash rotation/animation
        this.animations.play('jump');
        this.mVelocity.y = -15;
    }
};

Player.prototype.dash = function() {
    if(!this.dashing && this.mTouchingFloor) {
        this.dashing = true;
        this.animations.play('dash');
        this.body.setSize(100, 30, 0, 0);
        this.anchor.set(0.5);
        this.angle = -90;
        this.y += this.height;

        this.mDashTimer = 500;

        // Emit some dust \o/
        this.mDustEmitter.start(false, 1000, 50);
    }
};

Player.prototype.heal = function() {
    this.mHealth += Constants.GAME_CORRECT_HEALTH;

    // Prevent overfeeding health
    if(this.mHealth >= Constants.GAME_HEALTH_MAX) {
        this.mHealth = Constants.GAME_HEALTH_MAX;
    }
};

Player.prototype.hurt = function() {
    if(!this.isFlickering()) {
        this.mHealth -= Constants.GAME_MISTAKE_HEALTH;

        if(this.mHealth < 0) {
            this.mHealth = 0;
        }

        this.flicker(1000);
    }
};

Player.prototype.flicker = function(theDuration) {
    this.mFlickeringTimer = theDuration
};

Player.prototype.isFlickering = function() {
    return this.mFlickeringTimer > 0;
};

Player.prototype.getHealth = function() {
    return this.mHealth;
};

Player.prototype.getHealthPercentage = function() {
    return this.mHealth / Constants.GAME_HEALTH_MAX;
};

Player.prototype.setDustEmitter = function(theEmitter) {
    this.mDustEmitter = theEmitter;
    this.adjustDustEmitterPosition();
};
<|endoftext|>


<commit_before>/** @module paymentProviders/opencollective */

import collective from './collective';
import prepaid from './prepaid';
import giftcard from './giftcard';

/** Process orders from Open Collective payment method types */
async function processOrder(order) {
  switch (order.paymentMethod.type) {
  case 'prepaid': return prepaid.processOrder(order);
  case 'giftcard': return giftcard.processOrder(order);
  case 'collective':        // Fall through
  default: return collective.processOrder(order);
  }
}

/* API expected from a Payment Method provider */
export default {
  // payment method types
  // like cc, btc, prepaid, etc.
  types: {
    default: collective,
    collective,
    giftcard,
    prepaid,
  },
  processOrder,
};
<commit_msg>fix eslint<commit_after>/** @module paymentProviders/opencollective */

import * as collective from './collective';
import * as prepaid from './prepaid';
import * as giftcard from './giftcard';

/** Process orders from Open Collective payment method types */
async function processOrder(order) {
  switch (order.paymentMethod.type) {
  case 'prepaid': return prepaid.processOrder(order);
  case 'giftcard': return giftcard.processOrder(order);
  case 'collective':        // Fall through
  default: return collective.processOrder(order);
  }
}

/* API expected from a Payment Method provider */
export default {
  // payment method types
  // like cc, btc, prepaid, etc.
  types: {
    default: collective,
    collective,
    giftcard,
    prepaid,
  },
  processOrder,
};
<|endoftext|>


<commit_before>var in_old = zeros(5, 5) // old values
var in_new = zeros(5, 5) // new values
var in_tmp = zeros(5, 5) // old values (preserved)

function zeros(rows, cols) {

	grid = []
	for (let x = 0; x < rows; x++) {

		line = []
		for (let y = 0; y < cols; y++) {
			line.push({
				pressure: 0, // pressure
				velocity: {
					x: 0, // velocity (x-axis)
					y: 0  // velocity (y-axis)
				}
			})
		}
		grid.push(line)
	}
	return grid
}

function advect(arr) {
	out = zeros(5, 5)

	time = 1

	for (let x = 0; x < arr.length; x++) {

		for (let y = 0; y < arr[x].length; y++) {
			let x_old = x - arr[x][y].velocity.x * time
			let y_old = y - arr[x][y].velocity.y * time

			let q11 = {
				x: parseInt(Math.floor(x_old)),
				y: parseInt(Math.floor(y_old))
			}

			let q12 = {
				x: parseInt(Math.floor(x_old)),
				y: parseInt(Math.ceil(y_old))
			}

			let q21 = {
				x: parseInt(Math.ceil(x_old)),
				y: parseInt(Math.floor(y_old))
			}

			let q22 = {
				x: parseInt(Math.ceil(x_old)),
				y: parseInt(Math.ceil(y_old))
			}

			out[x][y] =
		}
	}
}

function bilinear(arr, x, y, q11, q12, q21, q22) {
	const d = {
		x:  x,
		y:  y,
		x1: q11.x,
		y1: q11.y,
		x2: q22.x,
		y2: q22.y
	}

	let term = 1 / (d.x2 - d.x1) * (d.y2 - d.y1)

	// performs x-axis velocity computation of interpolation
	let vx = ((d.x2 - d.x) * (arr[q11.x][q11.y].velocity.x + arr[q21.x][q21.y].velocity.x)) * (d.y2 - d.y)
		   + ((d.x - d.x1) * (arr[q12.x][q12.y].velocity.x + arr[q22.x][q22.y].velocity.x)) * (d.y - d.y1)

	// performs y-axis velocity computation of interpolation
	let vy = ((d.x2 - d.x) * (arr[q11.x][q11.y].velocity.y + arr[q21.x][q21.y].velocity.y)) * (d.y2 - d.y)
		   + ((d.x - d.x1) * (arr[q12.x][q12.y].velocity.y + arr[q22.x][q22.y].velocity.y)) * (d.y - d.y1)

	return {
		vx:  term * vx,
		vy:  term * vy
	}
}

function run() {
	while (true) {

	}
}

window.addEventListener("DOMContentLoaded", function() {
	run()
}, false)<commit_msg>created: continued work on algorithm<commit_after>var in_old = zeros(2, 2) // old values
var in_new = zeros(2, 2) // new values
var in_tmp = zeros(2, 2) // old values (preserved)

in_new[0][0].velocity.x = 0.1
in_new[0][0].velocity.y = 0.1

in_new[0][1].velocity.x = 0
in_new[0][1].velocity.y = 0

in_new[1][0].velocity.x = 0
in_new[1][0].velocity.y = 0

in_new[1][1].velocity.x = 0.4
in_new[1][1].velocity.y = 0.4

function zeros(rows, cols) {

	grid = []
	for (let x = 0; x < rows; x++) {

		line = []
		for (let y = 0; y < cols; y++) {
			line.push({
				pressure: 0, // pressure
				velocity: {
					x: 0, // velocity (x-axis)
					y: 0  // velocity (y-axis)
				}
			})
		}
		grid.push(line)
	}
	return grid
}

function advect(arr) {
	out = zeros(5, 5)

	time = 1

	for (let x = 0; x < arr.length; x++) {

		for (let y = 0; y < arr[x].length; y++) {
			let x_old = x - arr[x][y].velocity.x * time
			let y_old = y - arr[x][y].velocity.y * time

			let q11 = {
				x: parseInt(Math.floor(x_old)),
				y: parseInt(Math.floor(y_old))
			}

			let q12 = {
				x: parseInt(Math.floor(x_old)),
				y: parseInt(Math.floor(y_old) + 1)
			}

			let q21 = {
				x: parseInt(Math.floor(x_old) + 1),
				y: parseInt(Math.floor(y_old))
			}

			let q22 = {
				x: parseInt(Math.floor(x_old) + 1),
				y: parseInt(Math.floor(y_old) + 1)
			}

			console.log(bilinear(arr, x, y, q11, q12, q21, q22))

		//	out[x][y] =
		}
	}
}

function bilinear(arr, x, y, q11, q12, q21, q22) {
	const d = {
		x:  x,
		y:  y,
		x1: q11.x,
		y1: q11.y,
		x2: q22.x,
		y2: q22.y,

		v11: border(arr, q11),
		v12: border(arr, q12),
		v21: border(arr, q21),
		v22: border(arr, q22)
	}

	let term = 1 / (d.x2 - d.x1) * (d.y2 - d.y1)

	// performs x-axis velocity computation of interpolation
	let vx = (((d.x2 - d.x) * d.v11.x) + ((d.x - d.x1) * d.v21.x)) * (d.y2 - d.y)
		   + (((d.x2 - d.x) * d.v12.x) + ((d.x - d.x1) * d.v22.x)) * (d.y - d.y1)

	// performs y-axis velocity computation of interpolation
	let vy = (((d.x2 - d.x) * d.v11.y) + ((d.x - d.x1) * d.v21.y)) * (d.y2 - d.y)
		   + (((d.x2 - d.x) * d.v12.y) + ((d.x - d.x1) * d.v22.y)) * (d.y - d.y1)

	return {
		vx:  term * vx,
		vy:  term * vy
	}
}

function border(arr, q) {
	let v = {
		x: 0,
		y: 0
	}

	if (q.x >= 0 && q.x < arr.length && q.y >= 0 && q.y < arr[0].length) {
		v = {
			x: arr[q.x][q.y].velocity.x,
			y: arr[q.x][q.y].velocity.y
		}
	}

	return v
}

function run() {
	advect(in_new)
}

window.addEventListener("DOMContentLoaded", function() {
	run()
}, false)<|endoftext|>


<commit_before>'use strict';

var _ = require('lodash');
var when = require('when');
var beaconService = require('../../service/beacon.service.js');
var agentService = require('../../service/agent.service.js');
var socketio = require('../../config/socketio.js');

/**
 * Format:
 *  {
 *     "agentId": {
 *         "beaconsInRange": [beaconUniqueKey],
 *         "beaconCount": 1
 *     }
 * }
 *
 * Example:
 * {
 *   "10.1.10.34" :
 *      {
 *         "beaconsInRange" :
 *            [
 *               {
 *                  "key": "b9407f30f5f8466eaff925556b57fe6d:19602:10956",
 *                  "lastProximity" : 2.4545
 *               }
 *
 *            ],
 *         "beaconCount" : 1
 *      },
 *   "10.1.10.111" :
 *      {
 *         "beaconsInRange" :
 *            [
 *               "b9407f30f5f8466eaff925556b57fe6d:25:18",
 *               "b9407f30f5f8466eaff925556b57fe6d:99:73",
 *               "b9407f30f5f8466eaff925556b57fe6d:22:35"
 *            ],
 *         "beaconCount" : 3
 *      },
 *   "10.1.10.110" :
 *      {
 *         "beaconsInRange" : [],
 *         "beaconCount" : 0
 *      }
 * }
 *
 *
 * @type {{}}
 */
var agentStatusCache = {};

exports.getBeaconsInRangeOfLocationPromise = function getBeaconsInRangeOfLocationPromise(agentLocation) {

    var defer = when.defer();
    agentService.findByLocation(agentLocation)
        .then(function (foundAgent) {
            var agentCustomId = foundAgent.customId;
            var agentInfo = agentStatusCache[agentCustomId];

            if (agentInfo) {
                var beaconUniqueKeys = [];
                _.forEach(agentInfo.beaconsInRange, function(beaconStatus){
                    beaconUniqueKeys.push(beaconStatus.key);
                });

                var beaconUniqueKeysToRemove = [];

                // filter out any keys found by two beacons if needed (we only want to show it for the agent its closest to)
                var currentAgentIds = _.keys(agentStatusCache);
                _.forEach(currentAgentIds, function(agentId) {
                    if (agentId !== agentCustomId) {
                        var otherAgentInfo = agentStatusCache[agentId];
                        _.forEach(otherAgentInfo.beaconsInRange, function(otherBeaconStatus){
                            // is this beacon key in the list for this agent?
                            if (beaconUniqueKeys.indexOf(otherBeaconStatus.key) != -1) {
                                // which one is it closer to?
                                _.forEach(agentInfo.beaconsInRange, function(beaconStatus) {
                                    if (beaconStatus.key === otherBeaconStatus.key) {
                                        if (otherBeaconStatus.lastProximity < beaconStatus.lastProximity) {
                                            // its closer to another agent, so remove it from our lookup list
                                            beaconUniqueKeysToRemove.push(beaconStatus.key);
                                        }
                                    }
                                });
                            }
                        });
                    }
                });

                beaconUniqueKeys = _.difference(beaconUniqueKeys, beaconUniqueKeysToRemove);

                return lookUpBeaconsByUniqueKeysPromise(beaconUniqueKeys);
            } else {
                return when.resolve([]);
            }
        })
        //[Lindsay Thurmond:11/4/14] TODO: can i just return this promise instead?
        .then(function(beacons){
            defer.resolve(beacons);
        }, function(err){
            defer.reject(err);
        });

    return defer.promise;
}

function lookUpBeaconsByUniqueKeysPromise(beaconUniqueKeys) {
    var defer = when.defer();

    var found = [];
    var promises = [];
    beaconUniqueKeys.forEach(function (beaconUniqueKey) {
        var promise = beaconService.findByUniqueKey(beaconUniqueKey)
            .then(function (beacon) {
                found.push(beacon);
            }, function (err) {
                console.log(err);
            });
        promises.push(promise);
    });

    when.all(promises)
        .then(function (beaconsInRange) {
            //[Lindsay Thurmond:11/3/14] TODO: not sure why the beaconsInRange array contains undefined ... figure out later
//            defer.resolve(beaconsInRange);
            defer.resolve(found);
        }, function (err) {
            defer.reject(err);
        })
        .otherwise(function (err) {
            defer.reject(err);
        });

    return defer.promise;
}

function createBeaconUniqueKey(uuid, major, minor) {
    return uuid + ":" + major + ":" + minor;
}

function updateAgentStatusCache(eventType, agentId, beaconUniqueKey, proximity) {

    var agentStatus;
    if ('enter' === eventType || 'alive' === eventType) {

        agentStatus = agentStatusCache[agentId];
        if (!agentStatus) {
            agentStatusCache[agentId] = {
                beaconsInRange: [
                    {
                        key: beaconUniqueKey,
                        lastProximity: proximity
                    }
                ],
                beaconCount: 1
            };
        } else {
            var beaconUniqueKeys = [];
            _.forEach(agentStatus.beaconsInRange, function (beaconStatus) {
                beaconUniqueKeys.push(beaconStatus.key);
            });

            if (_.contains(beaconUniqueKeys, beaconUniqueKey)) {
                // update proximity
                _.forEach(agentStatus.beaconsInRange, function (beaconStatus) {
                    if (beaconStatus.key === beaconUniqueKey) {
                        beaconStatus.lastProximity = proximity;
                    }
                });
            }
            else {
                // add to list for first time
                agentStatus.beaconsInRange.push({ key: beaconUniqueKey, lastProximity: proximity});
                agentStatus.beaconCount = agentStatus.beaconsInRange.length;
            }
        }

    } else if ('exit' === eventType) {

        agentStatus = agentStatusCache[agentId];
        if (agentStatus) {
            agentStatus.beaconsInRange = _.remove(agentStatus.beaconsInRange, function (beaconStatus) {
                return beaconStatus.key !== beaconUniqueKey;
            });

            agentStatus.beaconCount = agentStatus.beaconsInRange.length;
        }

    } else {
        console.log('Unsupported detection event type found: ' + eventType);
    }

    console.log('Current status: ' + JSON.stringify(agentStatusCache));

    // just use this for local testing with a single agent
//    var agents = _.values(agentStatusCache);
//    var numBeacons = agents && agents.length > 0 ? agents[0].beaconCount : 0;
//    console.log('Current number of beacons: ' + numBeacons);
}

exports.processDetectionEvent = function (args) {

    var agentId = args[0];
    var uuid = args[1];
    var major = args[2];
    var minor = args[3];
    var proximity = args[4];
    var eventType = args[5];

    var beaconUniqueKey = createBeaconUniqueKey(uuid, major, minor);

//    console.log('Processing detection event. AgentId = ' + agentId + ', Beacon uuid = ' + uuid + ', Major = + ' + major + ', Minor = + ' + minor + ', EventType = ' + eventType);

    updateAgentStatusCache(eventType, agentId, beaconUniqueKey, proximity);

    notifyEngine(agentId, uuid, major, minor, eventType);
}

/**
 * Keeps track of the last time an enter event was seen for a beacon for a particular agent.
 *
 * Dictionary key = agentId:uuid:major:minor
 * Dictionary value = timestamp in ms
 *
 *
 * @type {{}}
 */
var lastTimeBeaconEnterSoundPlayed = {};

function notifyEngine(agentId, uuid, major, minor, eventType) {

    // play audio on the agent that detected the beacon
    if ('enter' === eventType) {

        var playSound = true;
        // if we haven't played the enter noise in the last 10 seconds then we can play it
        var key = agentId + ':' + uuid + ':' + major + ':' + minor;
        var lastPlayed = lastTimeBeaconEnterSoundPlayed[key];
        if (lastPlayed) {
            // check if we played in last 10 seconds
            var now = Date.now();
            var tenSecondsAgo = now - (10 * 1000);
            // ok play sound if the last time we played the sound was more than 10 seconds ago
            playSound = lastPlayed < tenSecondsAgo;
            if (!playSound) {
                console.log('Enter event for %s, but sound was played within the last 10 seconds.  Not playing again.', key);
            }
        }

        if (playSound) {
            var beaconKey = createBeaconUniqueKey(uuid, major, minor);
            lookupAudio(agentId, beaconKey)
                .then(function (filename) {

                    // update last play time
                    lastTimeBeaconEnterSoundPlayed[key] = Date.now();
                    console.log('Calling playAudioOnEngine - AgentId: %s, Filename: %s', agentId, filename);
                    socketio.playAudioOnEngines(filename);

                }, function (err) {
                    console.log('Error sending play audio command: ' + err);
                });
        }
    }
}

/**
 *
 * @param agentId agent that detected the beacon
 * @param beaconKey beacon that was detected
 * @returns {When.Promise<T>} promise containing the filename of the audio file
 */
function lookupAudio(agentId, beaconKey) {
    var defer = when.defer();

    beaconService.findByUniqueKey(beaconKey)
        .then(function(beacon){

            var foundFilename  = false;

            if (beacon && beacon.audio) {
                var filename = beacon.audio.filename;
                if (filename && filename != '') {
                    foundFilename = true;
                    defer.resolve(filename);
                }
            }

            if (!foundFilename) {
                // check for agent default
                agentService.findAgentByCustomId(agentId)
                    .then(function (agent) {
                        if (agent && agent.audio) {
                            defer.resolve(agent.audio.filename);
                        } else {
                            // we didn't find anything, just send an empty filename
                            defer.resolve('');
                        }
                    }, function (err) {
                        defer.reject(err);
                    });
            }

        }, function(err){
            defer.reject(err);
        });

    return defer.promise;
}<commit_msg>add logging<commit_after>'use strict';

var _ = require('lodash');
var when = require('when');
var beaconService = require('../../service/beacon.service.js');
var agentService = require('../../service/agent.service.js');
var socketio = require('../../config/socketio.js');

/**
 * Format:
 *  {
 *     "agentId": {
 *         "beaconsInRange": [beaconUniqueKey],
 *         "beaconCount": 1
 *     }
 * }
 *
 * Example:
 * {
 *   "10.1.10.34" :
 *      {
 *         "beaconsInRange" :
 *            [
 *               {
 *                  "key": "b9407f30f5f8466eaff925556b57fe6d:19602:10956",
 *                  "lastProximity" : 2.4545
 *               }
 *
 *            ],
 *         "beaconCount" : 1
 *      },
 *   "10.1.10.111" :
 *      {
 *         "beaconsInRange" :
 *            [
 *               "b9407f30f5f8466eaff925556b57fe6d:25:18",
 *               "b9407f30f5f8466eaff925556b57fe6d:99:73",
 *               "b9407f30f5f8466eaff925556b57fe6d:22:35"
 *            ],
 *         "beaconCount" : 3
 *      },
 *   "10.1.10.110" :
 *      {
 *         "beaconsInRange" : [],
 *         "beaconCount" : 0
 *      }
 * }
 *
 *
 * @type {{}}
 */
var agentStatusCache = {};

exports.getBeaconsInRangeOfLocationPromise = function getBeaconsInRangeOfLocationPromise(agentLocation) {

    var defer = when.defer();
    agentService.findByLocation(agentLocation)
        .then(function (foundAgent) {
            var agentCustomId = foundAgent.customId;
            var agentInfo = agentStatusCache[agentCustomId];

            if (agentInfo) {
                var beaconUniqueKeys = [];
                _.forEach(agentInfo.beaconsInRange, function(beaconStatus){
                    beaconUniqueKeys.push(beaconStatus.key);
                });

                var beaconUniqueKeysToRemove = [];

                // filter out any keys found by two beacons if needed (we only want to show it for the agent its closest to)
                var currentAgentIds = _.keys(agentStatusCache);
                _.forEach(currentAgentIds, function(agentId) {
                    if (agentId !== agentCustomId) {
                        var otherAgentInfo = agentStatusCache[agentId];
                        _.forEach(otherAgentInfo.beaconsInRange, function(otherBeaconStatus){
                            // is this beacon key in the list for this agent?
                            if (beaconUniqueKeys.indexOf(otherBeaconStatus.key) != -1) {
                                // which one is it closer to?
                                _.forEach(agentInfo.beaconsInRange, function(beaconStatus) {
                                    if (beaconStatus.key === otherBeaconStatus.key) {
                                        if (otherBeaconStatus.lastProximity < beaconStatus.lastProximity) {
                                            // its closer to another agent, so remove it from our lookup list
                                            beaconUniqueKeysToRemove.push(beaconStatus.key);
                                        }
                                    }
                                });
                            }
                        });
                    }
                });

                beaconUniqueKeys = _.difference(beaconUniqueKeys, beaconUniqueKeysToRemove);

                return lookUpBeaconsByUniqueKeysPromise(beaconUniqueKeys);
            } else {
                return when.resolve([]);
            }
        })
        //[Lindsay Thurmond:11/4/14] TODO: can i just return this promise instead?
        .then(function(beacons){
            defer.resolve(beacons);
        }, function(err){
            defer.reject(err);
        });

    return defer.promise;
}

function lookUpBeaconsByUniqueKeysPromise(beaconUniqueKeys) {
    var defer = when.defer();

    var found = [];
    var promises = [];
    beaconUniqueKeys.forEach(function (beaconUniqueKey) {
        var promise = beaconService.findByUniqueKey(beaconUniqueKey)
            .then(function (beacon) {
                found.push(beacon);
            }, function (err) {
                console.log(err);
            });
        promises.push(promise);
    });

    when.all(promises)
        .then(function (beaconsInRange) {
            //[Lindsay Thurmond:11/3/14] TODO: not sure why the beaconsInRange array contains undefined ... figure out later
//            defer.resolve(beaconsInRange);
            defer.resolve(found);
        }, function (err) {
            defer.reject(err);
        })
        .otherwise(function (err) {
            defer.reject(err);
        });

    return defer.promise;
}

function createBeaconUniqueKey(uuid, major, minor) {
    return uuid + ":" + major + ":" + minor;
}

function updateAgentStatusCache(eventType, agentId, beaconUniqueKey, proximity) {

    var agentStatus;
    if ('enter' === eventType || 'alive' === eventType) {

        agentStatus = agentStatusCache[agentId];
        if (!agentStatus) {
            agentStatusCache[agentId] = {
                beaconsInRange: [
                    {
                        key: beaconUniqueKey,
                        lastProximity: proximity
                    }
                ],
                beaconCount: 1
            };
        } else {
            var beaconUniqueKeys = [];
            _.forEach(agentStatus.beaconsInRange, function (beaconStatus) {
                beaconUniqueKeys.push(beaconStatus.key);
            });

            if (_.contains(beaconUniqueKeys, beaconUniqueKey)) {
                // update proximity
                _.forEach(agentStatus.beaconsInRange, function (beaconStatus) {
                    if (beaconStatus.key === beaconUniqueKey) {
                        beaconStatus.lastProximity = proximity;
                    }
                });
            }
            else {
                // add to list for first time
                agentStatus.beaconsInRange.push({ key: beaconUniqueKey, lastProximity: proximity});
                agentStatus.beaconCount = agentStatus.beaconsInRange.length;
            }
        }

    } else if ('exit' === eventType) {

        agentStatus = agentStatusCache[agentId];
        if (agentStatus) {
            agentStatus.beaconsInRange = _.remove(agentStatus.beaconsInRange, function (beaconStatus) {
                return beaconStatus.key !== beaconUniqueKey;
            });

            agentStatus.beaconCount = agentStatus.beaconsInRange.length;
        }

    } else {
        console.log('Unsupported detection event type found: ' + eventType);
    }

    console.log('Current status: ' + JSON.stringify(agentStatusCache));

    // just use this for local testing with a single agent
//    var agents = _.values(agentStatusCache);
//    var numBeacons = agents && agents.length > 0 ? agents[0].beaconCount : 0;
//    console.log('Current number of beacons: ' + numBeacons);
}

exports.processDetectionEvent = function (args) {

    var agentId = args[0];
    var uuid = args[1];
    var major = args[2];
    var minor = args[3];
    var proximity = args[4];
    var eventType = args[5];

    var beaconUniqueKey = createBeaconUniqueKey(uuid, major, minor);

//    console.log('Processing detection event. AgentId = ' + agentId + ', Beacon uuid = ' + uuid + ', Major = + ' + major + ', Minor = + ' + minor + ', EventType = ' + eventType);

    updateAgentStatusCache(eventType, agentId, beaconUniqueKey, proximity);

    notifyEngine(agentId, uuid, major, minor, eventType);
}

/**
 * Keeps track of the last time an enter event was seen for a beacon for a particular agent.
 *
 * Dictionary key = agentId:uuid:major:minor
 * Dictionary value = timestamp in ms
 *
 *
 * @type {{}}
 */
var lastTimeBeaconEnterSoundPlayed = {};

function notifyEngine(agentId, uuid, major, minor, eventType) {

    // play audio on the agent that detected the beacon
    if ('enter' === eventType) {

        var playSound = true;
        // if we haven't played the enter noise in the last 10 seconds then we can play it
        var key = agentId + ':' + uuid + ':' + major + ':' + minor;
        var lastPlayed = lastTimeBeaconEnterSoundPlayed[key];
        if (lastPlayed) {
            // check if we played in last 10 seconds
            var now = Date.now();
            var tenSecondsAgo = now - (10 * 1000);
            // ok play sound if the last time we played the sound was more than 10 seconds ago
            playSound = lastPlayed < tenSecondsAgo;
            if (!playSound) {
                console.log('Enter event for %s, but sound was played within the last 10 seconds.  Not playing again.', key);
            }
        }

        if (playSound) {
            var beaconKey = createBeaconUniqueKey(uuid, major, minor);
            lookupAudio(agentId, beaconKey)
                .then(function (filename) {

                    // update last play time
                    lastTimeBeaconEnterSoundPlayed[key] = Date.now();
                    console.log('Calling playAudioOnEngine - AgentId: %s, Filename: %s', agentId, filename);
                    socketio.playAudioOnEngines(filename);

                }, function (err) {
                    console.log('Error sending play audio command: ' + err);
                });
        }
    }
}

/**
 *
 * @param agentId agent that detected the beacon
 * @param beaconKey beacon that was detected
 * @returns {When.Promise<T>} promise containing the filename of the audio file
 */
function lookupAudio(agentId, beaconKey) {
    var defer = when.defer();

    beaconService.findByUniqueKey(beaconKey)
        .then(function(beacon){

            var foundFilename  = false;

            if (beacon && beacon.audio) {
                var filename = beacon.audio.filename;
                if (filename && filename != '') {
                    foundFilename = true;
                    defer.resolve(filename);
                }
            }

            if (!foundFilename) {
                // check for agent default
                agentService.findAgentByCustomId(agentId)
                    .then(function (agent) {
                        if (agent && agent.audio) {
                            defer.resolve(agent.audio.filename);
                        } else {
                            // we didn't find anything, just send an empty filename
                            defer.resolve('');
                        }
                    }, function (err) {
                        defer.reject(err);
                    });
            }

        }, function(err){
            console.log('Error looking up filename: %s', err);
            defer.reject(err);
        });

    return defer.promise;
}<|endoftext|>


<commit_before>'use strict';

var _ = require('lodash');
var when = require('when');
var beaconService = require('../../service/beacon.service.js');
var agentService = require('../../service/agent.service.js');
var socketio = require('../../config/socketio.js');

/**
 * Format:
 *  {
 *     "agentId": {
 *         "beaconsInRange": [beaconUniqueKey],
 *         "beaconCount": 1
 *     }
 * }
 *
 * Example:
 * {
 *   "10.1.10.34" :
 *      {
 *         "beaconsInRange" :
 *            [
 *               {
 *                  "key": "b9407f30f5f8466eaff925556b57fe6d:19602:10956",
 *                  "lastProximity" : 2.4545
 *               }
 *
 *            ],
 *         "beaconCount" : 1
 *      },
 *   "10.1.10.111" :
 *      {
 *         "beaconsInRange" :
 *            [
 *               "b9407f30f5f8466eaff925556b57fe6d:25:18",
 *               "b9407f30f5f8466eaff925556b57fe6d:99:73",
 *               "b9407f30f5f8466eaff925556b57fe6d:22:35"
 *            ],
 *         "beaconCount" : 3
 *      },
 *   "10.1.10.110" :
 *      {
 *         "beaconsInRange" : [],
 *         "beaconCount" : 0
 *      }
 * }
 *
 *
 * @type {{}}
 */
var agentStatusCache = {};

exports.getBeaconsInRangeOfLocationPromise = function getBeaconsInRangeOfLocationPromise(agentLocation) {

    var defer = when.defer();
    agentService.findByLocation(agentLocation)
        .then(function (foundAgent) {
            var agentCustomId = foundAgent.customId;
            var agentInfo = agentStatusCache[agentCustomId];

            if (agentInfo) {
                var beaconUniqueKeys = [];
                _.forEach(agentInfo.beaconsInRange, function(beaconStatus){
                    beaconUniqueKeys.push(beaconStatus.key);
                });

                var beaconUniqueKeysToRemove = [];

                // filter out any keys found by two beacons if needed (we only want to show it for the agent its closest to)
                var currentAgentIds = _.keys(agentStatusCache);
                _.forEach(currentAgentIds, function(agentId) {
                    if (agentId !== agentCustomId) {
                        var otherAgentInfo = agentStatusCache[agentId];
                        _.forEach(otherAgentInfo.beaconsInRange, function(otherBeaconStatus){
                            // is this beacon key in the list for this agent?
                            if (beaconUniqueKeys.indexOf(otherBeaconStatus.key) != -1) {
                                // which one is it closer to?
                                _.forEach(agentInfo.beaconsInRange, function(beaconStatus) {
                                    if (beaconStatus.key === otherBeaconStatus.key) {
                                        if (otherBeaconStatus.lastProximity < beaconStatus.lastProximity) {
                                            // its closer to another agent, so remove it from our lookup list
                                            beaconUniqueKeysToRemove.push(beaconStatus.key);
                                        }
                                    }
                                });
                            }
                        });
                    }
                });

                beaconUniqueKeys = _.difference(beaconUniqueKeys, beaconUniqueKeysToRemove);

                return lookUpBeaconsByUniqueKeysPromise(beaconUniqueKeys);
            } else {
                return when.resolve([]);
            }
        })
        //[Lindsay Thurmond:11/4/14] TODO: can i just return this promise instead?
        .then(function(beacons){
            defer.resolve(beacons);
        }, function(err){
            defer.reject(err);
        });

    return defer.promise;
}

function lookUpBeaconsByUniqueKeysPromise(beaconUniqueKeys) {
    var defer = when.defer();

    var found = [];
    var promises = [];
    beaconUniqueKeys.forEach(function (beaconUniqueKey) {
        var promise = beaconService.findByUniqueKey(beaconUniqueKey)
            .then(function (beacon) {
                found.push(beacon);
            }, function (err) {
                console.log(err);
            });
        promises.push(promise);
    });

    when.all(promises)
        .then(function (beaconsInRange) {
            //[Lindsay Thurmond:11/3/14] TODO: not sure why the beaconsInRange array contains undefined ... figure out later
//            defer.resolve(beaconsInRange);
            defer.resolve(found);
        }, function (err) {
            defer.reject(err);
        })
        .otherwise(function (err) {
            defer.reject(err);
        });

    return defer.promise;
}

function createBeaconUniqueKey(uuid, major, minor) {
    return uuid + ":" + major + ":" + minor;
}

function updateAgentStatusCache(eventType, agentId, beaconUniqueKey, proximity) {

    var agentStatus;
    if ('enter' === eventType || 'alive' === eventType) {

        agentStatus = agentStatusCache[agentId];
        if (!agentStatus) {
            agentStatusCache[agentId] = {
                beaconsInRange: [
                    {
                        key: beaconUniqueKey,
                        lastProximity: proximity
                    }
                ],
                beaconCount: 1
            };
        } else {
            var beaconUniqueKeys = [];
            _.forEach(agentStatus.beaconsInRange, function (beaconStatus) {
                beaconUniqueKeys.push(beaconStatus.key);
            });

            if (_.contains(beaconUniqueKeys, beaconUniqueKey)) {
                // update proximity
                _.forEach(agentStatus.beaconsInRange, function (beaconStatus) {
                    if (beaconStatus.key === beaconUniqueKey) {
                        beaconStatus.lastProximity = proximity;
                    }
                });
            }
            else {
                // add to list for first time
                agentStatus.beaconsInRange.push({ key: beaconUniqueKey, lastProximity: proximity});
                agentStatus.beaconCount = agentStatus.beaconsInRange.length;
            }
        }

    } else if ('exit' === eventType) {

        agentStatus = agentStatusCache[agentId];
        if (agentStatus) {
            agentStatus.beaconsInRange = _.remove(agentStatus.beaconsInRange, function (beaconStatus) {
                return beaconStatus.key !== beaconUniqueKey;
            });

            agentStatus.beaconCount = agentStatus.beaconsInRange.length;
        }

    } else {
        console.log('Unsupported detection event type found: ' + eventType);
    }

    console.log('Current status: ' + JSON.stringify(agentStatusCache));

    // just use this for local testing with a single agent
//    var agents = _.values(agentStatusCache);
//    var numBeacons = agents && agents.length > 0 ? agents[0].beaconCount : 0;
//    console.log('Current number of beacons: ' + numBeacons);
}

exports.processDetectionEvent = function (args) {

    var agentId = args[0];
    var uuid = args[1];
    var major = args[2];
    var minor = args[3];
    var proximity = args[4];
    var eventType = args[5];

    var beaconUniqueKey = createBeaconUniqueKey(uuid, major, minor);

//    console.log('Processing detection event. AgentId = ' + agentId + ', Beacon uuid = ' + uuid + ', Major = + ' + major + ', Minor = + ' + minor + ', EventType = ' + eventType);

    updateAgentStatusCache(eventType, agentId, beaconUniqueKey, proximity);

    notifyEngine(agentId, uuid, major, minor, eventType);
}

/**
 * Keeps track of the last time an enter event was seen for a beacon for a particular agent.
 *
 * Dictionary key = agentId:uuid:major:minor
 * Dictionary value = timestamp in ms
 *
 *
 * @type {{}}
 */
var lastTimeBeaconEnterSoundPlayed = {};

function notifyEngine(agentId, uuid, major, minor, eventType) {

    // play audio on the agent that detected the beacon
    if ('enter' === eventType) {

        var playSound = true;
        // if we haven't played the enter noise in the last 10 seconds then we can play it
        var key = agentId + ':' + uuid + ':' + major + ':' + minor;
        var lastPlayed = lastTimeBeaconEnterSoundPlayed[key];
        if (lastPlayed) {
            // check if we played in last 10 seconds
            var now = Date.now();
            var tenSecondsAgo = now - (10 * 1000);
            // ok play sound if the last time we played the sound was more than 10 seconds ago
            playSound = lastPlayed < tenSecondsAgo;
            if (!playSound) {
                console.log('Enter event for %s, but sound was played within the last 10 seconds.  Not playing again.', key);
            }
        }

        if (playSound) {
            var beaconKey = createBeaconUniqueKey(uuid, major, minor);
            lookupAudio(agentId, beaconKey)
                .then(function (filename) {

                    // update last play time
                    lastTimeBeaconEnterSoundPlayed[key] = Date.now();
                    console.log('Calling playAudioOnEngine - AgentId: %s, Filename: %s', agentId, filename);
                    socketio.playAudioOnEngines(filename);

                }, function (err) {
                    console.log('Error sending play audio command: ' + err);
                });
        }
    }
}

/**
 *
 * @param agentId agent that detected the beacon
 * @param beaconKey beacon that was detected
 * @returns {When.Promise<T>} promise containing the filename of the audio file
 */
function lookupAudio(agentId, beaconKey) {
    var defer = when.defer();

    beaconService.findByUniqueKey(beaconKey)
        .then(function(beacon){

            var foundFilename  = false;

            if (beacon && beacon.audio) {
                var filename = beacon.audio.filename;
                if (filename && filename != '') {
                    foundFilename = true;
                    defer.resolve(filename);
                }
            }

            if (!foundFilename) {
                // check for agent default
                agentService.findAgentByCustomId(agentId)
                    .then(function (agent) {
                        if (agent && agent.audio) {
                            defer.resolve(agent.audio.filename);
                        } else {
                            // we didn't find anything, just send an empty filename
                            defer.resolve('');
                        }
                    }, function (err) {
                        defer.reject(err);
                    });
            }

        }, function(err){
            console.log('Error looking up filename: %s', err);
            defer.reject(err);
        });

    return defer.promise;
}<commit_msg>add logging<commit_after>'use strict';

var _ = require('lodash');
var when = require('when');
var beaconService = require('../../service/beacon.service.js');
var agentService = require('../../service/agent.service.js');
var socketio = require('../../config/socketio.js');

/**
 * Format:
 *  {
 *     "agentId": {
 *         "beaconsInRange": [beaconUniqueKey],
 *         "beaconCount": 1
 *     }
 * }
 *
 * Example:
 * {
 *   "10.1.10.34" :
 *      {
 *         "beaconsInRange" :
 *            [
 *               {
 *                  "key": "b9407f30f5f8466eaff925556b57fe6d:19602:10956",
 *                  "lastProximity" : 2.4545
 *               }
 *
 *            ],
 *         "beaconCount" : 1
 *      },
 *   "10.1.10.111" :
 *      {
 *         "beaconsInRange" :
 *            [
 *               "b9407f30f5f8466eaff925556b57fe6d:25:18",
 *               "b9407f30f5f8466eaff925556b57fe6d:99:73",
 *               "b9407f30f5f8466eaff925556b57fe6d:22:35"
 *            ],
 *         "beaconCount" : 3
 *      },
 *   "10.1.10.110" :
 *      {
 *         "beaconsInRange" : [],
 *         "beaconCount" : 0
 *      }
 * }
 *
 *
 * @type {{}}
 */
var agentStatusCache = {};

exports.getBeaconsInRangeOfLocationPromise = function getBeaconsInRangeOfLocationPromise(agentLocation) {

    var defer = when.defer();
    agentService.findByLocation(agentLocation)
        .then(function (foundAgent) {
            var agentCustomId = foundAgent.customId;
            var agentInfo = agentStatusCache[agentCustomId];

            if (agentInfo) {
                var beaconUniqueKeys = [];
                _.forEach(agentInfo.beaconsInRange, function(beaconStatus){
                    beaconUniqueKeys.push(beaconStatus.key);
                });

                var beaconUniqueKeysToRemove = [];

                // filter out any keys found by two beacons if needed (we only want to show it for the agent its closest to)
                var currentAgentIds = _.keys(agentStatusCache);
                _.forEach(currentAgentIds, function(agentId) {
                    if (agentId !== agentCustomId) {
                        var otherAgentInfo = agentStatusCache[agentId];
                        _.forEach(otherAgentInfo.beaconsInRange, function(otherBeaconStatus){
                            // is this beacon key in the list for this agent?
                            if (beaconUniqueKeys.indexOf(otherBeaconStatus.key) != -1) {
                                // which one is it closer to?
                                _.forEach(agentInfo.beaconsInRange, function(beaconStatus) {
                                    if (beaconStatus.key === otherBeaconStatus.key) {
                                        if (otherBeaconStatus.lastProximity < beaconStatus.lastProximity) {
                                            // its closer to another agent, so remove it from our lookup list
                                            beaconUniqueKeysToRemove.push(beaconStatus.key);
                                        }
                                    }
                                });
                            }
                        });
                    }
                });

                beaconUniqueKeys = _.difference(beaconUniqueKeys, beaconUniqueKeysToRemove);

                return lookUpBeaconsByUniqueKeysPromise(beaconUniqueKeys);
            } else {
                return when.resolve([]);
            }
        })
        //[Lindsay Thurmond:11/4/14] TODO: can i just return this promise instead?
        .then(function(beacons){
            defer.resolve(beacons);
        }, function(err){
            defer.reject(err);
        });

    return defer.promise;
}

function lookUpBeaconsByUniqueKeysPromise(beaconUniqueKeys) {
    var defer = when.defer();

    var found = [];
    var promises = [];
    beaconUniqueKeys.forEach(function (beaconUniqueKey) {
        var promise = beaconService.findByUniqueKey(beaconUniqueKey)
            .then(function (beacon) {
                found.push(beacon);
            }, function (err) {
                console.log(err);
            });
        promises.push(promise);
    });

    when.all(promises)
        .then(function (beaconsInRange) {
            //[Lindsay Thurmond:11/3/14] TODO: not sure why the beaconsInRange array contains undefined ... figure out later
//            defer.resolve(beaconsInRange);
            defer.resolve(found);
        }, function (err) {
            defer.reject(err);
        })
        .otherwise(function (err) {
            defer.reject(err);
        });

    return defer.promise;
}

function createBeaconUniqueKey(uuid, major, minor) {
    return uuid + ":" + major + ":" + minor;
}

function updateAgentStatusCache(eventType, agentId, beaconUniqueKey, proximity) {

    var agentStatus;
    if ('enter' === eventType || 'alive' === eventType) {

        agentStatus = agentStatusCache[agentId];
        if (!agentStatus) {
            agentStatusCache[agentId] = {
                beaconsInRange: [
                    {
                        key: beaconUniqueKey,
                        lastProximity: proximity
                    }
                ],
                beaconCount: 1
            };
        } else {
            var beaconUniqueKeys = [];
            _.forEach(agentStatus.beaconsInRange, function (beaconStatus) {
                beaconUniqueKeys.push(beaconStatus.key);
            });

            if (_.contains(beaconUniqueKeys, beaconUniqueKey)) {
                // update proximity
                _.forEach(agentStatus.beaconsInRange, function (beaconStatus) {
                    if (beaconStatus.key === beaconUniqueKey) {
                        beaconStatus.lastProximity = proximity;
                    }
                });
            }
            else {
                // add to list for first time
                agentStatus.beaconsInRange.push({ key: beaconUniqueKey, lastProximity: proximity});
                agentStatus.beaconCount = agentStatus.beaconsInRange.length;
            }
        }

    } else if ('exit' === eventType) {

        agentStatus = agentStatusCache[agentId];
        if (agentStatus) {
            agentStatus.beaconsInRange = _.remove(agentStatus.beaconsInRange, function (beaconStatus) {
                return beaconStatus.key !== beaconUniqueKey;
            });

            agentStatus.beaconCount = agentStatus.beaconsInRange.length;
        }

    } else {
        console.log('Unsupported detection event type found: ' + eventType);
    }

    console.log('Current status: ' + JSON.stringify(agentStatusCache));

    // just use this for local testing with a single agent
//    var agents = _.values(agentStatusCache);
//    var numBeacons = agents && agents.length > 0 ? agents[0].beaconCount : 0;
//    console.log('Current number of beacons: ' + numBeacons);
}

exports.processDetectionEvent = function (args) {

    var agentId = args[0];
    var uuid = args[1];
    var major = args[2];
    var minor = args[3];
    var proximity = args[4];
    var eventType = args[5];

    var beaconUniqueKey = createBeaconUniqueKey(uuid, major, minor);

//    console.log('Processing detection event. AgentId = ' + agentId + ', Beacon uuid = ' + uuid + ', Major = + ' + major + ', Minor = + ' + minor + ', EventType = ' + eventType);

    updateAgentStatusCache(eventType, agentId, beaconUniqueKey, proximity);

    notifyEngine(agentId, uuid, major, minor, eventType);
}

/**
 * Keeps track of the last time an enter event was seen for a beacon for a particular agent.
 *
 * Dictionary key = agentId:uuid:major:minor
 * Dictionary value = timestamp in ms
 *
 *
 * @type {{}}
 */
var lastTimeBeaconEnterSoundPlayed = {};

function notifyEngine(agentId, uuid, major, minor, eventType) {

    // play audio on the agent that detected the beacon
    if ('enter' === eventType) {

        var playSound = true;
        // if we haven't played the enter noise in the last 10 seconds then we can play it
        var key = agentId + ':' + uuid + ':' + major + ':' + minor;
        var lastPlayed = lastTimeBeaconEnterSoundPlayed[key];
        if (lastPlayed) {
            // check if we played in last 10 seconds
            var now = Date.now();
            var tenSecondsAgo = now - (10 * 1000);
            // ok play sound if the last time we played the sound was more than 10 seconds ago
            playSound = lastPlayed < tenSecondsAgo;
            if (!playSound) {
                console.log('Enter event for %s, but sound was played within the last 10 seconds.  Not playing again.', key);
            }
        }

        if (playSound) {
            var beaconKey = createBeaconUniqueKey(uuid, major, minor);
            lookupAudio(agentId, beaconKey)
                .then(function (filename) {

                    // update last play time
                    lastTimeBeaconEnterSoundPlayed[key] = Date.now();
                    console.log('Calling playAudioOnEngine - AgentId: %s, Filename: %s', agentId, filename);
                    socketio.playAudioOnEngines(filename);

                }, function (err) {
                    console.log('Error sending play audio command: ' + err);
                });
        }
    }
}

/**
 *
 * @param agentId agent that detected the beacon
 * @param beaconKey beacon that was detected
 * @returns {When.Promise<T>} promise containing the filename of the audio file
 */
function lookupAudio(agentId, beaconKey) {
    var defer = when.defer();

    beaconService.findByUniqueKey(beaconKey)
        .then(function(beacon){

            console.log('Beacon LOOKUP SHOULD HAVE AUDIO: %s', JSON.stringify(beacon));

            var foundFilename  = false;

            if (beacon && beacon.audio) {
                var filename = beacon.audio.filename;
                if (filename && filename != '') {
                    foundFilename = true;
                    defer.resolve(filename);
                }
            }

            if (!foundFilename) {
                // check for agent default
                agentService.findAgentByCustomId(agentId)
                    .then(function (agent) {
                        if (agent && agent.audio) {
                            defer.resolve(agent.audio.filename);
                        } else {
                            // we didn't find anything, just send an empty filename
                            console.log('DID NOT FIND AUDIO');
                            defer.resolve('');
                        }
                    }, function (err) {
                        defer.reject(err);
                    });
            }

        }, function(err){
            console.log('Error looking up filename: %s', err);
            defer.reject(err);
        });

    return defer.promise;
}<|endoftext|>


<commit_before>// @flow

const R = require('ramda');
const validator = require('validator');
const sshpk = require('sshpk');
const { keycloakAdminClient } = require('../../clients/keycloakClient');
const searchguardClient = require('../../clients/searchguardClient');
const logger = require('../../logger');
const {
  inClause,
  prepare,
  query,
  whereAnd,
  isPatchEmpty,
} = require('../../util/db');

const Helpers = require('./helpers');
const KeycloakOperations = require('./keycloak');
const { SearchguardOperations } = require('../group/searchguard');
const Sql = require('./sql');
const { generatePrivateKey, getSshKeyFingerprint } = require('../sshKey');
const sshKeySql = require('../sshKey/sql');

/* ::

import type {ResolversObj} from '../';

*/

const removePrivateKey = R.assoc('privateKey', null);

const getAllProjects = async (
  root,
  args,
  {
    sqlClient,
    hasPermission,
    dataSources,
    keycloakGrant,
  },
) => {
  let where;
  try {
    await hasPermission('project', 'viewAll');

    where = whereAnd([
      args.createdAfter ? 'created >= :created_after' : '',
      args.gitUrl ? 'git_url = :git_url' : '',
    ]);
  } catch (err) {
    if (!keycloakGrant) {
      logger.warn('No grant available for getAllProjects');
      return [];
    }

    const userProjectIds = await dataSources.UserModel.getAllProjectsIdsForUser({
      id: keycloakGrant.access_token.content.sub,
    });

    where = whereAnd([
      args.createdAfter ? 'created >= :created_after' : '',
      args.gitUrl ? 'git_url = :git_url' : '',
      inClause('id', userProjectIds),
    ]);
  }

  const order = args.order ? ` ORDER BY ${R.toLower(args.order)} ASC` : '';

  const prep = prepare(sqlClient, `SELECT * FROM project ${where}${order}`);
  const rows = await query(sqlClient, prep(args));

  return rows;
};

const getProjectByEnvironmentId = async (
  { id: eid },
  args,
  {
    sqlClient,
    hasPermission,
  },
) => {
  const prep = prepare(
    sqlClient,
    `SELECT
        p.*
      FROM environment e
      JOIN project p ON e.project = p.id
      WHERE e.id = :eid
      LIMIT 1
    `,
  );

  const rows = await query(sqlClient, prep({ eid }));

  const project = rows[0];

  await hasPermission('project', 'view', {
    project: project.id,
  });

  try {
    await hasPermission('project', 'viewPrivateKey', {
      project: project.id,
    });

    return project;
  } catch (err) {
    return removePrivateKey(project);
  }
};

const getProjectByGitUrl = async (
  root,
  args,
  {
    sqlClient,
    hasPermission,
  },
) => {
  const str = `
      SELECT
        *
      FROM project
      WHERE git_url = :git_url
      LIMIT 1
    `;

  const prep = prepare(sqlClient, str);
  const rows = await query(sqlClient, prep(args));

  const project = rows[0];

  await hasPermission('project', 'view', {
    project: project.id,
  });

  try {
    await hasPermission('project', 'viewPrivateKey', {
      project: project.id,
    });

    return project;
  } catch (err) {
    return removePrivateKey(project);
  }
};

const getProjectByName = async (
  root,
  args,
  {
    sqlClient,
    hasPermission,
  },
) => {
  const str = `
      SELECT
        *
      FROM project
      WHERE name = :name
    `;

  const prep = prepare(sqlClient, str);

  const rows = await query(sqlClient, prep(args));
  const project = rows[0];

  await hasPermission('project', 'view', {
    project: project.id,
  });

  try {
    await hasPermission('project', 'viewPrivateKey', {
      project: project.id,
    });

    return project;
  } catch (err) {
    return removePrivateKey(project);
  }
};

const addProject = async (
  root,
  { input },
  {
    hasPermission,
    sqlClient,
    dataSources,
  },
) => {
  await hasPermission('project', 'add');

  if (validator.matches(input.name, /[^0-9a-z-]/)) {
    throw new Error(
      'Only lowercase characters, numbers and dashes allowed for name!',
    );
  }

  let keyPair = {};
  try {
    const privateKey = R.cond([
      [R.isNil, generatePrivateKey],
      [R.isEmpty, generatePrivateKey],
      [R.T, sshpk.parsePrivateKey],
    ])(R.prop('privateKey', input));

    const publicKey = privateKey.toPublic();

    keyPair = {
      ...keyPair,
      private: R.replace(/\n/g, '\n', privateKey.toString('openssh')),
      public: publicKey.toString(),
    };
  } catch (err) {
    throw new Error(`There was an error with the privateKey: ${err.message}`);
  }

  const prep = prepare(
    sqlClient,
    `CALL CreateProject(
        :id,
        :name,
        :git_url,
        :private_key,
        ${input.subfolder ? ':subfolder' : 'NULL'},
        :openshift,
        ${
  input.openshiftProjectPattern ? ':openshift_project_pattern' : 'NULL'
},
        ${
  input.activeSystemsDeploy
    ? ':active_systems_deploy'
    : '"lagoon_openshiftBuildDeploy"'
},
        ${
  input.activeSystemsPromote
    ? ':active_systems_promote'
    : '"lagoon_openshiftBuildDeploy"'
},
        ${
  input.activeSystemsRemove
    ? ':active_systems_remove'
    : '"lagoon_openshiftRemove"'
},
        ${
  input.activeSystemsTask
    ? ':active_systems_task'
    : '"lagoon_openshiftJob"'
},
        ${input.branches ? ':branches' : '"true"'},
        ${input.pullrequests ? ':pullrequests' : '"true"'},
        ${input.productionEnvironment ? ':production_environment' : 'NULL'},
        ${input.autoIdle ? ':auto_idle' : '1'},
        ${input.storageCalc ? ':storage_calc' : '1'},
        ${
  input.developmentEnvironmentsLimit
    ? ':development_environments_limit'
    : '5'
}
      );
    `,
  );

  const rows = await query(sqlClient, prep({
    ...input,
    privateKey: keyPair.private,
  }));
  const project = R.path([0, 0], rows);

  // Create a default group for this project
  let group;
  try {
    group = await dataSources.GroupModel.addGroup({
      name: `project-${project.name}`,
      attributes: {
        type: ['project-default-group'],
        'lagoon-projects': [project.id],
      },
    });
  } catch (err) {
    logger.error(`Could not create default project group for ${project.name}: ${err.message}`);
  }

  await SearchguardOperations(sqlClient, dataSources.GroupModel).syncGroup(`project-${project.name}`, project.id);

  // Find or create a user that has the public key linked to them
  const userRows = await query(
    sqlClient,
    sshKeySql.selectUserIdsBySshKeyFingerprint(getSshKeyFingerprint(keyPair.public)),
  );
  const userId = R.path([0, 'usid'], userRows);

  let user;
  if (!userId) {
    try {
      user = await dataSources.UserModel.addUser({
        email: `default-user@${project.name}`,
        username: `default-user@${project.name}`,
        comment: `autogenerated user for project ${project.name}`,
      });

      const keyParts = keyPair.public.split(' ');

      const {
        info: { insertId },
      } = await query(
        sqlClient,
        sshKeySql.insertSshKey({
          id: null,
          name: 'auto-add via api',
          keyValue: keyParts[1],
          keyType: keyParts[0],
          keyFingerprint: getSshKeyFingerprint(keyPair.public),
        }),
      );
      await query(sqlClient, sshKeySql.addSshKeyToUser({ sshKeyId: insertId, userId: user.id }));
    } catch (err) {
      logger.error(`Could not create default project user for ${project.name}: ${err.message}`);
    }
  } else {
    user = await dataSources.UserModel.loadUserById(userId);
  }

  // Add the user (with linked public key) to the default group as guest
  try {
    await dataSources.GroupModel.addUserToGroup(user, group, 'guest');
  } catch (err) {
    logger.error(`Could not link user to default projet group for ${project.name}: ${err.message}`);
  }

  // TODO
  // await SearchguardOperations(sqlClient).addProject(project);

  return project;
};

const deleteProject = async (
  root,
  { input: { project } },
  {
    sqlClient,
    hasPermission,
    dataSources,
  },
) => {
  // Will throw on invalid conditions
  const pid = await Helpers(sqlClient).getProjectIdByName(project);
  const project = await Helpers(sqlClient).getProjectById(pid);

  await hasPermission('project', 'delete', {
    project: pid,
  });

  const prep = prepare(sqlClient, 'CALL DeleteProject(:project)');
  await query(sqlClient, prep({ project }));

  // Remove the default group and user
  try {
    const group = await dataSources.GroupModel.loadGroupByName(`project-${project.name}`);
    await dataSources.GroupModel.deleteGroup(group.id);
    await SearchguardOperations(sqlClient, dataSources.GroupModel).deleteGroup(group.name)
  } catch (err) {
    logger.error(`Could not delete default group for project ${project.name}: ${err.message}`);
  }

  try {
    const user = await dataSources.UserModel.loadUserByUsername(`default-user@${project.name}`);
    await dataSources.UserModel.deleteUser(user.id);
  } catch (err) {
    logger.error(`Could not delete default user for project ${project.name}: ${err.message}`);
  }

  // TODO searchguard
  // await KeycloakOperations.deleteGroup(project);

  // try {
  //   // Delete SearchGuard Role for this project with the same name as the Project
  //   await searchguardClient.delete(`roles/${project}`);
  // } catch (err) {
  //   logger.error(`SearchGuard delete role error: ${err}`);
  //   throw new Error(`SearchGuard delete role error: ${err}`);
  // }
  // TODO: maybe check rows for changed result
  return 'success';
};

const updateProject = async (
  root,
  {
    input: {
      id,
      patch,
      patch: {
        name,
        gitUrl,
        privateKey,
        subfolder,
        activeSystemsDeploy,
        activeSystemsRemove,
        activeSystemsTask,
        branches,
        productionEnvironment,
        autoIdle,
        storageCalc,
        pullrequests,
        openshift,
        openshiftProjectPattern,
        developmentEnvironmentsLimit,
      },
    },
  },
  {
    sqlClient,
    hasPermission,
    dataSources,
  },
) => {
  await hasPermission('project', 'update', {
    project: id,
  });

  if (isPatchEmpty({ patch })) {
    throw new Error('input.patch requires at least 1 attribute');
  }

  if (typeof name === 'string') {
    if (validator.matches(name, /[^0-9a-z-]/)) {
      throw new Error(
        'Only lowercase characters, numbers and dashes allowed for name!',
      );
    }
  }

  const oldProject = await Helpers(sqlClient).getProjectById(id);

  // TODO If the privateKey changes, automatically remove the old one from the
  // default user and link the new one.

  // const originalProject = await Helpers(sqlClient).getProjectById(id);
  // const originalName = R.prop('name', originalProject);
  // const originalCustomer = parseInt(R.prop('customer', originalProject));

  // // If the project will be updating the `name` or `customer` fields, update Keycloak groups and users accordingly
  // if (typeof customer === 'number' && customer !== originalCustomer) {
  //   // Delete Keycloak users from original projects where given user ids do not have other access via `project_user` (projects where the user loses access if they lose customer access).
  //   await Helpers(sqlClient).mapIfNoDirectProjectAccess(
  //     id,
  //     originalCustomer,
  //     async ({
  //       keycloakUserId,
  //       keycloakUsername,
  //       keycloakGroupId,
  //       keycloakGroupName,
  //     }) => {
  //       await keycloakAdminClient.users.delFromGroup({
  //         id: keycloakUserId,
  //         groupId: keycloakGroupId,
  //       });
  //       logger.debug(
  //         `Removed Keycloak user ${keycloakUsername} from group "${keycloakGroupName}"`,
  //       );
  //     },
  //   );
  // }

  await query(
    sqlClient,
    Sql.updateProject({
      id,
      patch: {
        name,
        gitUrl,
        privateKey,
        subfolder,
        activeSystemsDeploy,
        activeSystemsRemove,
        activeSystemsTask,
        branches,
        productionEnvironment,
        autoIdle,
        storageCalc,
        pullrequests,
        openshift,
        openshiftProjectPattern,
        developmentEnvironmentsLimit,
      },
    }),
  );

  // Rename the default group and user
  if (patch.name && oldProject.name !== patch.name) {
    try {
      const group = await dataSources.GroupModel.loadGroupByName(`project-${oldProject.name}`);
      await dataSources.GroupModel.updateGroup({
        id: group.id,
        name: `project-${patch.name}`,
      });
    } catch (err) {
      logger.error(`Could not rename default group for project ${patch.name}: ${err.message}`);
    }

    try {
      const user = await dataSources.UserModel.loadUserByUsername(`default-user@${oldProject.name}`);
      await dataSources.UserModel.updateUser({
        id: user.id,
        email: `default-user@${patch.name}`,
        username: `default-user@${patch.name}`,
        comment: `autogenerated user for project ${patch.name}`,
      });
    } catch (err) {
      logger.error(`Could not rename default user for project ${patch.name}: ${err.message}`);
    }
  }

  // if (typeof name === 'string' && name !== originalName) {
  //   const groupId = await KeycloakOperations.findGroupIdByName(originalName);

  //   await keycloakAdminClient.groups.update({ id: groupId }, { name });
  //   logger.debug(
  //     `Renamed Keycloak group ${groupId} from "${originalName}" to "${name}"`,
  //   );
  // }

  // if (typeof customer === 'number' && customer !== originalCustomer) {
  //   // Add Keycloak users to new projects where given user ids do not have other access via `project_user` (projects where the user loses access if they lose customer access).
  //   await Helpers(sqlClient).mapIfNoDirectProjectAccess(
  //     id,
  //     customer,
  //     async ({
  //       keycloakUserId,
  //       keycloakUsername,
  //       keycloakGroupId,
  //       keycloakGroupName,
  //     }) => {
  //       await keycloakAdminClient.users.addToGroup({
  //         id: keycloakUserId,
  //         groupId: keycloakGroupId,
  //       });
  //       logger.debug(
  //         `Added Keycloak user ${keycloakUsername} to group "${keycloakGroupName}"`,
  //       );
  //     },
  //   );
  // }

  return Helpers(sqlClient).getProjectById(id);
};

const deleteAllProjects = async (
  root,
  args,
  { sqlClient, hasPermission },
) => {
  await hasPermission('project', 'deleteAll');

  const projectNames = await Helpers(sqlClient).getAllProjectNames();

  await query(sqlClient, Sql.truncateProject());

  for (const name of projectNames) {
    await KeycloakOperations.deleteGroup(name);
  }

  // TODO: Check rows for success
  return 'success';
};

const Resolvers /* : ResolversObj */ = {
  deleteProject,
  addProject,
  getProjectByName,
  getProjectByGitUrl,
  getProjectByEnvironmentId,
  getAllProjects,
  updateProject,
  deleteAllProjects,
};

module.exports = Resolvers;
<commit_msg>Fix duplicate variable name in deleteProject<commit_after>// @flow

const R = require('ramda');
const validator = require('validator');
const sshpk = require('sshpk');
const { keycloakAdminClient } = require('../../clients/keycloakClient');
const searchguardClient = require('../../clients/searchguardClient');
const logger = require('../../logger');
const {
  inClause,
  prepare,
  query,
  whereAnd,
  isPatchEmpty,
} = require('../../util/db');

const Helpers = require('./helpers');
const KeycloakOperations = require('./keycloak');
const { SearchguardOperations } = require('../group/searchguard');
const Sql = require('./sql');
const { generatePrivateKey, getSshKeyFingerprint } = require('../sshKey');
const sshKeySql = require('../sshKey/sql');

/* ::

import type {ResolversObj} from '../';

*/

const removePrivateKey = R.assoc('privateKey', null);

const getAllProjects = async (
  root,
  args,
  {
    sqlClient,
    hasPermission,
    dataSources,
    keycloakGrant,
  },
) => {
  let where;
  try {
    await hasPermission('project', 'viewAll');

    where = whereAnd([
      args.createdAfter ? 'created >= :created_after' : '',
      args.gitUrl ? 'git_url = :git_url' : '',
    ]);
  } catch (err) {
    if (!keycloakGrant) {
      logger.warn('No grant available for getAllProjects');
      return [];
    }

    const userProjectIds = await dataSources.UserModel.getAllProjectsIdsForUser({
      id: keycloakGrant.access_token.content.sub,
    });

    where = whereAnd([
      args.createdAfter ? 'created >= :created_after' : '',
      args.gitUrl ? 'git_url = :git_url' : '',
      inClause('id', userProjectIds),
    ]);
  }

  const order = args.order ? ` ORDER BY ${R.toLower(args.order)} ASC` : '';

  const prep = prepare(sqlClient, `SELECT * FROM project ${where}${order}`);
  const rows = await query(sqlClient, prep(args));

  return rows;
};

const getProjectByEnvironmentId = async (
  { id: eid },
  args,
  {
    sqlClient,
    hasPermission,
  },
) => {
  const prep = prepare(
    sqlClient,
    `SELECT
        p.*
      FROM environment e
      JOIN project p ON e.project = p.id
      WHERE e.id = :eid
      LIMIT 1
    `,
  );

  const rows = await query(sqlClient, prep({ eid }));

  const project = rows[0];

  await hasPermission('project', 'view', {
    project: project.id,
  });

  try {
    await hasPermission('project', 'viewPrivateKey', {
      project: project.id,
    });

    return project;
  } catch (err) {
    return removePrivateKey(project);
  }
};

const getProjectByGitUrl = async (
  root,
  args,
  {
    sqlClient,
    hasPermission,
  },
) => {
  const str = `
      SELECT
        *
      FROM project
      WHERE git_url = :git_url
      LIMIT 1
    `;

  const prep = prepare(sqlClient, str);
  const rows = await query(sqlClient, prep(args));

  const project = rows[0];

  await hasPermission('project', 'view', {
    project: project.id,
  });

  try {
    await hasPermission('project', 'viewPrivateKey', {
      project: project.id,
    });

    return project;
  } catch (err) {
    return removePrivateKey(project);
  }
};

const getProjectByName = async (
  root,
  args,
  {
    sqlClient,
    hasPermission,
  },
) => {
  const str = `
      SELECT
        *
      FROM project
      WHERE name = :name
    `;

  const prep = prepare(sqlClient, str);

  const rows = await query(sqlClient, prep(args));
  const project = rows[0];

  await hasPermission('project', 'view', {
    project: project.id,
  });

  try {
    await hasPermission('project', 'viewPrivateKey', {
      project: project.id,
    });

    return project;
  } catch (err) {
    return removePrivateKey(project);
  }
};

const addProject = async (
  root,
  { input },
  {
    hasPermission,
    sqlClient,
    dataSources,
  },
) => {
  await hasPermission('project', 'add');

  if (validator.matches(input.name, /[^0-9a-z-]/)) {
    throw new Error(
      'Only lowercase characters, numbers and dashes allowed for name!',
    );
  }

  let keyPair = {};
  try {
    const privateKey = R.cond([
      [R.isNil, generatePrivateKey],
      [R.isEmpty, generatePrivateKey],
      [R.T, sshpk.parsePrivateKey],
    ])(R.prop('privateKey', input));

    const publicKey = privateKey.toPublic();

    keyPair = {
      ...keyPair,
      private: R.replace(/\n/g, '\n', privateKey.toString('openssh')),
      public: publicKey.toString(),
    };
  } catch (err) {
    throw new Error(`There was an error with the privateKey: ${err.message}`);
  }

  const prep = prepare(
    sqlClient,
    `CALL CreateProject(
        :id,
        :name,
        :git_url,
        :private_key,
        ${input.subfolder ? ':subfolder' : 'NULL'},
        :openshift,
        ${
  input.openshiftProjectPattern ? ':openshift_project_pattern' : 'NULL'
},
        ${
  input.activeSystemsDeploy
    ? ':active_systems_deploy'
    : '"lagoon_openshiftBuildDeploy"'
},
        ${
  input.activeSystemsPromote
    ? ':active_systems_promote'
    : '"lagoon_openshiftBuildDeploy"'
},
        ${
  input.activeSystemsRemove
    ? ':active_systems_remove'
    : '"lagoon_openshiftRemove"'
},
        ${
  input.activeSystemsTask
    ? ':active_systems_task'
    : '"lagoon_openshiftJob"'
},
        ${input.branches ? ':branches' : '"true"'},
        ${input.pullrequests ? ':pullrequests' : '"true"'},
        ${input.productionEnvironment ? ':production_environment' : 'NULL'},
        ${input.autoIdle ? ':auto_idle' : '1'},
        ${input.storageCalc ? ':storage_calc' : '1'},
        ${
  input.developmentEnvironmentsLimit
    ? ':development_environments_limit'
    : '5'
}
      );
    `,
  );

  const rows = await query(sqlClient, prep({
    ...input,
    privateKey: keyPair.private,
  }));
  const project = R.path([0, 0], rows);

  // Create a default group for this project
  let group;
  try {
    group = await dataSources.GroupModel.addGroup({
      name: `project-${project.name}`,
      attributes: {
        type: ['project-default-group'],
        'lagoon-projects': [project.id],
      },
    });
  } catch (err) {
    logger.error(`Could not create default project group for ${project.name}: ${err.message}`);
  }

  await SearchguardOperations(sqlClient, dataSources.GroupModel).syncGroup(`project-${project.name}`, project.id);

  // Find or create a user that has the public key linked to them
  const userRows = await query(
    sqlClient,
    sshKeySql.selectUserIdsBySshKeyFingerprint(getSshKeyFingerprint(keyPair.public)),
  );
  const userId = R.path([0, 'usid'], userRows);

  let user;
  if (!userId) {
    try {
      user = await dataSources.UserModel.addUser({
        email: `default-user@${project.name}`,
        username: `default-user@${project.name}`,
        comment: `autogenerated user for project ${project.name}`,
      });

      const keyParts = keyPair.public.split(' ');

      const {
        info: { insertId },
      } = await query(
        sqlClient,
        sshKeySql.insertSshKey({
          id: null,
          name: 'auto-add via api',
          keyValue: keyParts[1],
          keyType: keyParts[0],
          keyFingerprint: getSshKeyFingerprint(keyPair.public),
        }),
      );
      await query(sqlClient, sshKeySql.addSshKeyToUser({ sshKeyId: insertId, userId: user.id }));
    } catch (err) {
      logger.error(`Could not create default project user for ${project.name}: ${err.message}`);
    }
  } else {
    user = await dataSources.UserModel.loadUserById(userId);
  }

  // Add the user (with linked public key) to the default group as guest
  try {
    await dataSources.GroupModel.addUserToGroup(user, group, 'guest');
  } catch (err) {
    logger.error(`Could not link user to default projet group for ${project.name}: ${err.message}`);
  }

  // TODO
  // await SearchguardOperations(sqlClient).addProject(project);

  return project;
};

const deleteProject = async (
  root,
  { input: { project: projectName } },
  {
    sqlClient,
    hasPermission,
    dataSources,
  },
) => {
  // Will throw on invalid conditions
  const pid = await Helpers(sqlClient).getProjectIdByName(projectName);
  const project = await Helpers(sqlClient).getProjectById(pid);

  await hasPermission('project', 'delete', {
    project: pid,
  });

  const prep = prepare(sqlClient, 'CALL DeleteProject(:project)');
  await query(sqlClient, prep({ project }));

  // Remove the default group and user
  try {
    const group = await dataSources.GroupModel.loadGroupByName(`project-${project.name}`);
    await dataSources.GroupModel.deleteGroup(group.id);
    await SearchguardOperations(sqlClient, dataSources.GroupModel).deleteGroup(group.name);
  } catch (err) {
    logger.error(`Could not delete default group for project ${project.name}: ${err.message}`);
  }

  try {
    const user = await dataSources.UserModel.loadUserByUsername(`default-user@${project.name}`);
    await dataSources.UserModel.deleteUser(user.id);
  } catch (err) {
    logger.error(`Could not delete default user for project ${project.name}: ${err.message}`);
  }

  // TODO searchguard
  // await KeycloakOperations.deleteGroup(project);

  // try {
  //   // Delete SearchGuard Role for this project with the same name as the Project
  //   await searchguardClient.delete(`roles/${project}`);
  // } catch (err) {
  //   logger.error(`SearchGuard delete role error: ${err}`);
  //   throw new Error(`SearchGuard delete role error: ${err}`);
  // }
  // TODO: maybe check rows for changed result
  return 'success';
};

const updateProject = async (
  root,
  {
    input: {
      id,
      patch,
      patch: {
        name,
        gitUrl,
        privateKey,
        subfolder,
        activeSystemsDeploy,
        activeSystemsRemove,
        activeSystemsTask,
        branches,
        productionEnvironment,
        autoIdle,
        storageCalc,
        pullrequests,
        openshift,
        openshiftProjectPattern,
        developmentEnvironmentsLimit,
      },
    },
  },
  {
    sqlClient,
    hasPermission,
    dataSources,
  },
) => {
  await hasPermission('project', 'update', {
    project: id,
  });

  if (isPatchEmpty({ patch })) {
    throw new Error('input.patch requires at least 1 attribute');
  }

  if (typeof name === 'string') {
    if (validator.matches(name, /[^0-9a-z-]/)) {
      throw new Error(
        'Only lowercase characters, numbers and dashes allowed for name!',
      );
    }
  }

  const oldProject = await Helpers(sqlClient).getProjectById(id);

  // TODO If the privateKey changes, automatically remove the old one from the
  // default user and link the new one.

  // const originalProject = await Helpers(sqlClient).getProjectById(id);
  // const originalName = R.prop('name', originalProject);
  // const originalCustomer = parseInt(R.prop('customer', originalProject));

  // // If the project will be updating the `name` or `customer` fields, update Keycloak groups and users accordingly
  // if (typeof customer === 'number' && customer !== originalCustomer) {
  //   // Delete Keycloak users from original projects where given user ids do not have other access via `project_user` (projects where the user loses access if they lose customer access).
  //   await Helpers(sqlClient).mapIfNoDirectProjectAccess(
  //     id,
  //     originalCustomer,
  //     async ({
  //       keycloakUserId,
  //       keycloakUsername,
  //       keycloakGroupId,
  //       keycloakGroupName,
  //     }) => {
  //       await keycloakAdminClient.users.delFromGroup({
  //         id: keycloakUserId,
  //         groupId: keycloakGroupId,
  //       });
  //       logger.debug(
  //         `Removed Keycloak user ${keycloakUsername} from group "${keycloakGroupName}"`,
  //       );
  //     },
  //   );
  // }

  await query(
    sqlClient,
    Sql.updateProject({
      id,
      patch: {
        name,
        gitUrl,
        privateKey,
        subfolder,
        activeSystemsDeploy,
        activeSystemsRemove,
        activeSystemsTask,
        branches,
        productionEnvironment,
        autoIdle,
        storageCalc,
        pullrequests,
        openshift,
        openshiftProjectPattern,
        developmentEnvironmentsLimit,
      },
    }),
  );

  // Rename the default group and user
  if (patch.name && oldProject.name !== patch.name) {
    try {
      const group = await dataSources.GroupModel.loadGroupByName(`project-${oldProject.name}`);
      await dataSources.GroupModel.updateGroup({
        id: group.id,
        name: `project-${patch.name}`,
      });
    } catch (err) {
      logger.error(`Could not rename default group for project ${patch.name}: ${err.message}`);
    }

    try {
      const user = await dataSources.UserModel.loadUserByUsername(`default-user@${oldProject.name}`);
      await dataSources.UserModel.updateUser({
        id: user.id,
        email: `default-user@${patch.name}`,
        username: `default-user@${patch.name}`,
        comment: `autogenerated user for project ${patch.name}`,
      });
    } catch (err) {
      logger.error(`Could not rename default user for project ${patch.name}: ${err.message}`);
    }
  }

  // if (typeof name === 'string' && name !== originalName) {
  //   const groupId = await KeycloakOperations.findGroupIdByName(originalName);

  //   await keycloakAdminClient.groups.update({ id: groupId }, { name });
  //   logger.debug(
  //     `Renamed Keycloak group ${groupId} from "${originalName}" to "${name}"`,
  //   );
  // }

  // if (typeof customer === 'number' && customer !== originalCustomer) {
  //   // Add Keycloak users to new projects where given user ids do not have other access via `project_user` (projects where the user loses access if they lose customer access).
  //   await Helpers(sqlClient).mapIfNoDirectProjectAccess(
  //     id,
  //     customer,
  //     async ({
  //       keycloakUserId,
  //       keycloakUsername,
  //       keycloakGroupId,
  //       keycloakGroupName,
  //     }) => {
  //       await keycloakAdminClient.users.addToGroup({
  //         id: keycloakUserId,
  //         groupId: keycloakGroupId,
  //       });
  //       logger.debug(
  //         `Added Keycloak user ${keycloakUsername} to group "${keycloakGroupName}"`,
  //       );
  //     },
  //   );
  // }

  return Helpers(sqlClient).getProjectById(id);
};

const deleteAllProjects = async (
  root,
  args,
  { sqlClient, hasPermission },
) => {
  await hasPermission('project', 'deleteAll');

  const projectNames = await Helpers(sqlClient).getAllProjectNames();

  await query(sqlClient, Sql.truncateProject());

  for (const name of projectNames) {
    await KeycloakOperations.deleteGroup(name);
  }

  // TODO: Check rows for success
  return 'success';
};

const Resolvers /* : ResolversObj */ = {
  deleteProject,
  addProject,
  getProjectByName,
  getProjectByGitUrl,
  getProjectByEnvironmentId,
  getAllProjects,
  updateProject,
  deleteAllProjects,
};

module.exports = Resolvers;
<|endoftext|>