<commit_before>function main()
{
  // Create the "engine" object with the main components
  TANK.addComponents("InputManager, CollisionManager, RenderManager, GameLogic");

  // Point the render manager's context to the canvas one
  // Would be nice not to require this somehow?
  TANK.RenderManager.context = document.getElementById("screen").getContext("2d");
  TANK.InputManager.context = document.getElementById("stage");

  // Create a bullet object prefab
  TANK.addPrefab("Bullet",
  {
    "ColoredBox":
    {
      width: 5,
      height: 5
    },
    "Velocity":
    {},
    "Collider":
    {
      width: 5,
      height: 5
    },
    "DeleteOnCollide":
    {},
    "DeleteOutOfBounds":
    {},
    "InvokeOnCollide":
    {
      invokeOther: "TakeDamage",
      argOther: 1
    }
  });

  // Create background object
  var bg = TANK.createEntity().addComponents("ColoredBox");
  bg.ColoredBox.width = 640;
  bg.ColoredBox.height = 480;
  bg.ColoredBox.color = "#fff";
  bg.ColoredBox.zdepth = -1;
  bg.ColoredBox.centered = false;
  TANK.addEntity(bg);

  // Create a player object
  var player = TANK.createEntity()
    .addComponents("Text, Image, TopDownMovement, RotateController, ObjectSpawner, Collider, Health, CustomUpdate")
    .attr("Pos2D",
  {
    x: 150,
    y: 100
  })
    .attr("Image",
  {
    imagePath: "res/BlueBall.png"
  })
    .attr("Health",
  {
    max: 20,
    value: 20
  })
    .attr("Text",
  {
    color: "#000",
    zdepth: 1,
    offsetX: -5,
    offsetY: 4
  })
    .attr("ObjectSpawner",
  {
    objectPrefab: "Bullet",
    triggerKey: TANK.SPACE
  })
    .attr("CustomUpdate",
  {
    func: function (dt)
    {
      this.Text.text = this.Health.value;
    }
  });
  TANK.addEntity(player, "Player");

  // Create AI object
  var ai = TANK.createEntity()
    .addComponents("Text, Image, KlangAI, ObjectSpawner, Collider, Health, CustomUpdate")
    .attr("Pos2D",
  {
    x: 450,
    y: 400
  })
    .attr("Image",
  {
    imagePath: "res/RedBall.png"
  })
    .attr("Health",
  {
    max: 20,
    value: 20
  })
    .attr("Text",
  {
    color: "#000",
    zdepth: 1,
    offsetX: -5,
    offsetY: 4
  })
    .attr("ObjectSpawner",
  {
    objectPrefab: "Bullet"
  })
    .attr("CustomUpdate",
  {
    func: function (dt)
    {
      this.Text.text = this.Health.value;
    }
  });
  TANK.addEntity(ai, "AI");

  // Create walls around edges
  var obj;
  obj = TANK.createEntity().addComponents("ColoredBox, Collider").attr("Pos2D",
  {
    x: 640 / 2,
    y: -25
  }).attr("Collider",
  {
    isStatic: true,
    width: 640
  });
  TANK.addEntity(obj);

  obj = TANK.createEntity().addComponents("ColoredBox, Collider").attr("Pos2D",
  {
    x: 640 / 2,
    y: 480 + 25
  }).attr("Collider",
  {
    isStatic: true,
    width: 640
  });
  TANK.addEntity(obj);

  obj = TANK.createEntity().addComponents("ColoredBox, Collider").attr("Pos2D",
  {
    x: -25,
    y: 480 / 2
  }).attr("Collider",
  {
    isStatic: true,
    height: 480
  });
  TANK.addEntity(obj);

  obj = TANK.createEntity().addComponents("ColoredBox, Collider").attr("Pos2D",
  {
    x: 640 + 25,
    y: 480 / 2
  }).attr("Collider",
  {
    isStatic: true,
    height: 480
  });
  TANK.addEntity(obj);

  obj = TANK.createEntity().addComponents("ColoredBox, Collider").attr("Pos2D",
  {
    x: 100,
    y: 150
  }).attr("Collider",
  {
    isStatic: true
  });
  TANK.addEntity(obj);

  obj = TANK.createEntity().addComponents("ColoredBox, Collider").attr("Pos2D",
  {
    x: 150,
    y: 150
  }).attr("Collider",
  {
    isStatic: true
  });
  TANK.addEntity(obj);

  obj = TANK.createEntity().addComponents("ColoredBox, Collider").attr("Pos2D",
  {
    x: 200,
    y: 150
  }).attr("Collider",
  {
    isStatic: true
  });
  TANK.addEntity(obj);

  obj = TANK.createEntity().addComponents("ColoredBox, Collider").attr("Pos2D",
  {
    x: 200,
    y: 100
  }).attr("Collider",
  {
    isStatic: true
  });
  TANK.addEntity(obj);

  obj = TANK.createEntity().addComponents("ColoredBox, Collider").attr("Pos2D",
  {
    x: 400,
    y: 350
  }).attr("Collider",
  {
    isStatic: true
  });
  TANK.addEntity(obj);

  obj = TANK.createEntity().addComponents("ColoredBox, Collider").attr("Pos2D",
  {
    x: 450,
    y: 350
  }).attr("Collider",
  {
    isStatic: true
  });
  TANK.addEntity(obj);

  obj = TANK.createEntity().addComponents("ColoredBox, Collider").attr("Pos2D",
  {
    x: 500,
    y: 350
  }).attr("Collider",
  {
    isStatic: true
  });
  TANK.addEntity(obj);

  obj = TANK.createEntity().addComponents("ColoredBox, Collider").attr("Pos2D",
  {
    x: 500,
    y: 400
  }).attr("Collider",
  {
    isStatic: true
  });
  TANK.addEntity(obj);

  TANK.start();
}

// Custom game logic component to manage general state of game
TANK.registerComponent("GameLogic")

.initialize(function ()
{
  TANK.addEventListener("OnEnterFrame", this);
})

.destruct(function ()
{
  TANK.removeEventListener("OnEnterFrame", this);
})

.addFunction("OnEnterFrame", function (dt)
{
  if (!TANK.getNamedEntity("Player") || !TANK.getNamedEntity("AI"))
  {
    TANK.reset();
  }
});

// Custom component to implement AI for the other player
TANK.registerComponent("KlangAI")

.requires("Pos2D")

.initialize(function ()
{
  this._desiredAngle = 0;
  this._rotateTimer = 0;
  this._rotateAmount = 0.5;
  this._movementSpeed = 50;
  this._left = false;
  this._up = false;
  this._right = false;
  this._down = false;

  TANK.addEventListener("OnEnterFrame", this);
})

.destruct(function ()
{
  TANK.removeEventListener("OnEnterFrame", this);
})

.addFunction("OnEnterFrame", function (dt)
{
  this._rotateTimer -= dt;

  var player = TANK.getNamedEntity("Player");
  if (!player)
    return;

  var playerPos = player.Pos2D;
  var pos = this.parent.Pos2D;
  var gun = this.parent.ObjectSpawner;

  // Shoot randomly
  if (Math.random() < 0.05)
    gun.spawn();

  // Move around semi-randomly
  if (Math.random() < 0.01)
    this._up = true;
  if (Math.random() < 0.01)
    this._left = true;
  if (Math.random() < 0.01)
    this._right = true;
  if (Math.random() < 0.01)
    this._down = true;
  if (Math.random() < 0.01)
    this._up = false;
  if (Math.random() < 0.01)
    this._left = false;
  if (Math.random() < 0.01)
    this._right = false;
  if (Math.random() < 0.01)
    this._down = false;

  // Apply movement
  if (this._left)
    pos.x -= this._movementSpeed * dt;
  if (this._up)
    pos.y -= this._movementSpeed * dt;
  if (this._right)
    pos.x += this._movementSpeed * dt;
  if (this._down)
    pos.y += this._movementSpeed * dt;

  // Aim at player
  var angle = TANK.Math.angleToPoint([pos.x, pos.y], [playerPos.x, playerPos.y]);
  if (this._rotateTimer <= this._rotateAmount)
  {
    if (pos.rotation < angle)
      pos.rotation += 0.1;
    else
      pos.rotation -= 0.1;
  }
  if (this._rotateTimer <= 0)
  {
    this._rotateTimer = this._rotateAmount + Math.random() * 3 + 0.5;
    this._rotateAmount = 0.1 + Math.random() * 1;
  }
});<commit_msg>Remove use of deprecated attr in Klang sample<commit_after>function main()
{
  // Create the "engine" object with the main components
  TANK.addComponents("InputManager, CollisionManager, RenderManager, GameLogic");

  // Point the render manager's context to the canvas one
  // Would be nice not to require this somehow?
  TANK.RenderManager.context = document.getElementById("screen").getContext("2d");
  TANK.InputManager.context = document.getElementById("stage");

  // Create a bullet object prefab
  TANK.addPrefab("Bullet",
  {
    "ColoredBox":
    {
      width: 5,
      height: 5
    },
    "Velocity":
    {},
    "Collider":
    {
      width: 5,
      height: 5
    },
    "DeleteOnCollide":
    {},
    "DeleteOutOfBounds":
    {},
    "InvokeOnCollide":
    {
      invokeOther: "TakeDamage",
      argOther: 1
    }
  });

  // Create background object
  var bg = TANK.createEntity().addComponents("ColoredBox");
  bg.ColoredBox.width = 640;
  bg.ColoredBox.height = 480;
  bg.ColoredBox.color = "#fff";
  bg.ColoredBox.zdepth = -1;
  bg.ColoredBox.centered = false;
  TANK.addEntity(bg);

  // Create a player object
  var player = TANK.createEntity("Text, Image, TopDownMovement, RotateController, ObjectSpawner, Collider, Health, CustomUpdate");
  player.Pos2D.x = 150;
  player.Pos2D.y = 100;
  player.Image.imagePath = "res/BlueBall.png";
  player.Health.max = 20;
  player.Health.value = 20;
  player.Text.color = "#000";
  player.Text.zdepth = 1;
  player.Text.offsetX = -5;
  player.Text.offsetY = 4;
  player.ObjectSpawner.objectPrefab = "Bullet";
  player.ObjectSpawner.triggerKey = TANK.SPACE;
  player.CustomUpdate.func = function (dt)
  {
    this.Text.text = this.Health.value;
  }
  TANK.addEntity(player, "Player");

  // Create AI object
  var ai = TANK.createEntity("Text, Image, KlangAI, ObjectSpawner, Collider, Health, CustomUpdate");
  ai.Pos2D.x = 450;
  ai.Pos2D.y = 400;
  ai.Image.imagePath = "res/RedBall.png";
  ai.Health.max = 20;
  ai.Health.value = 20;
  ai.Text.color = "#000";
  ai.Text.zdepth = 1;
  ai.Text.offsetX = -5;
  ai.Text.offsetY = 4;
  ai.ObjectSpawner.objectPrefab = "Bullet";
  ai.CustomUpdate.func = function (dt)
  {
    this.Text.text = this.Health.value;
  }
  TANK.addEntity(ai, "AI");

  // Create walls around edges
  var obj;
  obj = TANK.createEntity().addComponents("ColoredBox, Collider");
  obj.Pos2D.x = 640 / 2;
  obj.Pos2D.y = -25;
  obj.Collider.isStatic = true;
  obj.Collider.width = 640;
  TANK.addEntity(obj);

  obj = TANK.createEntity().addComponents("ColoredBox, Collider");
  obj.Pos2D.x = 640 / 2;
  obj.Pos2D.y = 480 + 25;
  obj.Collider.isStatic = true;
  obj.Collider.width = 640;
  TANK.addEntity(obj);

  obj = TANK.createEntity().addComponents("ColoredBox, Collider");
  obj.Pos2D.x = -25;
  obj.Pos2D.y = 480 / 2;
  obj.Collider.isStatic = true;
  obj.Collider.height = 480;
  TANK.addEntity(obj);

  obj = TANK.createEntity().addComponents("ColoredBox, Collider");
  obj.Pos2D.x = 640 + 25;
  obj.Pos2D.y = 480 / 2;
  obj.Collider.isStatic = true;
  obj.Collider.height = 480;
  TANK.addEntity(obj);

  obj = TANK.createEntity().addComponents("ColoredBox, Collider");
  obj.Pos2D.x = 100;
  obj.Pos2D.y = 150;
  obj.Collider.isStatic = true;
  TANK.addEntity(obj);

  obj = TANK.createEntity().addComponents("ColoredBox, Collider");
  obj.Pos2D.x = 150;
  obj.Pos2D.y = 150;
  obj.Collider.isStatic = true;
  TANK.addEntity(obj);

  obj = TANK.createEntity().addComponents("ColoredBox, Collider");
  obj.Pos2D.x = 200;
  obj.Pos2D.y = 150;
  obj.Collider.isStatic = true;
  TANK.addEntity(obj);

  obj = TANK.createEntity().addComponents("ColoredBox, Collider");
  obj.Pos2D.x = 200;
  obj.Pos2D.y = 100;
  obj.Collider.isStatic = true;
  TANK.addEntity(obj);

  obj = TANK.createEntity().addComponents("ColoredBox, Collider");
  obj.Pos2D.x = 400;
  obj.Pos2D.y = 350;
  obj.Collider.isStatic = true;
  TANK.addEntity(obj);

  obj = TANK.createEntity().addComponents("ColoredBox, Collider");
  obj.Pos2D.x = 450;
  obj.Pos2D.y = 350;
  obj.Collider.isStatic = true;
  TANK.addEntity(obj);

  obj = TANK.createEntity().addComponents("ColoredBox, Collider");
  obj.Pos2D.x = 500;
  obj.Pos2D.y = 350;
  obj.Collider.isStatic = true;
  TANK.addEntity(obj);

  obj = TANK.createEntity().addComponents("ColoredBox, Collider");
  obj.Pos2D.x = 500;
  obj.Pos2D.y = 400;
  obj.Collider.isStatic = true;
  TANK.addEntity(obj);

  TANK.start();
}

// Custom game logic component to manage general state of game
TANK.registerComponent("GameLogic")

.initialize(function ()
{
  this.addEventListener("OnEnterFrame", this.OnEnterFrame);
})

.destruct(function ()
{
  this.removeEventListener("OnEnterFrame", this.OnEnterFrame);
})

.addFunction("OnEnterFrame", function (dt)
{
  if (!TANK.getEntity("Player") || !TANK.getEntity("AI"))
  {
    TANK.reset();
  }
});

// Custom component to implement AI for the other player
TANK.registerComponent("KlangAI")

.requires("Pos2D")

.initialize(function ()
{
  this._desiredAngle = 0;
  this._rotateTimer = 0;
  this._rotateAmount = 0.5;
  this._movementSpeed = 50;
  this._left = false;
  this._up = false;
  this._right = false;
  this._down = false;

  this.addEventListener("OnEnterFrame", this.OnEnterFrame);
})

.destruct(function ()
{
  this.removeEventListener("OnEnterFrame", this.OnEnterFrame);
})

.addFunction("OnEnterFrame", function (dt)
{
  this._rotateTimer -= dt;

  var player = TANK.getEntity("Player");
  if (!player)
    return;

  var playerPos = player.Pos2D;
  var pos = this.parent.Pos2D;
  var gun = this.parent.ObjectSpawner;

  // Shoot randomly
  if (Math.random() < 0.05)
    gun.spawn();

  // Move around semi-randomly
  if (Math.random() < 0.01)
    this._up = true;
  if (Math.random() < 0.01)
    this._left = true;
  if (Math.random() < 0.01)
    this._right = true;
  if (Math.random() < 0.01)
    this._down = true;
  if (Math.random() < 0.01)
    this._up = false;
  if (Math.random() < 0.01)
    this._left = false;
  if (Math.random() < 0.01)
    this._right = false;
  if (Math.random() < 0.01)
    this._down = false;

  // Apply movement
  if (this._left)
    pos.x -= this._movementSpeed * dt;
  if (this._up)
    pos.y -= this._movementSpeed * dt;
  if (this._right)
    pos.x += this._movementSpeed * dt;
  if (this._down)
    pos.y += this._movementSpeed * dt;

  // Aim at player
  var angle = TANK.Math.angleToPoint([pos.x, pos.y], [playerPos.x, playerPos.y]);
  if (this._rotateTimer <= this._rotateAmount)
  {
    if (pos.rotation < angle)
      pos.rotation += 0.1;
    else
      pos.rotation -= 0.1;
  }
  if (this._rotateTimer <= 0)
  {
    this._rotateTimer = this._rotateAmount + Math.random() * 3 + 0.5;
    this._rotateAmount = 0.1 + Math.random() * 1;
  }
});<|endoftext|>


<commit_before>module.exports = {
  name: 'Radio Selector',
  description: 'Select a radio option',
  validation: {
    radioValidation: validation,
  },
  inputs: {
    radioSelector: {
      validation: {
        function: 'radioValidation',
        on: 'blur',
      },
      label: 'Select a radio',
      type: 'radio',
      options: [
        { label: 'red',
          value: 'red',
        },
        { label: 'blue',
          value: 'blue',
        },
        { label: 'green',
          value: 'green',
        },
        { label: 'yellow',
          value: 'yellow',
        },
      ],
      settings: {
        empty: false,
      },
    },
  },
  html: `{{radioSelector.label}}{% for option in radioSelector.options %}<input type="{{radioSelector.type}}" name={{radioSelector.name}} id="{{radioSelector.id}}" value="{{radioSelector.value}}" {% if option.value == radioSelector.value %}checked{% endif %}>{{option.label}}{% endfor %}`,
};<commit_msg>added require for the validation script<commit_after>const validation = require('./lib/validation.js');

module.exports = {
  name: 'Radio Selector',
  description: 'Select a radio option',
  validation: {
    radioValidation: validation,
  },
  inputs: {
    radioSelector: {
      validation: {
        function: 'radioValidation',
        on: 'blur',
      },
      label: 'Select a radio',
      type: 'radio',
      options: [
        { label: 'red',
          value: 'red',
        },
        { label: 'blue',
          value: 'blue',
        },
        { label: 'green',
          value: 'green',
        },
        { label: 'yellow',
          value: 'yellow',
        },
      ],
      settings: {
        empty: false,
      },
    },
  },
  html: `{{radioSelector.label}}{% for option in radioSelector.options %}<input type="{{radioSelector.type}}" name={{radioSelector.name}} id="{{radioSelector.id}}" value="{{radioSelector.value}}" {% if option.value == radioSelector.value %}checked{% endif %}>{{option.label}}{% endfor %}`,
};<|endoftext|>


<commit_before>/**
 * @license
 * Google Analytics C3 Adapter <https://github.com/monkdev/google-analytics-c3-adapter>
 * Copyright 2014 Monk Development <http://monkdevelopment.com/>
 * Inspired by Lo-Dash 2.4.1 <http://lodash.com/>
 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
 * Available under MIT license <https://github.com/MonkDev/google-analytics-c3-adapter/blob/master/LICENSE.txt>
 */
(function(){"use strict";function e(e){var t=r(e),c=t?e.keys.map(function(e){return[e]}):[];return n(e),e.sources.forEach(function(n,r){n.rows.forEach(function(n){var a=o(e.callback)?e.callback(+n[1]):+n[1];t?c[r].push(a):c.push([n[0],a])})}),1===c.length?c.reduce(function(e,n){return e.concat(n)}):c}function n(e){if(!t(e.sources))throw new TypeError("Expects an array of data sources.");if(e.keys&&!t(e.keys))throw new TypeError("Expects an array of keys.");if(e.callback&&!o(e.callback))throw new TypeError("Expects callback to be a function.");if(e.sources.length>1&&!r(e))throw new RangeError("Expects keys to be specified for multiple data sources.");if(r(e)&&e.keys.length!==e.sources.length)throw new RangeError("If keys are provided, there must be an equal number of keys and data-sources.")}function r(e){return e.keys&&e.keys.length?!0:!1}function t(e){return e&&"object"==typeof e&&"number"==typeof e.length||!1}function o(e){return"function"==typeof e}var c={};c.columns=e,window.gac3=c,"function"==typeof define&&"object"==typeof define.amd&&define.amd&&define(function(){return c})}).call(this);<commit_msg>fixup! Fixes bug where single column data is flattened into a one dimensional array<commit_after>/**
 * @license
 * Google Analytics C3 Adapter <https://github.com/monkdev/google-analytics-c3-adapter>
 * Copyright 2014 Monk Development <http://monkdevelopment.com/>
 * Inspired by Lo-Dash 2.4.1 <http://lodash.com/>
 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
 * Available under MIT license <https://github.com/MonkDev/google-analytics-c3-adapter/blob/master/LICENSE.txt>
 */
(function(){"use strict";function e(e){var t=r(e),c=t?e.keys.map(function(e){return[e]}):[];return n(e),e.sources.forEach(function(n,r){n.rows.forEach(function(n){var a=o(e.callback)?e.callback(+n[1]):+n[1];t?c[r].push(a):c.push([n[0],a])})}),c}function n(e){if(!t(e.sources))throw new TypeError("Expects an array of data sources.");if(e.keys&&!t(e.keys))throw new TypeError("Expects an array of keys.");if(e.callback&&!o(e.callback))throw new TypeError("Expects callback to be a function.");if(e.sources.length>1&&!r(e))throw new RangeError("Expects keys to be specified for multiple data sources.");if(r(e)&&e.keys.length!==e.sources.length)throw new RangeError("If keys are provided, there must be an equal number of keys and data-sources.")}function r(e){return e.keys&&e.keys.length?!0:!1}function t(e){return e&&"object"==typeof e&&"number"==typeof e.length||!1}function o(e){return"function"==typeof e}var c={};c.columns=e,window.gac3=c,"function"==typeof define&&"object"==typeof define.amd&&define.amd&&define(function(){return c})}).call(this);<|endoftext|>


<commit_before>'use strict';

/**
 * api/controllers/Shorty.js
 *
 * Shorty model controller and routing logic. Only the bare minimum routes are
 * implemented.
 */

const router     = require('express').Router();
const models     = require('../models');
const config     = require('../../config');
const utils      = require('../utils');
const log        = config.logger.getLogger('Controllers::Shorty');
const Shorty     = models.shorty;
const Permutator = utils.permutator;

/* Controller objects */
const permutator = new Permutator();

/**
 * @GET
 * Retrieve all shorty resources.
 */
router.get('/', (req, res) => {
  log.debug('Attempting to retrieve all shorty resources');

  Shorty.find({})
    .then((shorties) => {
      res.status(200).json(shorties);
    })
    .catch((err) => {
      log.error(`Failed to retrieve all shorty resources: ${err.message}`);
      res.status(400).json({error: err.message});
    });
});

/**
 * @GET
 * Retrieve a shorty resource by its MongoDB ID attribute.
 */
router.get('/:id', (req, res) => {
  let shortyID = req.params.id;
  log.debug(`Attempting to retrieve shorty resource: ${shortyID}`)

  Shorty.findById(shortyID)
    .then((shorty) => {
      if (shorty) {
        res.status(200).json(shorty);
      } else {
        log.error('Resource not found');
        res.status(404).json({
          error: 'Resource not found'
        });
      }
    })
    .catch((err) => {
      log.error(`Failed to query for shorty resource: ${err.message}`);
      res.status(400).json({
        error: err.message
      });
    });
});

/**
 * @POST
 * Create a shorty resource.
 */
router.post('/', (req, res) => {
  let body = req.body;
  // log.debug(`Attempting to create shorty resource: ${JSON.stringify(body)}`);

  Shorty.findOne({
      url: body.url
    })
    .then((shorty) => {
      if (shorty) {
        log.warn('Resource already exists');
        res.status(422).json({
          error: 'Resource already exists'
        });

        return;
      } else {
        let shorty = new Shorty({
          url: body.url,
          uid: permutator.next()
        });

        return new Promise((resolve, reject) => {
          shorty.save()
            .then((shorty) => {
              resolve(shorty);
            })
            .catch((err) => {
              reject(err);
            });
        });
      }
    })
    .then((shorty) => {
      res.status(200).json(shorty);
    })
    .catch((err) => {
      log.error(`Failed to create shorty resource: ${err.message}`);
      res.status(400).json({
        error: err.message
      });
    });

  return;
});

/**
 * @PATCH
 * Updates an existing shorty resource.
 */
router.patch('/:id', (req, res) => {
  let shortyID = req.params.id;
  let body     = req.body;
  log.debug(`Attempting to update shorty resource: ${shortyID}`);

  Shorty.findByIdAndUpdate(shortyID, {
      $set: body
    })
    .then((shorty) => {
      if (shorty) {
        res.status(200).json(shorty);
      } else {
        log.error('Resource not found');
        res.status(404).json({
          error: 'Resource not found'
        });
      }
    })
    .catch((err) => {
      log.error(`Failed to query for shorty resource: ${err.message}`);
      res.status(400).json({
        error: err.message
      });
    });

  return;
});

/**
 * @DELETE
 * Delete a shorty resource by its MongoDB ID attribute.
 */
router.delete('/:id', (req, res) => {
  let shortyID = req.params.id;
  log.debug(`Attempting to delete shorty resource: ${shortyID}`)

  Shorty.findByIdAndRemove(shortyID)
    .then((shorty) => {
      if (shorty) {
        res.status(200).json(shorty);
      } else {
        log.debug('Resource not found');
        res.status(404).json({
          error: 'Resource not found'
        });
      }
    })
    .catch((err) => {
      log.error(`Failed to query for shorty resource: ${err.message}`);
      res.status(400).json({
        error: err.message
      });
    });

  return;
});

module.exports = router;
<commit_msg>Implement sanitization middleware for shorty controller<commit_after>'use strict';

/**
 * api/controllers/Shorty.js
 *
 * Shorty model controller and routing logic. Only the bare minimum routes are
 * implemented.
 */

const router     = require('express').Router();
const validator  = require('validator');
const _          = require('lodash');
const models     = require('../models');
const config     = require('../../config');
const utils      = require('../utils');
const log        = config.logger.getLogger('Controllers::Shorty');
const Shorty     = models.shorty;
const Permutator = utils.permutator;

/* Controller objects */
const permutator = new Permutator();

/* Middleware */

/**
 * Performs a sanitization on a Shorty resource object and strips out any
 * necessary fields.
 *
 * @return {Object}
 */
const sanitizeResource = (req, res, next) => {
  log.info('Performing sanitization on resource');
  let resource = req.body;

  // Strip out necessary attributes
  resource = _.pick(resource, ['url']);

  // Sanitize values
  _.forEach(resource, (k, v) => {
    resource[v] = validator.trim(k);
  });

  log.info(`Sanitized resource: ${JSON.stringify(resource)}`);
  req.resource = resource;
  next();
};

/* Routes*/

/**
 * @GET
 * Retrieve all shorty resources.
 */
router.get('/', (req, res) => {
  log.debug('Attempting to retrieve all shorty resources');

  Shorty.find({})
    .then((shorties) => {
      res.status(200).json(shorties);
    })
    .catch((err) => {
      log.error(`Failed to retrieve all shorty resources: ${err.message}`);
      res.status(400).json({error: err.message});
    });
});

/**
 * @GET
 * Retrieve a shorty resource by its MongoDB ID attribute.
 */
router.get('/:id', (req, res) => {
  let shortyID = req.params.id;
  log.debug(`Attempting to retrieve shorty resource: ${shortyID}`)

  Shorty.findById(shortyID)
    .then((shorty) => {
      if (shorty) {
        res.status(200).json(shorty);
      } else {
        log.error('Resource not found');
        res.status(404).json({
          error: 'Resource not found'
        });
      }
    })
    .catch((err) => {
      log.error(`Failed to query for shorty resource: ${err.message}`);
      res.status(400).json({
        error: err.message
      });
    });
});

/**
 * @POST
 * Create a shorty resource.
 */
router.post('/', sanitizeResource, (req, res) => {
  let resource = req.resource;
  log.debug(`Attempting to create shorty resource: ${JSON.stringify(resource)}`);

  Shorty.findOne({
      url: resource.url
    })
    .then((shorty) => {
      if (shorty) {
        log.warn('Resource already exists');
        res.status(422).json({
          error: 'Resource already exists'
        });

        return;
      } else {
        let shorty = new Shorty({
          url: resource.url,
          uid: permutator.next()
        });

        return new Promise((resolve, reject) => {
          shorty.save()
            .then((shorty) => {
              resolve(shorty);
            })
            .catch((err) => {
              reject(err);
            });
        });
      }
    })
    .then((shorty) => {
      res.status(200).json(shorty);
    })
    .catch((err) => {
      log.error(`Failed to create shorty resource: ${err.message}`);
      res.status(400).json({
        error: err.message
      });
    });

  return;
});

/**
 * @PATCH
 * Updates an existing shorty resource.
 */
router.patch('/:id', sanitizeResource, (req, res) => {
  let shortyID = req.params.id;
  let resource = req.resource;
  log.debug(`Attempting to update shorty resource: ${shortyID}`);

  Shorty.findByIdAndUpdate(shortyID, {
      $set: resource
    })
    .then((shorty) => {
      if (shortyz) {
        res.status(200).json(shorty);
      } else {
        log.error('Resource not found');
        res.status(404).json({
          error: 'Resource not found'
        });
      }
    })
    .catch((err) => {
      log.error(`Failed to query for shorty resource: ${err.message}`);
      res.status(400).json({
        error: err.message
      });
    });

  return;
});

/**
 * @DELETE
 * Delete a shorty resource by its MongoDB ID attribute.
 */
router.delete('/:id', (req, res) => {
  let shortyID = req.params.id;
  log.debug(`Attempting to delete shorty resource: ${shortyID}`)

  Shorty.findByIdAndRemove(shortyID)
    .then((shorty) => {
      if (shorty) {
        res.status(200).json(shorty);
      } else {
        log.debug('Resource not found');
        res.status(404).json({
          error: 'Resource not found'
        });
      }
    })
    .catch((err) => {
      log.error(`Failed to query for shorty resource: ${err.message}`);
      res.status(400).json({
        error: err.message
      });
    });

  return;
});

module.exports = router;
<|endoftext|>


<commit_before>/**
 * Notes on the map positioning & animation:
 *
 * - Placing the map in the correct screen location is a pain, because map zoom is very coarse.
 *   We measure the bounds of what we want to fit, then set the map to contain that. From there
 *   we transform the map as a whole to increase its size to the exact dimensions needed.
 * - When you apply more than one GSAP tween to an element at once, it gets pretty weird about it.
 *   There must be a way to make it work, or we could just avoid their documentation and work around it.
 */

(function($, STS) {

STS.TOTAL_MAPS_COUNT = 0; // init, set after maps have loaded to DOM and used by electorate highlighting to fire across all maps

var DEFAULT_COORDS = [-28.043981, 134.912109];
var DEFAULT_ZOOM = 4;

// used to force leaflet to make the whole map visible
if (window.L) {
  var COUNTRY_BOUNDS = L.latLngBounds(L.latLng(-44.205835, 154.841309), L.latLng(-8.795678, 111.708984));
}

// these correspond to opacities 0.1 - 0.9 in 0.1 increments. 0 is below first count, 1 is above last.
var AREA_SHADING_THRESHOLDS = {
  'all' : [1, 2, 3, 8, 10, 50, 80, 150, 200],
  'emails' : [1, 2, 3, 8, 10, 50, 80, 150, 200],
  'views' : [1, 2, 3, 8, 10, 50, 80, 150, 200],
  'tweets' : [1, 2, 3, 8, 10, 50, 80, 150, 200],
  'facebooks' : [1, 2, 3, 8, 10, 50, 80, 150, 200]
  // 'calls' : [1, 2, 3, 8, 10, 50, 80, 150, 200],
};
var MAP_EVENT_COLORS = {
  all : '#ffffff',
  visits : '#E58231',     // orange

  views : '#E58231',      // orange
  calls : '#36C48F',      // dark green
  emails : '#45CBA1',     // light green

  tweets : '#59ADEB',     // light blue
  facebooks : '#3c5a96',  // dark blue
  googles : '#db585a'     // red
};

// all maps are the same, just add more things to this selector & adjust after creating
var mapEls = '.australia-map.status';
var maps = [];
var mapShapes = [];
var MAPS_DISABLED = false;

var winW = $(window).width();
var winH = $(window).height();

//------------------------------------------------------------------------------------------------------------------------------------------------------------
// init
//------------------------------------------------------------------------------------------------------------------------------------------------------------

var MAP_INIT_CALLBACKS = {
  '.status' : function(el) {
    STS.anim.map.enter(el);
  }
};

// load up map when page is ready
$(initMaps);

// watch resize for map positioning
$(window).on('resize', debounce(function(e) {
  winW = $(window).width();
  winH = $(window).height();

  $.each(maps, function(i, map) {
    exactFitMap(map, COUNTRY_BOUNDS);
  });
}));

//------------------------------------------------------------------------------------------------------------------------------------------------------------
// setup
//------------------------------------------------------------------------------------------------------------------------------------------------------------

function initMaps()
{
  // disabled for first launch
  if (!$(mapEls).length) {
    MAPS_DISABLED = true;
    return;
  }

  // short delay on pulling electorate data, it's large
  setTimeout(function() {
    $.ajax({
      url: '/map/electorates.json',
      dataType: "json",
      data: {},
      success: function(geojson) {
        showElectorates(geojson);
      }
    });
  }, 100);
}

function showElectorates(geojson)
{
  var wardOptions = {
    style : function(feature) {
      // :TODO: finalise pallete and hookup to legislator data
      var color = '#fff';

      var style = {
        weight: 1,
        opacity: 0.3,
        fillOpacity: 0.05,
        color: color,
        fillColor: color
      };

      feature.__defaultStyle = style; // reference here for use in callbacks

      return style;
    },
    onEachFeature: function(feature, layer) {
      // layer.on("mouseover touchstart", function (e) {
      //   onFocusWard.call(layer, e, feature);
      // });
      // layer.on("mouseout touchend", function (e) {
      //   onBlurWard.call(layer, e, feature);
      // });
      // layer.on("dblclick", function(e) {
      //   map.setZoomAround(e.latlng);
      //   map.zoomIn(1);
      // });
    }
  };

  mapEls = $(mapEls);

  mapEls.each(function() {
    mapDOM = this;
    var map = L.map(mapDOM, {}).setView(DEFAULT_COORDS, DEFAULT_ZOOM);
    var layer = L.geoJson(geojson, wardOptions).addTo(map);

    exactFitMap(map, COUNTRY_BOUNDS);

    deactivateUI(map);
    maps.push(map);
    mapShapes.push(layer);
  });

  STS.TOTAL_MAPS_COUNT = maps.length;

  // initialise the maps
  for (var filter in MAP_INIT_CALLBACKS) {
    MAP_INIT_CALLBACKS[filter](mapEls.filter(filter)[0]);
  }
}

//------------------------------------------------------------------------------------------------------------------------------------------------------------
// precision positioning
//------------------------------------------------------------------------------------------------------------------------------------------------------------

// :TODO: make this animated (will need to fix leaflet fitBounds() method if we end up zooming to electorates)
// :TODO: restore original position after the bounds have been projected (not yet necessary)
function getMapFitScale(map, bounds)
{
  var mapEl = $(map.getContainer());

  bounds || (bounds = COUNTRY_BOUNDS);

  // first, zoom the map as appropriate
  map.fitBounds(bounds, { reset: true });

  var mapDims = [ mapEl.width(), mapEl.height() ];

  // now, measure the actual pixel bounds we've ended up at since zoom is very coarse
  var ne = map.project(bounds.getNorthEast());
  var sw = map.project(bounds.getSouthWest());

  var topBounds = map.getPixelOrigin();

  var projectionCoords = {
    top: ne.y - topBounds.y,
    left: sw.x - topBounds.x,
    bottom: sw.y - topBounds.y,
    right: ne.x - topBounds.x
  };

  // now that we have projected screenspace coords we can determine our scale mutiplier to exactly fit..
  var projectionSize = [ projectionCoords.right - projectionCoords.left, projectionCoords.bottom - projectionCoords.top ];
  var scaleAdjust = Math.min(mapDims[0] / projectionSize[0], mapDims[1] / projectionSize[1]);

  return scaleAdjust;
}

function exactFitMap(map, bounds)
{
  $(map.getContainer()).css('transform', 'scale(' + getMapFitScale(map, bounds) + ')');
}

//------------------------------------------------------------------------------------------------------------------------------------------------------------
// UI events
//------------------------------------------------------------------------------------------------------------------------------------------------------------

// function onFocusWard(e, feature)
// {
//   var style = $.extend({}, feature.__defaultStyle);
//   style.color = style.fillColor = '#F0F';
//   this.setStyle(style);
// }

// function onBlurWard(e, feature)
// {
//   this.setStyle(feature.__defaultStyle);
// }

//------------------------------------------------------------------------------------------------------------------------------------------------------------
// UI state
//------------------------------------------------------------------------------------------------------------------------------------------------------------

function activateUI(map)
{
  var mapEl = $(map.getContainer());

  if (!mapEl.hasClass('inactive')) {
    return;
  }

  mapEl.removeClass('inactive');
  map.scrollWheelZoom.enable();
  map.doubleClickZoom.enable();
  map.touchZoom.enable();
  map.keyboard.enable();
  map.dragging.enable();

  mapEl.on('clickoutside', function() {
    deactivateUI(map);
  });
}

function deactivateUI(map)
{
  var mapEl = $(map.getContainer());

  mapEl.addClass('inactive');
  map.scrollWheelZoom.disable();
  map.doubleClickZoom.disable();
  map.touchZoom.disable();
  map.keyboard.disable();
  map.dragging.disable();

  mapEl.off('clickoutside');
}

//------------------------------------------------------------------------------------------------------------------------------------------------------------
// map focusing
//------------------------------------------------------------------------------------------------------------------------------------------------------------

function focusByWardName(map, electorate)
{
  var matched = findElectorate(map, electorate);

  if (matched) {
    focusGeoJSON(map, matched);
  }
}

function focusByMembers(map, memberIds)
{
  var matched = findMembersElectorate(map, memberIds);

  if (matched) {
    focusGeoJSON(map, matched);
  }
}

function focusLatLng(latlng)
{
  map.panTo(latlng, { animate: true });
}

// :TODO: window resize will have to be updated to deal with active bounds state to make this work
function focusGeoJSON(map, layer)
{
  exactFitMap(map, getGeoJSONBounds(layer));
}

function getGeoJSONBounds(layer)
{
  var feature = layer.feature;
  var bounds = layer.getBounds();   // this will be bounds just for the first poly

  var i = 1, coords = feature.geometry.coordinates[0], il = coords.length,
    ring, latLngs;

  // run through all following polys if this is a multipolygon shape
  if (feature.geometry.type === "MultiPolygon") {
    for (; i < il; i++) {
      ring = coords[i];
      latLngs = ring.map(function(pair) {
        return new L.LatLng(pair[1], pair[0]);
      });
      bounds.extend(new L.LatLngBounds(latLngs));
    }
  }

  return bounds;
}

//------------------------------------------------------------------------------------------------------------------------------------------------------------
// stats map area activity intensity shading
//------------------------------------------------------------------------------------------------------------------------------------------------------------

function shadeWardsByActivity(totalEvents, reps, eventId)
{
  var i, j, k, l, rep, shape, eventCount, map;

  if (!eventId) {
    eventId = 'all';
  }

  var shadingSteps = AREA_SHADING_THRESHOLDS[eventId];
  var color = MAP_EVENT_COLORS[eventId];

  for (j = 0, k = maps.length; j < k && (map = maps[j]); ++j) {
    for (i = 0, l = reps.length; i < l && (rep = reps[i]); ++i) {
      eventCount = STS.getTotal(rep, eventId, true);
      shape = findMembersElectorate(map, rep._id);

      if (!shape) {
        continue;   // senator
      }

      var shapeData = STS.CampaignMap.getGeoJSONShape(shape);
      var g = shapeData[0];
      var $paths = shapeData[1];
      var opacity = 0;

      for (var ss = 0, sl = shadingSteps.length; ss < sl; ++ss) {
        if (eventCount < shadingSteps[ss]) {
          break;
        }
        opacity += 0.1;
      }

      // set new baseline style
      var newAttrs = {
        fillOpacity: opacity,
        fillColor: color,
        color: color
      };
      shape.feature.__defaultStyle = $.extend(shape.feature.__defaultStyle, newAttrs);
      if (shape.feature.__defaultStyle.css) {
        shape.feature.__defaultStyle.css = $.extend(shape.feature.__defaultStyle.css, newAttrs);
      }

      TweenMax.to($paths, 0.3, STS.anim.leafletStyleToSVGStyle(shape.feature.__defaultStyle));
    }
  }
}

//------------------------------------------------------------------------------------------------------------------------------------------------------------
// map shape search helpers
//------------------------------------------------------------------------------------------------------------------------------------------------------------

// :IMPORTANT: we assume there is one and only one layer on the map, and it's the geoJSON
function getShapeLayer(map)
{
  for (var i = 0, l = maps.length; i < l; ++i) {
    if (maps[i] === map) {
      return mapShapes[i];
    }
  }
  return null;
}

function findMapByDOM(dom, bounds)
{
  var map, i = 0, l = maps.length;

  for (; i < l; ++i) {
    if (maps[i].getContainer() === dom) {
      return maps[i];
    }
  }

  return null;
}

function findElectorate(map, wardName)
{
  var matched;

  getShapeLayer(map).eachLayer(function(layer) {
    if (!matched && layer.feature.properties.electorate === wardName) {
      matched = layer;
    }
  });

  return matched;
}

// :IMPORTANT:  the FIRST matching member is returned, since further members unrelated to
//        the user in question may be shown to fill the list.
function findMembersElectorate(map, memberIds)
{
  if (MAPS_DISABLED) {
    return null;
  }

  var matched, i, l, thisId;

  if (!$.isArray(memberIds)) {
    memberIds = [memberIds];
  }

  l = memberIds.length;

  getShapeLayer(map).eachLayer(function(layer) {
    if (matched) {
      return;
    }

    thisId = layer.feature.properties.member_id;

    for (i = 0; i < l; ++i) {
      if (thisId === memberIds[i]) {
        matched = layer;
      }
    }
  });

  return matched;
}

//------------------------------------------------------------------------------------------------------------------------------------------------------------
// exports
//------------------------------------------------------------------------------------------------------------------------------------------------------------

STS.CampaignMap = {
  focusPoint : focusLatLng,
  focusArea : focusGeoJSON,

  getWardForMember : function(mapId, rep) {
    return findMembersElectorate(maps[mapId], rep);
  },
  focusWard : focusByWardName,
  focusMembersWard : focusByMembers,

  shadeWardStats : shadeWardsByActivity,

  redraw : exactFitMap,
  getContainerScale : function(dom, bounds) {
    var m = findMapByDOM(dom, bounds);
    return getMapFitScale(m);
  },

  // grab layer SVG elements to directly manipulate
  getGeoJSONShape : function(layer)
  {
    var i, g, $g = $(), layers;
    if (layer.getLayers && (layers = layer.getLayers())) {
      g = layers[0]._container;
      for (i = 0; i < layers.length; ++i) {
        $g = $g.add(layers[i]._container);
      }
    } else {
      g = layer._container;
      $g = $(g);
    }

    return [g, $('path', $g)];
  },

  activateUI : activateUI,
  deactivateUI : deactivateUI,

  EVENT_COLORS : MAP_EVENT_COLORS
};

})(jQuery, STS);
<commit_msg>UNFINISHED implementation of pinging states for senator events. API would need to be adjusted to broadcast more data, too much work at this point<commit_after>/**
 * Notes on the map positioning & animation:
 *
 * - Placing the map in the correct screen location is a pain, because map zoom is very coarse.
 *   We measure the bounds of what we want to fit, then set the map to contain that. From there
 *   we transform the map as a whole to increase its size to the exact dimensions needed.
 * - When you apply more than one GSAP tween to an element at once, it gets pretty weird about it.
 *   There must be a way to make it work, or we could just avoid their documentation and work around it.
 */

(function($, STS) {

STS.TOTAL_MAPS_COUNT = 0; // init, set after maps have loaded to DOM and used by electorate highlighting to fire across all maps

var DEFAULT_COORDS = [-28.043981, 134.912109];
var DEFAULT_ZOOM = 4;

// used to force leaflet to make the whole map visible
if (window.L) {
  var COUNTRY_BOUNDS = L.latLngBounds(L.latLng(-44.205835, 154.841309), L.latLng(-8.795678, 111.708984));
}

// these correspond to opacities 0.1 - 0.9 in 0.1 increments. 0 is below first count, 1 is above last.
var AREA_SHADING_THRESHOLDS = {
  'all' : [1, 2, 3, 8, 10, 50, 80, 150, 200],
  'emails' : [1, 2, 3, 8, 10, 50, 80, 150, 200],
  'views' : [1, 2, 3, 8, 10, 50, 80, 150, 200],
  'tweets' : [1, 2, 3, 8, 10, 50, 80, 150, 200],
  'facebooks' : [1, 2, 3, 8, 10, 50, 80, 150, 200]
  // 'calls' : [1, 2, 3, 8, 10, 50, 80, 150, 200],
};
var MAP_EVENT_COLORS = {
  all : '#ffffff',
  visits : '#E58231',     // orange

  views : '#E58231',      // orange
  calls : '#36C48F',      // dark green
  emails : '#45CBA1',     // light green

  tweets : '#59ADEB',     // light blue
  facebooks : '#3c5a96',  // dark blue
  googles : '#db585a'     // red
};

// all maps are the same, just add more things to this selector & adjust after creating
var mapEls = '.australia-map.status';
var maps = [];
var mapShapes = [];
var MAPS_DISABLED = false;

var winW = $(window).width();
var winH = $(window).height();

//------------------------------------------------------------------------------------------------------------------------------------------------------------
// init
//------------------------------------------------------------------------------------------------------------------------------------------------------------

var MAP_INIT_CALLBACKS = {
  '.status' : function(el) {
    STS.anim.map.enter(el);
  }
};

// load up map when page is ready
$(initMaps);

// watch resize for map positioning
$(window).on('resize', debounce(function(e) {
  winW = $(window).width();
  winH = $(window).height();

  $.each(maps, function(i, map) {
    exactFitMap(map, COUNTRY_BOUNDS);
  });
}));

//------------------------------------------------------------------------------------------------------------------------------------------------------------
// setup
//------------------------------------------------------------------------------------------------------------------------------------------------------------

function initMaps()
{
  // disabled for first launch
  if (!$(mapEls).length) {
    MAPS_DISABLED = true;
    return;
  }

  // short delay on pulling electorate data, it's large
  setTimeout(function() {
    $.ajax({
      url: '/map/electorates.json',
      dataType: "json",
      data: {},
      success: function(geojson) {
        showElectorates(geojson);
      }
    });
  }, 100);
}

function showElectorates(geojson)
{
  var wardOptions = {
    style : function(feature) {
      // :TODO: finalise pallete and hookup to legislator data
      var color = '#fff';

      var style = {
        weight: 1,
        opacity: 0.3,
        fillOpacity: 0.05,
        color: color,
        fillColor: color
      };

      feature.__defaultStyle = style; // reference here for use in callbacks

      return style;
    },
    onEachFeature: function(feature, layer) {
      // layer.on("mouseover touchstart", function (e) {
      //   onFocusWard.call(layer, e, feature);
      // });
      // layer.on("mouseout touchend", function (e) {
      //   onBlurWard.call(layer, e, feature);
      // });
      // layer.on("dblclick", function(e) {
      //   map.setZoomAround(e.latlng);
      //   map.zoomIn(1);
      // });
    }
  };

  mapEls = $(mapEls);

  mapEls.each(function() {
    mapDOM = this;
    var map = L.map(mapDOM, {}).setView(DEFAULT_COORDS, DEFAULT_ZOOM);
    var layer = L.geoJson(geojson, wardOptions).addTo(map);

    exactFitMap(map, COUNTRY_BOUNDS);

    deactivateUI(map);
    maps.push(map);
    mapShapes.push(layer);
  });

  STS.TOTAL_MAPS_COUNT = maps.length;

  // initialise the maps
  for (var filter in MAP_INIT_CALLBACKS) {
    MAP_INIT_CALLBACKS[filter](mapEls.filter(filter)[0]);
  }
}

//------------------------------------------------------------------------------------------------------------------------------------------------------------
// precision positioning
//------------------------------------------------------------------------------------------------------------------------------------------------------------

// :TODO: make this animated (will need to fix leaflet fitBounds() method if we end up zooming to electorates)
// :TODO: restore original position after the bounds have been projected (not yet necessary)
function getMapFitScale(map, bounds)
{
  var mapEl = $(map.getContainer());

  bounds || (bounds = COUNTRY_BOUNDS);

  // first, zoom the map as appropriate
  map.fitBounds(bounds, { reset: true });

  var mapDims = [ mapEl.width(), mapEl.height() ];

  // now, measure the actual pixel bounds we've ended up at since zoom is very coarse
  var ne = map.project(bounds.getNorthEast());
  var sw = map.project(bounds.getSouthWest());

  var topBounds = map.getPixelOrigin();

  var projectionCoords = {
    top: ne.y - topBounds.y,
    left: sw.x - topBounds.x,
    bottom: sw.y - topBounds.y,
    right: ne.x - topBounds.x
  };

  // now that we have projected screenspace coords we can determine our scale mutiplier to exactly fit..
  var projectionSize = [ projectionCoords.right - projectionCoords.left, projectionCoords.bottom - projectionCoords.top ];
  var scaleAdjust = Math.min(mapDims[0] / projectionSize[0], mapDims[1] / projectionSize[1]);

  return scaleAdjust;
}

function exactFitMap(map, bounds)
{
  $(map.getContainer()).css('transform', 'scale(' + getMapFitScale(map, bounds) + ')');
}

//------------------------------------------------------------------------------------------------------------------------------------------------------------
// UI events
//------------------------------------------------------------------------------------------------------------------------------------------------------------

// function onFocusWard(e, feature)
// {
//   var style = $.extend({}, feature.__defaultStyle);
//   style.color = style.fillColor = '#F0F';
//   this.setStyle(style);
// }

// function onBlurWard(e, feature)
// {
//   this.setStyle(feature.__defaultStyle);
// }

//------------------------------------------------------------------------------------------------------------------------------------------------------------
// UI state
//------------------------------------------------------------------------------------------------------------------------------------------------------------

function activateUI(map)
{
  var mapEl = $(map.getContainer());

  if (!mapEl.hasClass('inactive')) {
    return;
  }

  mapEl.removeClass('inactive');
  map.scrollWheelZoom.enable();
  map.doubleClickZoom.enable();
  map.touchZoom.enable();
  map.keyboard.enable();
  map.dragging.enable();

  mapEl.on('clickoutside', function() {
    deactivateUI(map);
  });
}

function deactivateUI(map)
{
  var mapEl = $(map.getContainer());

  mapEl.addClass('inactive');
  map.scrollWheelZoom.disable();
  map.doubleClickZoom.disable();
  map.touchZoom.disable();
  map.keyboard.disable();
  map.dragging.disable();

  mapEl.off('clickoutside');
}

//------------------------------------------------------------------------------------------------------------------------------------------------------------
// map focusing
//------------------------------------------------------------------------------------------------------------------------------------------------------------

function focusByWardName(map, electorate)
{
  var matched = findElectorate(map, electorate);

  if (matched) {
    focusGeoJSON(map, matched);
  }
}

function focusByMembers(map, memberIds)
{
  var matched = findMembersElectorate(map, memberIds);

  if (matched) {
    focusGeoJSON(map, matched);
  }
}

function focusLatLng(latlng)
{
  map.panTo(latlng, { animate: true });
}

// :TODO: window resize will have to be updated to deal with active bounds state to make this work
function focusGeoJSON(map, layer)
{
  exactFitMap(map, getGeoJSONBounds(layer));
}

function getGeoJSONBounds(layer)
{
  var feature = layer.feature;
  var bounds = layer.getBounds();   // this will be bounds just for the first poly

  var i = 1, coords = feature.geometry.coordinates[0], il = coords.length,
    ring, latLngs;

  // run through all following polys if this is a multipolygon shape
  if (feature.geometry.type === "MultiPolygon") {
    for (; i < il; i++) {
      ring = coords[i];
      latLngs = ring.map(function(pair) {
        return new L.LatLng(pair[1], pair[0]);
      });
      bounds.extend(new L.LatLngBounds(latLngs));
    }
  }

  return bounds;
}

//------------------------------------------------------------------------------------------------------------------------------------------------------------
// stats map area activity intensity shading
//------------------------------------------------------------------------------------------------------------------------------------------------------------

function shadeWardsByActivity(totalEvents, reps, eventId)
{
  var i, j, k, l, rep, shape, eventCount, map;

  if (!eventId) {
    eventId = 'all';
  }

  var shadingSteps = AREA_SHADING_THRESHOLDS[eventId];
  var color = MAP_EVENT_COLORS[eventId];

  for (j = 0, k = maps.length; j < k && (map = maps[j]); ++j) {
    for (i = 0, l = reps.length; i < l && (rep = reps[i]); ++i) {
      eventCount = STS.getTotal(rep, eventId, true);
      shape = findMembersElectorate(map, rep._id);

      if (!shape) {
        continue;   // senator
      }

      var shapeData = STS.CampaignMap.getGeoJSONShape(shape);
      var g = shapeData[0];
      var $paths = shapeData[1];
      var opacity = 0;

      for (var ss = 0, sl = shadingSteps.length; ss < sl; ++ss) {
        if (eventCount < shadingSteps[ss]) {
          break;
        }
        opacity += 0.1;
      }

      // set new baseline style
      var newAttrs = {
        fillOpacity: opacity,
        fillColor: color,
        color: color
      };
      shape.feature.__defaultStyle = $.extend(shape.feature.__defaultStyle, newAttrs);
      if (shape.feature.__defaultStyle.css) {
        shape.feature.__defaultStyle.css = $.extend(shape.feature.__defaultStyle.css, newAttrs);
      }

      TweenMax.to($paths, 0.3, STS.anim.leafletStyleToSVGStyle(shape.feature.__defaultStyle));
    }
  }
}

//------------------------------------------------------------------------------------------------------------------------------------------------------------
// map shape search helpers
//------------------------------------------------------------------------------------------------------------------------------------------------------------

// :IMPORTANT: we assume there is one and only one layer on the map, and it's the geoJSON
function getShapeLayer(map)
{
  for (var i = 0, l = maps.length; i < l; ++i) {
    if (maps[i] === map) {
      return mapShapes[i];
    }
  }
  return null;
}

function findMapByDOM(dom, bounds)
{
  var map, i = 0, l = maps.length;

  for (; i < l; ++i) {
    if (maps[i].getContainer() === dom) {
      return maps[i];
    }
  }

  return null;
}

function findElectorate(map, wardName)
{
  var matched;

  getShapeLayer(map).eachLayer(function(layer) {
    if (!matched && layer.feature.properties.electorate === wardName) {
      matched = layer;
    }
  });

  return matched;
}

// :IMPORTANT:  the FIRST matching member is returned, since further members unrelated to
//        the user in question may be shown to fill the list.
function findMembersElectorate(map, memberIds)
{
  if (MAPS_DISABLED) {
    return null;
  }

  var matched, i, l, thisId;

  if (!$.isArray(memberIds)) {
    memberIds = [memberIds];
  }

  l = memberIds.length;

  getShapeLayer(map).eachLayer(function(layer) {
    if (matched) {
      return;
    }

    thisId = layer.feature.properties.member_id;

    for (i = 0; i < l; ++i) {
      if (thisId === memberIds[i]) {
        matched = layer;
      }
    }
  });

  return matched;
}

function findRandomStateElectorate(map, constituency)
{
  if (MAPS_DISABLED) {
    return null;
  }

  var constituencies = {
    "act" : "ACT",
    "australian capital territory" : "ACT",
    "nsw" : "NSW",
    "new south wales" : "NSW",
    "nt" : "NT",
    "northern territory" : "NT",
    "qld" : "QLD",
    "queensland" : "QLD",
    "sa" : "SA",
    "south australia" : "SA",
    "tas" : "TAS",
    "tasmania" : "TAS",
    "vic" : "VIC",
    "victoria" : "VIC",
    "wa" : "WA",
    "western australia" : "WA",
  };

  var selection = [];

  constituency = constituencies[constituency] || constituency.nsw;  // just being paranoid with the backup value

  getShapeLayer(map).eachLayer(function(layer) {
    if (layer.state == constituency) {
      selection.push(layer);
    }
  });

  return selection[Math.floor(Math.random()*items.length)];
}

//------------------------------------------------------------------------------------------------------------------------------------------------------------
// exports
//------------------------------------------------------------------------------------------------------------------------------------------------------------

STS.CampaignMap = {
  focusPoint : focusLatLng,
  focusArea : focusGeoJSON,

  getWardForMember : function(mapId, rep) {
    if (rep.house === '1') {
      return findMembersElectorate(maps[mapId], rep._id);
    }
    // senate = '2'
    return findRandomStateElectorate(maps[mapId], rep.constituency);
  },
  focusWard : focusByWardName,
  focusMembersWard : focusByMembers,

  shadeWardStats : shadeWardsByActivity,

  redraw : exactFitMap,
  getContainerScale : function(dom, bounds) {
    var m = findMapByDOM(dom, bounds);
    return getMapFitScale(m);
  },

  // grab layer SVG elements to directly manipulate
  getGeoJSONShape : function(layer)
  {
    var i, g, $g = $(), layers;
    if (layer.getLayers && (layers = layer.getLayers())) {
      g = layers[0]._container;
      for (i = 0; i < layers.length; ++i) {
        $g = $g.add(layers[i]._container);
      }
    } else {
      g = layer._container;
      $g = $(g);
    }

    return [g, $('path', $g)];
  },

  activateUI : activateUI,
  deactivateUI : deactivateUI,

  EVENT_COLORS : MAP_EVENT_COLORS
};

})(jQuery, STS);
<|endoftext|>


<commit_before>process.env.TMPDIR = 'tmp'; // to avoid the EXDEV rename error, see http://stackoverflow.com/q/21071303/76173

var express = require('express');
var multipart = require('connect-multiparty');
var multipartMiddleware = multipart();
var uploader = require('./uploader-node.js')('tmp');
var app = express();

// Configure access control allow origin header stuff
var ACCESS_CONTROLL_ALLOW_ORIGIN = false;

// Host most stuff in the public folder
app.use(express.static(__dirname + '/public'));
app.use(express.static(__dirname + '/../../dist'));

// Handle uploads through Uploader.js
app.post('/upload', multipartMiddleware, function(req, res) {
  uploader.post(req, function(status, filename, original_filename, identifier) {
    console.log('POST', status, original_filename, identifier);
    if (ACCESS_CONTROLL_ALLOW_ORIGIN) {
      res.header("Access-Control-Allow-Origin", "*");
    }
    res.send(status);
  });
});

app.options('/upload', function(req, res){
  console.log('OPTIONS');
  if (ACCESS_CONTROLL_ALLOW_ORIGIN) {
    res.header("Access-Control-Allow-Origin", "*");
  }
  res.status(200).send();
});

// Handle status checks on chunks through Uploader.js
app.get('/upload', function(req, res) {
  uploader.get(req, function(status, filename, original_filename, identifier) {
    console.log('GET', status);
    if (ACCESS_CONTROLL_ALLOW_ORIGIN) {
      res.header("Access-Control-Allow-Origin", "*");
    }

    res.status(status == 'found' ? 200 : 404).send(status);
  });
});

app.get('/download/:identifier', function(req, res) {
  uploader.write(req.params.identifier, res);
});

app.listen(3000);
<commit_msg>hack time<commit_after>process.env.TMPDIR = 'tmp'; // to avoid the EXDEV rename error, see http://stackoverflow.com/q/21071303/76173

var express = require('express');
var multipart = require('connect-multiparty');
var multipartMiddleware = multipart();
var uploader = require('./uploader-node.js')('tmp');
var app = express();

// Configure access control allow origin header stuff
var ACCESS_CONTROLL_ALLOW_ORIGIN = true;

// Host most stuff in the public folder
app.use(express.static(__dirname + '/public'));
app.use(express.static(__dirname + '/../../dist'));

// Handle uploads through Uploader.js
app.post('/upload', multipartMiddleware, function(req, res) {
  uploader.post(req, function(status, filename, original_filename, identifier) {
    console.log('POST', status, original_filename, identifier);
    if (ACCESS_CONTROLL_ALLOW_ORIGIN) {
      res.header("Access-Control-Allow-Origin", "*");
    }
    setTimeout(function () {
      res.send(status);
    }, 500);
  });
});

app.options('/upload', function(req, res){
  console.log('OPTIONS');
  if (ACCESS_CONTROLL_ALLOW_ORIGIN) {
    res.header("Access-Control-Allow-Origin", "*");
  }
  res.status(200).send();
});

// Handle status checks on chunks through Uploader.js
app.get('/upload', function(req, res) {
  uploader.get(req, function(status, filename, original_filename, identifier) {
    console.log('GET', status);
    if (ACCESS_CONTROLL_ALLOW_ORIGIN) {
      res.header("Access-Control-Allow-Origin", "*");
    }

    res.status(status == 'found' ? 200 : 404).send(status);
  });
});

app.get('/download/:identifier', function(req, res) {
  uploader.write(req.params.identifier, res);
});

app.listen(3000);
<|endoftext|>


<commit_before>'use strict';

/**
 * api/controllers/Shorty.js
 *
 * Shorty model controller and routing logic. Only the bare minimum routes are
 * implemented.
 */

const router     = require('express').Router();
const validator  = require('validator');
const _          = require('lodash');
const models     = require('../models');
const config     = require('../../config');
const utils      = require('../utils');
const log        = config.logger.getLogger('Controllers::Shorty');
const Shorty     = models.shorty;
const Permutator = utils.permutator;

/* Controller objects */
const permutator = new Permutator();

/* Middleware */

/**
 * Performs a sanitization on a Shorty resource object and strips out any
 * necessary fields.
 *
 * @return {Object}
 */
const sanitizeResource = (req, res, next) => {
  log.info('Performing sanitization on resource');
  let resource = req.body;

  // Strip out necessary attributes
  resource = _.pick(resource, ['url']);

  // Sanitize values
  _.forEach(resource, (k, v) => {
    resource[v] = validator.trim(k);
  });

  log.info(`Sanitized resource: ${JSON.stringify(resource)}`);
  req.resource = resource;
  next();
};

/* Routes*/

/**
 * @GET
 * Retrieve all shorty resources.
 */
router.get('/', (req, res) => {
  log.debug('Attempting to retrieve all shorty resources');

  Shorty.find({})
    .then((shorties) => {
      res.status(200).json(shorties);
    })
    .catch((err) => {
      log.error(`Failed to retrieve all shorty resources: ${err.message}`);
      res.status(400).json({error: err.message});
    });
});

/**
 * @GET
 * Retrieve a shorty resource by its MongoDB ID attribute.
 */
router.get('/:id', (req, res) => {
  let shortyID = req.params.id;
  log.debug(`Attempting to retrieve shorty resource: ${shortyID}`)

  Shorty.findById(shortyID)
    .then((shorty) => {
      if (shorty) {
        res.status(200).json(shorty);
      } else {
        log.error('Resource not found');
        res.status(404).json({
          error: 'Resource not found'
        });
      }
    })
    .catch((err) => {
      log.error(`Failed to query for shorty resource: ${err.message}`);
      res.status(400).json({
        error: err.message
      });
    });
});

/**
 * @POST
 * Create a shorty resource.
 */
router.post('/', sanitizeResource, (req, res) => {
  let resource = req.resource;
  log.debug(`Attempting to create shorty resource: ${JSON.stringify(resource)}`);

  Shorty.findOne({
      url: resource.url
    })
    .then((shorty) => {
      if (shorty) {
        log.warn('Resource already exists');
        res.status(422).json({
          error: 'Resource already exists'
        });

        return;
      } else {
        let shorty = new Shorty({
          url: resource.url,
          uid: permutator.next()
        });

        return new Promise((resolve, reject) => {
          shorty.save()
            .then((shorty) => {
              resolve(shorty);
            })
            .catch((err) => {
              reject(err);
            });
        });
      }
    })
    .then((shorty) => {
      res.status(200).json(shorty);
    })
    .catch((err) => {
      log.error(`Failed to create shorty resource: ${err.message}`);
      res.status(400).json({
        error: err.message
      });
    });

  return;
});

/**
 * @PATCH
 * Updates an existing shorty resource.
 */
router.patch('/:id', sanitizeResource, (req, res) => {
  let shortyID = req.params.id;
  let resource = req.resource;
  log.debug(`Attempting to update shorty resource: ${shortyID}`);

  Shorty.findByIdAndUpdate(shortyID, {
      $set: resource
    })
    .then((shorty) => {
      if (shortyz) {
        res.status(200).json(shorty);
      } else {
        log.error('Resource not found');
        res.status(404).json({
          error: 'Resource not found'
        });
      }
    })
    .catch((err) => {
      log.error(`Failed to query for shorty resource: ${err.message}`);
      res.status(400).json({
        error: err.message
      });
    });

  return;
});

/**
 * @DELETE
 * Delete a shorty resource by its MongoDB ID attribute.
 */
router.delete('/:id', (req, res) => {
  let shortyID = req.params.id;
  log.debug(`Attempting to delete shorty resource: ${shortyID}`)

  Shorty.findByIdAndRemove(shortyID)
    .then((shorty) => {
      if (shorty) {
        res.status(200).json(shorty);
      } else {
        log.debug('Resource not found');
        res.status(404).json({
          error: 'Resource not found'
        });
      }
    })
    .catch((err) => {
      log.error(`Failed to query for shorty resource: ${err.message}`);
      res.status(400).json({
        error: err.message
      });
    });

  return;
});

module.exports = router;
<commit_msg>Update PATCH @ /api/shorties to return newly modified resource<commit_after>'use strict';

/**
 * api/controllers/Shorty.js
 *
 * Shorty model controller and routing logic. Only the bare minimum routes are
 * implemented.
 */

const router     = require('express').Router();
const validator  = require('validator');
const _          = require('lodash');
const models     = require('../models');
const config     = require('../../config');
const utils      = require('../utils');
const log        = config.logger.getLogger('Controllers::Shorty');
const Shorty     = models.shorty;
const Permutator = utils.permutator;

/* Controller objects */
const permutator = new Permutator();

/* Middleware */

/**
 * Performs a sanitization on a Shorty resource object and strips out any
 * necessary fields.
 *
 * @return {Object}
 */
const sanitizeResource = (req, res, next) => {
  log.info('Performing sanitization on resource');
  let resource = req.body;

  // Strip out necessary attributes
  resource = _.pick(resource, ['url']);

  // Sanitize values
  _.forEach(resource, (k, v) => {
    resource[v] = validator.trim(k);
  });

  log.info(`Sanitized resource: ${JSON.stringify(resource)}`);
  req.resource = resource;
  next();
};

/* Routes*/

/**
 * @GET
 * Retrieve all shorty resources.
 */
router.get('/', (req, res) => {
  log.debug('Attempting to retrieve all shorty resources');

  Shorty.find({})
    .then((shorties) => {
      res.status(200).json(shorties);
    })
    .catch((err) => {
      log.error(`Failed to retrieve all shorty resources: ${err.message}`);
      res.status(400).json({error: err.message});
    });
});

/**
 * @GET
 * Retrieve a shorty resource by its MongoDB ID attribute.
 */
router.get('/:id', (req, res) => {
  let shortyID = req.params.id;
  log.debug(`Attempting to retrieve shorty resource: ${shortyID}`)

  Shorty.findById(shortyID)
    .then((shorty) => {
      if (shorty) {
        res.status(200).json(shorty);
      } else {
        log.error('Resource not found');
        res.status(404).json({
          error: 'Resource not found'
        });
      }
    })
    .catch((err) => {
      log.error(`Failed to query for shorty resource: ${err.message}`);
      res.status(400).json({
        error: err.message
      });
    });
});

/**
 * @POST
 * Create a shorty resource.
 */
router.post('/', sanitizeResource, (req, res) => {
  let resource = req.resource;
  log.debug(`Attempting to create shorty resource: ${JSON.stringify(resource)}`);

  Shorty.findOne({
      url: resource.url
    })
    .then((shorty) => {
      if (shorty) {
        log.warn('Resource already exists');
        res.status(422).json({
          error: 'Resource already exists'
        });

        return;
      } else {
        let shorty = new Shorty({
          url: resource.url,
          uid: permutator.next()
        });

        return new Promise((resolve, reject) => {
          shorty.save()
            .then((shorty) => {
              resolve(shorty);
            })
            .catch((err) => {
              reject(err);
            });
        });
      }
    })
    .then((shorty) => {
      res.status(200).json(shorty);
    })
    .catch((err) => {
      log.error(`Failed to create shorty resource: ${err.message}`);
      res.status(400).json({
        error: err.message
      });
    });

  return;
});

/**
 * @PATCH
 * Updates an existing shorty resource.
 */
router.patch('/:id', sanitizeResource, (req, res) => {
  let shortyID = req.params.id;
  let resource = req.resource;
  log.debug(`Attempting to update shorty resource: ${shortyID}`);

  Shorty.findByIdAndUpdate(shortyID, {
      $set: resource
    }, {
      new: true
    })
    .then((shorty) => {
      if (shorty) {
        res.status(200).json(shorty);
      } else {
        log.error('Resource not found');
        res.status(404).json({
          error: 'Resource not found'
        });
      }
    })
    .catch((err) => {
      log.error(`Failed to query for shorty resource: ${err.message}`);
      res.status(400).json({
        error: err.message
      });
    });

  return;
});

/**
 * @DELETE
 * Delete a shorty resource by its MongoDB ID attribute.
 */
router.delete('/:id', (req, res) => {
  let shortyID = req.params.id;
  log.debug(`Attempting to delete shorty resource: ${shortyID}`)

  Shorty.findByIdAndRemove(shortyID)
    .then((shorty) => {
      if (shorty) {
        res.status(200).json(shorty);
      } else {
        log.debug('Resource not found');
        res.status(404).json({
          error: 'Resource not found'
        });
      }
    })
    .catch((err) => {
      log.error(`Failed to query for shorty resource: ${err.message}`);
      res.status(400).json({
        error: err.message
      });
    });

  return;
});

module.exports = router;
<|endoftext|>


<commit_before>/* global ResearchEntity, Document, SqlService */
'use strict';

/**
 * ResearchEntity.js
 *
 * @description :: TODO: You might write a short summary of how this model works and what it represents here.
 * @docs        :: http://sailsjs.org/#!documentation/models
 */

const Promise = require("bluebird");
const _ = require("lodash");
const BaseModel = require("./BaseModel.js");

module.exports = _.merge({}, BaseModel, {
    attributes: {},
    createDraft: function (ResearchEntityModel, researchEntityId, draftData) {
        const selectedDraftData = Document.selectDraftData(draftData);
        return Promise.all([
            ResearchEntityModel.findOneById(researchEntityId).populate('drafts'),
            Document.create(selectedDraftData)
        ])
            .spread(function (researchEntity, draft) {
                researchEntity.drafts.add(draft);
                return Promise.all([
                    draft.id,
                    researchEntity.savePromise()
                ]);
            })
            .spread(function (draftId) {
                return Promise.all([
                    draftId,
                    Authorship.createDraftAuthorships(draftId, draftData)
                ]);
            })
            .spread(function (draftId) {
                return Document.findOneById(draftId)
                    .populate('authorships')
                    .populate('affiliations')
                    .populate('authors')
                    .populate('source');
            });
    },
    unverifyDocument: function (ResearchEntityModel, researchEntityId, documentId) {
        return this
            .doUnverifyDocument(ResearchEntityModel, researchEntityId, documentId)
            .then(function () {
                return Document.deleteIfNotVerified(documentId);
            });
    },
    doUnverifyDocument: function (ResearchEntityModel, researchEntityId, documentId) {
        var authorshipModel = getAuthorshipModel(ResearchEntityModel);
        return authorshipModel
            .findOne({researchEntity: researchEntityId, document: documentId})
            .then(function (authorship) {
                if (!authorship)
                    return;
                return authorship.unverify();
            });
    },
    createDrafts: function (Model, researchEntityId, documents) {
        return Promise.all(documents.map(function (document) {
            return Model.createDraft(Model, researchEntityId, document);
        }));
    },
    discardDocuments: function (Model, researchEntityId, documentIds) {
        return Promise.all(documentIds.map(function (documentId) {
            return Model.discardDocument(researchEntityId, documentId);
        }));
    },
    verifyDrafts: function (ResearchEntityModel, researchEntityId, draftIds) {
        return Promise.all(
            draftIds.map(draftId => ResearchEntityModel.verifyDraft(ResearchEntityModel, researchEntityId, draftId))
        );
    },
    verifyDraft: function (ResearchEntityModel, researchEntityId, draftId, position, affiliationInstituteIds, corresponding) {
        return Document.findOneById(draftId)
            .populate('authorships')
            .populate('affiliations')
            .then(draft => {
                if (!draft || !draft.draft)
                    throw {
                        error: 'Draft not found',
                        item: draftId
                    };
                if (!draft.isValid())
                    throw {
                        error: 'Draft not valid for verification',
                        item: draft
                    };
                return ResearchEntityModel.getAuthorshipsData(draft, researchEntityId, position, affiliationInstituteIds, corresponding)
                    .then(authorshipData => {
                        if (!authorshipData.isVerifiable)
                            throw {
                                error: authorshipData.error,
                                item: authorshipData.document
                            };

                        return Document.findCopies(draft, authorshipData.position)
                            .then(documents => {
                                var n = documents.length;
                                if (n === 0) return draft;
                                if (n > 1)
                                    sails.log.debug('Too many similar documents to ' + draft.id + ' ( ' + n + ')');
                                var doc = documents[0];

                                if (doc.isPositionVerified(authorshipData.position))
                                    throw {
                                        error: "The position is already verified",
                                        item: doc
                                    };

                                sails.log.debug('Draft ' + draft.id + ' will be deleted and substituted by ' + doc.id);
                                return Document.destroy({id: draft.id}).then(_ => doc);
                            })
                            .then(d => d.draftToDocument())
                            .then(d => ResearchEntityModel.doVerifyDocument(d, researchEntityId, authorshipData));
                    });
            })
            .catch(e => {
                if (e.error)
                    return {
                        error: e.error,
                        item: e.item
                    };
                throw e;
            });
    },
    verifyDocuments: function (Model, researchEntityId, documentIds) {
        return Promise.all(documentIds.map(documentId => Model.verifyDocument(Model, researchEntityId, documentId)));
    },
    verifyDocument: function (Model, researchEntityId, documentId, position, affiliationInstituteIds, corresponding) {
        const DiscardedModel = getDiscardedModel(Model);
        return DiscardedModel.destroy({document: documentId})
            .then(() => Document.findOneById(documentId)
                .populate('affiliations')
                .populate('authorships')
            )
            .then(document => {
                if (!document)
                    throw {
                        error: 'Document not found',
                        item: researchEntityId
                    };
                return Model.getAuthorshipsData(document, researchEntityId, position, affiliationInstituteIds, corresponding)
            })
            .then(authorshipData => {
                if (!authorshipData.isVerifiable)
                    throw {
                        error: authorshipData.error,
                        item: authorshipData.document
                    };

                if (authorshipData.document.isPositionVerified(authorshipData.position))
                    throw {
                        error: "The position is already verified",
                        item: authorshipData.document
                    };
                return Model.doVerifyDocument(authorshipData.document, researchEntityId, authorshipData);
            })
            .catch(e => ({
                error: e.error,
                item: e.item
            }));
    },
    updateDraft: function (ResearchEntityModel, draftId, draftData) {
        const documentFields = Document.getFields();
        const selectedDraftData = _.pick(draftData, documentFields);
        return Document.update({id: draftId}, selectedDraftData)
    },
    checkCopiedDocuments: function (ResearchEntityModel, researchEntityId, documentsToCheck, includeDrafts) {
        const threeshold = .85;
        return Promise.all(_.map(documentsToCheck, function (docToCheck) {
            return getSimilarDocuments(ResearchEntityModel, researchEntityId, docToCheck, includeDrafts)
                .then(function (documentsToCompare) {
                    const isDuplicate = _.some(documentsToCompare, d => d.getSimiliarity(docToCheck) >= threeshold);
                    if (isDuplicate)
                        DocumentLabels.addLabel(docToCheck, DocumentLabels.DUPLICATE);

                    return docToCheck;
                });
        }));
    },
    addTags: function (TagModel, userId, documentId, tags) {
        return TagModel.destroy({researchEntity: userId, document: documentId})
            .then(() =>
                tags.forEach(t =>
                    TagLabel
                        .findOrCreate({value: t})
                        .then(tl => TagModel.create({
                            document: documentId,
                            researchEntity: userId,
                            tagLabel: tl.id
                        }))
                )
            )
    },
    setAuthorships: async function(ResearchEntityModel, researchEntityId, draftId, authorshipsData) {
        authorshipsData.forEach(a => delete a.id);
        const deleteAuthorships = await Authorship.destroy({document: draftId});
        await Affiliation.destroy({authorship: deleteAuthorships.map(a => a.id)});
        return Authorship.create(authorshipsData);
    },
    _config: {
        actions: false,
        shortcuts: false,
        rest: false
    }
});

function getThroughModel(ResearchEntityModel, fieldName) {
    var throughModelName = ResearchEntityModel._attributes[fieldName].through;
    return sails.models[throughModelName];
}

function getAuthorshipModel(ResearchEntityModel) {
    return getThroughModel(ResearchEntityModel, 'documents');
}

function getDiscardedModel(ResearchEntityModel) {
    return getThroughModel(ResearchEntityModel, 'discardedDocuments');
}

function getSimilarDocuments(ResearchEntityModel, researchEntityid, doc, includeDrafts) {
    const criteria = {or: []};
    if (doc.id)
        criteria.id = {'!': doc.id};
    if (doc.title)
        criteria.or.push({title: doc.title});
    if (doc.doi)
        criteria.or.push({doi: doc.doi});
    if (doc.scopusId)
        criteria.or.push({scopusId: doc.scopusId});
    if (doc.authorsStr)
        criteria.or.push(...doc.authorsStr.split(', ').map(author => ({authorsStr: {contains: author}})));
    if (_.isEmpty(criteria.or))
        delete criteria.or;
    let q = ResearchEntityModel
        .findOneById(researchEntityid)
        .populate('documents', criteria);

    if (includeDrafts)
        q = q.populate('drafts', criteria);

    return q.then(function (researchEntity) {
        return _.union(
            researchEntity.drafts,
            researchEntity.documents
        );
    });
}<commit_msg>Improved the duplicate calculation speeed.<commit_after>/* global ResearchEntity, Document, SqlService */
'use strict';

/**
 * ResearchEntity.js
 *
 * @description :: TODO: You might write a short summary of how this model works and what it represents here.
 * @docs        :: http://sailsjs.org/#!documentation/models
 */

const Promise = require("bluebird");
const _ = require("lodash");
const BaseModel = require("./BaseModel.js");

module.exports = _.merge({}, BaseModel, {
    attributes: {},
    createDraft: function (ResearchEntityModel, researchEntityId, draftData) {
        const selectedDraftData = Document.selectDraftData(draftData);
        return Promise.all([
            ResearchEntityModel.findOneById(researchEntityId).populate('drafts'),
            Document.create(selectedDraftData)
        ])
            .spread(function (researchEntity, draft) {
                researchEntity.drafts.add(draft);
                return Promise.all([
                    draft.id,
                    researchEntity.savePromise()
                ]);
            })
            .spread(function (draftId) {
                return Promise.all([
                    draftId,
                    Authorship.createDraftAuthorships(draftId, draftData)
                ]);
            })
            .spread(function (draftId) {
                return Document.findOneById(draftId)
                    .populate('authorships')
                    .populate('affiliations')
                    .populate('authors')
                    .populate('source');
            });
    },
    unverifyDocument: function (ResearchEntityModel, researchEntityId, documentId) {
        return this
            .doUnverifyDocument(ResearchEntityModel, researchEntityId, documentId)
            .then(function () {
                return Document.deleteIfNotVerified(documentId);
            });
    },
    doUnverifyDocument: function (ResearchEntityModel, researchEntityId, documentId) {
        var authorshipModel = getAuthorshipModel(ResearchEntityModel);
        return authorshipModel
            .findOne({researchEntity: researchEntityId, document: documentId})
            .then(function (authorship) {
                if (!authorship)
                    return;
                return authorship.unverify();
            });
    },
    createDrafts: function (Model, researchEntityId, documents) {
        return Promise.all(documents.map(function (document) {
            return Model.createDraft(Model, researchEntityId, document);
        }));
    },
    discardDocuments: function (Model, researchEntityId, documentIds) {
        return Promise.all(documentIds.map(function (documentId) {
            return Model.discardDocument(researchEntityId, documentId);
        }));
    },
    verifyDrafts: function (ResearchEntityModel, researchEntityId, draftIds) {
        return Promise.all(
            draftIds.map(draftId => ResearchEntityModel.verifyDraft(ResearchEntityModel, researchEntityId, draftId))
        );
    },
    verifyDraft: function (ResearchEntityModel, researchEntityId, draftId, position, affiliationInstituteIds, corresponding) {
        return Document.findOneById(draftId)
            .populate('authorships')
            .populate('affiliations')
            .then(draft => {
                if (!draft || !draft.draft)
                    throw {
                        error: 'Draft not found',
                        item: draftId
                    };
                if (!draft.isValid())
                    throw {
                        error: 'Draft not valid for verification',
                        item: draft
                    };
                return ResearchEntityModel.getAuthorshipsData(draft, researchEntityId, position, affiliationInstituteIds, corresponding)
                    .then(authorshipData => {
                        if (!authorshipData.isVerifiable)
                            throw {
                                error: authorshipData.error,
                                item: authorshipData.document
                            };

                        return Document.findCopies(draft, authorshipData.position)
                            .then(documents => {
                                var n = documents.length;
                                if (n === 0) return draft;
                                if (n > 1)
                                    sails.log.debug('Too many similar documents to ' + draft.id + ' ( ' + n + ')');
                                var doc = documents[0];

                                if (doc.isPositionVerified(authorshipData.position))
                                    throw {
                                        error: "The position is already verified",
                                        item: doc
                                    };

                                sails.log.debug('Draft ' + draft.id + ' will be deleted and substituted by ' + doc.id);
                                return Document.destroy({id: draft.id}).then(_ => doc);
                            })
                            .then(d => d.draftToDocument())
                            .then(d => ResearchEntityModel.doVerifyDocument(d, researchEntityId, authorshipData));
                    });
            })
            .catch(e => {
                if (e.error)
                    return {
                        error: e.error,
                        item: e.item
                    };
                throw e;
            });
    },
    verifyDocuments: function (Model, researchEntityId, documentIds) {
        return Promise.all(documentIds.map(documentId => Model.verifyDocument(Model, researchEntityId, documentId)));
    },
    verifyDocument: function (Model, researchEntityId, documentId, position, affiliationInstituteIds, corresponding) {
        const DiscardedModel = getDiscardedModel(Model);
        return DiscardedModel.destroy({document: documentId})
            .then(() => Document.findOneById(documentId)
                .populate('affiliations')
                .populate('authorships')
            )
            .then(document => {
                if (!document)
                    throw {
                        error: 'Document not found',
                        item: researchEntityId
                    };
                return Model.getAuthorshipsData(document, researchEntityId, position, affiliationInstituteIds, corresponding)
            })
            .then(authorshipData => {
                if (!authorshipData.isVerifiable)
                    throw {
                        error: authorshipData.error,
                        item: authorshipData.document
                    };

                if (authorshipData.document.isPositionVerified(authorshipData.position))
                    throw {
                        error: "The position is already verified",
                        item: authorshipData.document
                    };
                return Model.doVerifyDocument(authorshipData.document, researchEntityId, authorshipData);
            })
            .catch(e => ({
                error: e.error,
                item: e.item
            }));
    },
    updateDraft: function (ResearchEntityModel, draftId, draftData) {
        const documentFields = Document.getFields();
        const selectedDraftData = _.pick(draftData, documentFields);
        return Document.update({id: draftId}, selectedDraftData)
    },
    checkCopiedDocuments: async function (ResearchEntityModel, researchEntityId, documentsToCheck, includeDrafts) {
        const threeshold = .85;
        const res = await Promise.all(_.map(documentsToCheck, async function (docToCheck, i) {
            const documentsToCompare = await getSimilarDocuments(ResearchEntityModel, researchEntityId, docToCheck, includeDrafts);
            const isDuplicate = _.some(documentsToCompare, d => d.getSimiliarity(docToCheck) >= threeshold);
            if (isDuplicate)
                DocumentLabels.addLabel(docToCheck, DocumentLabels.DUPLICATE);

            return docToCheck;
        }));
        return res;
    },
    addTags: function (TagModel, userId, documentId, tags) {
        return TagModel.destroy({researchEntity: userId, document: documentId})
            .then(() =>
                tags.forEach(t =>
                    TagLabel
                        .findOrCreate({value: t})
                        .then(tl => TagModel.create({
                            document: documentId,
                            researchEntity: userId,
                            tagLabel: tl.id
                        }))
                )
            )
    },
    setAuthorships: async function (ResearchEntityModel, researchEntityId, draftId, authorshipsData) {
        authorshipsData.forEach(a => delete a.id);
        const deleteAuthorships = await Authorship.destroy({document: draftId});
        await Affiliation.destroy({authorship: deleteAuthorships.map(a => a.id)});
        return Authorship.create(authorshipsData);
    },
    _config: {
        actions: false,
        shortcuts: false,
        rest: false
    }
});

function getThroughModel(ResearchEntityModel, fieldName) {
    var throughModelName = ResearchEntityModel._attributes[fieldName].through;
    return sails.models[throughModelName];
}

function getAuthorshipModel(ResearchEntityModel) {
    return getThroughModel(ResearchEntityModel, 'documents');
}

function getDiscardedModel(ResearchEntityModel) {
    return getThroughModel(ResearchEntityModel, 'discardedDocuments');
}

function getSimilarDocuments(ResearchEntityModel, researchEntityid, doc, includeDrafts) {
    const criteria = {or: []};
    if (doc.id)
        criteria.id = {'!': doc.id};
    if (doc.title)
        criteria.or.push({title: doc.title});
    if (doc.doi)
        criteria.or.push({doi: doc.doi});
    if (doc.scopusId)
        criteria.or.push({scopusId: doc.scopusId});
    if (doc.authorsStr)
        criteria.or.push({authorsStr: doc.authorsStr});
    if (_.isEmpty(criteria.or))
        delete criteria.or;
    let q = ResearchEntityModel
        .findOneById(researchEntityid)
        .populate('documents', criteria);

    if (includeDrafts)
        q = q.populate('drafts', criteria);

    return q.then(function (researchEntity) {
        return _.union(
            researchEntity.drafts,
            researchEntity.documents
        );
    });
}<|endoftext|>


<commit_before>'use strict';

const mongoURI = process.env.MONGODB_URI;

var fs = require('fs'),
	readline = require('readline'),
	MongoClient = require('mongodb').MongoClient,
	assert = require('assert'),
	dict = "dict",
	codeCounter = "codeCounter";

function init() {
	MongoClient.connect(mongoURI, function (err, db) {
		if (err) {
			console.log("GET CURRCODE: OPENING", err);
		}
		else {
			var collection = db.collection('codeCounter');
			collection.find().toArray(function (err, res) {
				if (err) {
					console.log("GET CURRCODE: READING", err);
				}
				else {
					console.log(res[0]);
					codeCounter = JSON.parse(JSON.stringify(res[0].codeCounter));
					//
				}
			})
		}
	});

	fs.readFile('dict.json', 'utf8', function (err, data) {
		if (err) throw err;
		console.log(JSON.parse(data));
		dict = JSON.parse(data);
	});
}
init();

var dict = (function () {
	fs.readFile('dict.json', 'utf8', function (err, data) {
		if (err) throw err;
		console.log(JSON.parse(data));
		return JSON.parse(data);
	});
})();

/*limitation, if codeCounter exceeds [n,n,n,n], where n
is dictSize, counter does not change*/
var makeNewCode = function () {
	//I wonder if there is going to be any async issues, if u run two of these at the same time hmm
	var isFinished = false;
	var curr = codeCounter.length - 1;
	console.log("makeNewCode");
	console.log(codeCounter);
	codeCounter[curr]++;
	while (!isFinished && curr > 0) {
		console.log((Number(codeCounter[curr]) > (Object.keys(dict).length - 1)));
		console.log(Number(codeCounter[curr]));
		console.log(dict);
		console.log(Object.keys(dict) - 1);
		if (Number(codeCounter[curr]) > (Object.keys(dict).length - 1)) {
			codeCounter[curr] = 0;
			curr--;
			if (curr < 0) {
				throw "Code Counter Overflowed, I.E Game Limit Reached";
			}
			else {
				codeCounter[curr]++;
			}
		}
		else {
			isFinished = true;
		}
	}

	MongoClient.connect(mongoURI, function (err, db) {
		if (err) {
			console.log("WRITING CURRCODE TO DB: ", err);
		}
		else {
			var collection = db.collection('codeCounter');
			collection.updateOne({}, {
				"codeCounter": codeCounter
			}, function (err, res) {
				if (err) {
					console.log("WRITING CURRCODE TO DB: ", err);
				}
				else {
					console.log("WRITING CURRCODE TO DB: Success");
				}
			})
		}
	});

	var retv = dict[codeCounter[0]] + dict[codeCounter[1]] + dict[codeCounter[2]] + dict[codeCounter[3]];
	return retv;
}

module.exports.genCode = function () {
	console.log("genCode");
	console.log("fs: " + fs);
	console.log("codeCounter: " + codeCounter);
	return makeNewCode();
};

module.exports.acceptGame = function (code, opponent) {
	return new Promise((resolve, reject) => {
		MongoClient.connect(mongoURI, function (err, db) {
			if (err) {
				console.log("Opening GameDB acceptGame: ", err);
			}
			else {
				var filter = { "gameCode": code };
				var collection = db.collection('games');
				collection.find(filter).toArray(function (err, res) {
					if (err) {
						console.log("acceptGame: Finding game", err);
					}
					else {
						console.log(res);
						if (res.length == 0) {
							reject("Game not found");
						}
						if (typeof res[0].black != "undefined") {
							reject("Game already has opponent");
						}
						res[0].black = opponent;
						collection.updateOne(filter, res[0], function(err, res){
							if(err){
								console.log("Cannot write opponent to DB: ", err);
								reject("Unexpected error, Try Again");
							}
							resolve();
						})
					}
				})
			}
		});
	});
}<commit_msg>Learning shit<commit_after>'use strict';

const mongoURI = process.env.MONGODB_URI;

var fs = require('fs'),
	readline = require('readline'),
	MongoClient = require('mongodb').MongoClient,
	assert = require('assert'),
	dict = "dict",
	codeCounter = "codeCounter";

function init() {
	MongoClient.connect(mongoURI, function (err, db) {
		if (err) {
			console.log("GET CURRCODE: OPENING", err);
		}
		else {
			var collection = db.collection('codeCounter');
			collection.find().toArray(function (err, res) {
				if (err) {
					console.log("GET CURRCODE: READING", err);
				}
				else {
					console.log(res[0]);
					codeCounter = JSON.parse(JSON.stringify(res[0].codeCounter));
					//
				}
			})
		}
	});

	fs.readFile('dict.json', 'utf8', function (err, data) {
		if (err) throw err;
		console.log(JSON.parse(data));
		dict = JSON.parse(data);
	});
}
init();

var dict = (function () {
	fs.readFile('dict.json', 'utf8', function (err, data) {
		if (err) throw err;
		console.log(JSON.parse(data));
		return JSON.parse(data);
	});
})();

/*limitation, if codeCounter exceeds [n,n,n,n], where n
is dictSize, counter does not change*/
var makeNewCode = function () {
	//I wonder if there is going to be any async issues, if u run two of these at the same time hmm
	var isFinished = false;
	var curr = codeCounter.length - 1;
	console.log("makeNewCode");
	console.log(codeCounter);
	codeCounter[curr]++;
	while (!isFinished && curr > 0) {
		console.log((Number(codeCounter[curr]) > (Object.keys(dict).length - 1)));
		console.log(Number(codeCounter[curr]));
		console.log(dict);
		console.log(Object.keys(dict) - 1);
		if (Number(codeCounter[curr]) > (Object.keys(dict).length - 1)) {
			codeCounter[curr] = 0;
			curr--;
			if (curr < 0) {
				throw "Code Counter Overflowed, I.E Game Limit Reached";
			}
			else {
				codeCounter[curr]++;
			}
		}
		else {
			isFinished = true;
		}
	}

	MongoClient.connect(mongoURI, function (err, db) {
		if (err) {
			console.log("WRITING CURRCODE TO DB: ", err);
		}
		else {
			var collection = db.collection('codeCounter');
			collection.updateOne({}, {
				"codeCounter": codeCounter
			}, function (err, res) {
				if (err) {
					console.log("WRITING CURRCODE TO DB: ", err);
				}
				else {
					console.log("WRITING CURRCODE TO DB: Success");
				}
			})
		}
	});

	var retv = dict[codeCounter[0]] + dict[codeCounter[1]] + dict[codeCounter[2]] + dict[codeCounter[3]];
	return retv;
}

module.exports.genCode = function () {
	console.log("genCode");
	console.log("fs: " + fs);
	console.log("codeCounter: " + codeCounter);
	return makeNewCode();
};

module.exports.acceptGame = function (code, opponent) {
	return new Promise((resolve, reject) => {
		MongoClient.connect(mongoURI, function (err, db) {
			if (err) {
				console.log("Opening GameDB acceptGame: ", err);
			}
			else {
				var filter = { "gameCode": code };
				var collection = db.collection('games');
				collection.find(filter).toArray(function (err, res) {
					if (err) {
						console.log("acceptGame: Finding game", err);
					}
					else {
						console.log(res);
						console.log(res.length);
						if (res.length == 0) {
							reject("Game not found");
						}
						if (typeof res[0].black != "undefined") {
							reject("Game already has opponent");
						}
						res[0].black = opponent;
						collection.updateOne(filter, res[0], function(err, res){
							if(err){
								console.log("Cannot write opponent to DB: ", err);
								reject("Unexpected error, Try Again");
							}
							resolve();
						})
					}
				})
			}
		});
	});
}<|endoftext|>