<commit_before>var _ = require('lodash');
var serverStatus = require('express-server-status');
var roles = require('../constants/roles');
var jwt = require('../middlewares/jwt');

module.exports = function(app) {

  var aN = require('../middleware/security/authentication')(app);
  var aZ = require('../middleware/security/authorization')(app);

  /**
   * Public methods.
   */
  var Controllers = app.set('controllers');
  var mw = Controllers.middlewares;
  var users = Controllers.users;
  var params = Controllers.params;
  var groups = Controllers.groups;
  var activities = Controllers.activities;
  var notifications = Controllers.notifications;
  var transactions = Controllers.transactions;
  var payments = Controllers.payments;
  var paypal = Controllers.paypal;
  var images = Controllers.images;
  var cards = Controllers.cards;
  var webhooks = Controllers.webhooks;
  var stripe = Controllers.stripe;
  var test = Controllers.test;
  var subscriptions = Controllers.subscriptions;
  var errors = app.errors;

  const HOST = roles.HOST;
  const MEMBER = roles.MEMBER;

  /**
   * Status.
   */
  app.use('/status', serverStatus(app));

  /**
   * Parameters.
   */
  app.param('userid', params.userid);
  app.param('groupid', params.groupid);
  app.param('transactionid', params.transactionid);
  app.param('paranoidtransactionid', params.paranoidtransactionid);
  app.param('paykey', params.paykey);

  /**
   * User reset password flow (no jwt verification)
   */
  app.post('/users/password/forgot', aN.authenticateAppByApiKey, mw.required('email'), users.forgotPassword); // Send forgot password email
  app.post('/users/password/reset/:userid_enc/:reset_token', aN.authenticateAppByApiKey, mw.required('password', 'passwordConfirmation'), users.resetPassword); // Reset password

  app.post('/subscriptions/new_token', aN.authenticateAppByApiKey, mw.required('email'), subscriptions.sendNewTokenByEmail);

  /**
   * Routes without expiration validation
   */
  app.post('/subscriptions/refresh_token', aN.authenticateUserAndAppByJwtNoExpiry(), subscriptions.refreshTokenByEmail);

  /**
   * NotImplemented response.
   */
  var NotImplemented = function(req, res, next) {
    return next(new errors.NotImplemented('Not implemented yet.'));
  };

  /**
   * For testing the email templates
   */
  app.get('/email/:template', test.generateTestEmail);

  /**
   * Users.
   */
  app.post('/users', aN.authenticateAppByApiKey, aZ.appAccess(0.5), mw.required('user'), users.create); // Create a user.
  app.get('/users/:userid', aN.authenticateUserByJwt(), users.show); // Get a user.
  app.put('/users/:userid', aN.authenticateAppByApiKey, mw.required('user'), users.updateUserWithoutLoggedIn); // Update a user.
  app.put('/users/:userid/password', aZ.authorizeUserToAccessUser(), mw.required('password', 'passwordConfirmation'), users.updatePassword); // Update a user password.
  app.put('/users/:userid/paypalemail', mw.required('paypalEmail'), aZ.authorizeUserToAccessUser(), users.updatePaypalEmail); // Update a user paypal email.
  app.put('/users/:userid/avatar', mw.required('avatar'), aZ.authorizeUserToAccessUser(), users.updateAvatar); // Update a user's avatar
  app.get('/users/:userid/email', NotImplemented); // Confirm a user's email.
  // TODO why is this route duplicated?
  app.post('/users', aN.authenticateAppByApiKey, aZ.appAccess(0.5), mw.required('user'), users.create); // Create a user.

  /**
   * Authentication.
   */
  app.post('/authenticate', aN.authenticateAppByApiKey, aN.authenticateUserByPassword, users.getToken); // Authenticate user to get a token.
  app.post('/authenticate/refresh', NotImplemented); // Refresh the token (using a valid token OR a expired token + refresh_token).
  app.post('/authenticate/reset', NotImplemented); // Reset the refresh_token.

  /**
   * Credit card.
   *
   *  Let's assume for now a card is linked to a user.
   */
  app.get('/users/:userid/cards', aZ.authorizeUserToAccessUser(), cards.getCards); // Get a user's cards.
  app.post('/users/:userid/cards', NotImplemented); // Create a user's card.
  app.put('/users/:userid/cards/:cardid', NotImplemented); // Update a user's card.
  app.delete('/users/:userid/cards/:cardid', NotImplemented); // Delete a user's card.

  /**
   * Paypal Preapproval.
   */
  app.get('/users/:userid/paypal/preapproval', aZ.authorizeUserToAccessUser(), paypal.getPreapprovalKey); // Get a user's preapproval key.
  app.post('/users/:userid/paypal/preapproval/:preapprovalkey', aZ.authorizeUserToAccessUser(), paypal.confirmPreapproval); // Confirm a preapproval key.
  app.get('/users/:userid/paypal/preapproval/:preapprovalkey', aZ.authorizeUserToAccessUser(), paypal.getDetails); // Get a preapproval key details.

  /**
   * Groups.
   */
  app.post('/groups', aN.authenticateUserByJwt(), mw.required('group'), groups.create); // Create a group. Option `role` to assign the caller directly (default to null).
  app.get('/groups/:groupid', aZ.authorizeAccessToGroup({authIfPublic: true}), groups.getOne);
  app.get('/groups/:groupid/users', aZ.authorizeAccessToGroup({authIfPublic: true}),  mw.cache(60), groups.getUsers); // Get group users
  app.put('/groups/:groupid', aZ.authorizeAccessToGroup({userRoles: [HOST, MEMBER], bypassUserRolesCheckIfAuthenticatedAsAppAndNotUser: true}), mw.required('group'), groups.update); // Update a group.
  app.delete('/groups/:groupid', NotImplemented); // Delete a group.
  app.post('/groups/:groupid/payments', aN.authenticateUserOrApp(), mw.required('payment'), mw.getOrCreateUser, payments.post); // Make a payment/donation.
  app.post('/groups/:groupid/payments/paypal', aN.authenticateUserOrApp(), mw.required('payment'), payments.paypal); // Make a payment/donation.

  /**
   * UserGroup.
   *
   *  Relations between a group and a user.
   */
  app.get('/users/:userid/groups', aZ.authorizeUserToAccessUser(), users.getGroups); // Get user's groups.
  app.post('/groups/:groupid/users/:userid', aZ.authorizeAccessToGroup({userRoles: [HOST]}), groups.addUser); // Add a user to a group.
  app.put('/groups/:groupid/users/:userid', aZ.authorizeAccessToGroup({userRoles: [HOST]}), groups.updateUser); // Update a user's role in a group.
  app.delete('/groups/:groupid/users/:userid', aZ.authorizeAccessToGroup({userRoles: [HOST]}), groups.deleteUser); // Remove a user from a group.

  /**
   * Transactions (financial).
   */
  app.get('/groups/:groupid/transactions', aZ.authorizeAccessToGroup({authIfPublic: true}), mw.paginate(), mw.sorting({key: 'createdAt', dir: 'DESC'}), groups.getTransactions); // Get a group's transactions.
  app.post('/groups/:groupid/transactions', aN.authenticateUserByJwt(), aZ.authorizeAccessToGroup({authIfPublic: true}), mw.required('transaction'), mw.getOrCreateUser, groups.createTransaction); // Create a transaction for a group.

  app.use(mw.apiKey, jwt, mw.identifyFromToken, mw.checkJWTExpiration);

  app.get('/groups/:groupid/transactions/:transactionid', mw.authorizeIfGroupPublic, mw.authorizeAuthUserOrApp, mw.authorizeGroup, mw.authorizeTransaction, groups.getTransaction); // Get a transaction.
  app.get('/groups/:groupid/transactions/:transactionid', groups.getTransaction); // Get a transaction.
  app.put('/groups/:groupid/transactions/:transactionid', mw.authorizeAuthUserOrApp, mw.authorizeGroup, mw.authorizeTransaction, mw.required('transaction'), groups.updateTransaction); // Update a transaction.
  app.delete('/groups/:groupid/transactions/:transactionid', mw.authorizeAuthUserOrApp, mw.authorizeGroup, mw.authorizeGroupRoles(roles.HOST), mw.authorizeTransaction, groups.deleteTransaction); // Delete a transaction.

  app.post('/groups/:groupid/transactions/:transactionid/approve', mw.authorizeAuthUserOrApp, mw.authorizeGroup, mw.authorizeTransaction, mw.required('approved'), transactions.approve); // Approve a transaction.
  app.post('/groups/:groupid/transactions/:transactionid/pay', mw.authorizeAuthUser, mw.authorizeGroup, mw.authorizeGroupRoles([roles.HOST, roles.MEMBER]), mw.authorizeTransaction, mw.required('service'), transactions.pay); // Pay a transaction.
  app.get('/groups/:groupid/transactions/:transactionid/paykey', mw.authorizeAuthUserOrApp, mw.authorizeGroup, mw.authorizeGroupRoles([roles.HOST, roles.MEMBER]), mw.authorizeTransaction, transactions.getPayKey); // Get a transaction's pay key.
  app.post('/groups/:groupid/transactions/:transactionid/paykey/:paykey', mw.authorizeAuthUserOrApp, mw.authorizeGroup, mw.authorizeGroupRoles([roles.HOST, roles.MEMBER]), mw.authorizeTransaction, transactions.confirmPayment); // Confirm a transaction's payment.
  app.post('/groups/:groupid/transactions/:transactionid/attribution/:userid', mw.authorizeAuthUserOrApp, mw.authorizeGroup, mw.authorizeTransaction, mw.authorizeGroupRoles([roles.HOST, roles.MEMBER]), transactions.attributeUser); // Attribute a transaction to a user.
  app.get('/groups/:groupid/transactions/:paranoidtransactionid/callback', payments.paypalCallback); // Callback after a payment

  /**
   * Activities.
   *
   *  An activity is any action linked to a User or a Group.
   */
  app.get('/groups/:groupid/activities', mw.authorizeAuthUserOrApp, mw.authorizeGroup, mw.paginate(), mw.sorting({key: 'createdAt', dir: 'DESC'}), activities.group); // Get a group's activities.
  app.get('/users/:userid/activities', mw.authorizeAuthUser, mw.authorizeUser, mw.paginate(), mw.sorting({key: 'createdAt', dir: 'DESC'}), activities.user); // Get a user's activities.

  /**
   * Notifications.
   *
   *  A user can subscribe by email to any type of activity of a Group.
   */
  app.post('/groups/:groupid/activities/:activityType/subscribe', mw.authorizeAuthUser, mw.authorizeGroup, notifications.subscribe); // Subscribe to a group's activities
  app.post('/groups/:groupid/activities/:activityType/unsubscribe', mw.authorizeAuthUser, mw.authorizeGroup, notifications.unsubscribe); // Unsubscribe to a group's activities

  /**
   * Separate route for uploading images to S3
   */
  app.post('/images', mw.authorizeAuthUser, images.upload);

  /**
   * Webhook for stripe when it gets a new subscription invoice
   */
  app.post('/webhooks/stripe', webhooks.stripe);

  /**
   * Stripe oAuth
   */

  app.get('/stripe/authorize', mw.authorizeAuthUser, stripe.authorize);
  app.get('/stripe/oauth/callback', stripe.callback);

  /**
   * Reset test-api database
   */
  app.get('/database/reset', test.resetTestDatabase);

  /**
   * Stripe subscriptions (recurring payments)
   */
  app.get('/subscriptions', mw.jwtScope('subscriptions'), subscriptions.getAll);
  app.post('/subscriptions/:subscriptionid/cancel', mw.jwtScope('subscriptions'), subscriptions.cancel);

  /**
   * Leaderboard
   */
  app.get('/leaderboard', mw.required('api_key'), mw.authorizeApp, groups.getLeaderboard); // Create a user.

  /**
   * Error handler.
   */
  app.use(function(err, req, res, next) {

    if (res.headersSent) {
      return next(err);
    }

    var name = err.name;

    if (name === 'UnauthorizedError') // because of jwt-express
      err.code = err.status;
    res.header('Cache-Control', 'no-cache');

    // Validation error.
    var e = name && name.toLowerCase ? name.toLowerCase() : '';
    if (e.indexOf('validation') !== -1)
      err = new errors.ValidationFailed(null, _.map(err.errors, function(e) { return e.path; }), err.message);
    else if (e.indexOf('uniqueconstraint') !== -1)
      err = new errors.ValidationFailed(null, _.map(err.errors, function(e) { return e.path; }), 'Unique Constraint Error.');

    if (!err.code) {
      var code = (err.type && err.type.indexOf('Stripe') > -1) ? 400 : 500;
      err.code = err.status || code;
    }

    console.error('Error Express : ', err);
    if (err.stack) console.log(err.stack);
    res.status(err.code).send({error: err});
  });

};
<commit_msg>Revert change to POST /groups/:groupid/transactions before providing cleaner fix<commit_after>var _ = require('lodash');
var serverStatus = require('express-server-status');
var roles = require('../constants/roles');
var jwt = require('../middlewares/jwt');

module.exports = function(app) {

  var aN = require('../middleware/security/authentication')(app);
  var aZ = require('../middleware/security/authorization')(app);

  /**
   * Public methods.
   */
  var Controllers = app.set('controllers');
  var mw = Controllers.middlewares;
  var users = Controllers.users;
  var params = Controllers.params;
  var groups = Controllers.groups;
  var activities = Controllers.activities;
  var notifications = Controllers.notifications;
  var transactions = Controllers.transactions;
  var payments = Controllers.payments;
  var paypal = Controllers.paypal;
  var images = Controllers.images;
  var cards = Controllers.cards;
  var webhooks = Controllers.webhooks;
  var stripe = Controllers.stripe;
  var test = Controllers.test;
  var subscriptions = Controllers.subscriptions;
  var errors = app.errors;

  const HOST = roles.HOST;
  const MEMBER = roles.MEMBER;

  /**
   * Status.
   */
  app.use('/status', serverStatus(app));

  /**
   * Parameters.
   */
  app.param('userid', params.userid);
  app.param('groupid', params.groupid);
  app.param('transactionid', params.transactionid);
  app.param('paranoidtransactionid', params.paranoidtransactionid);
  app.param('paykey', params.paykey);

  /**
   * User reset password flow (no jwt verification)
   */
  app.post('/users/password/forgot', aN.authenticateAppByApiKey, mw.required('email'), users.forgotPassword); // Send forgot password email
  app.post('/users/password/reset/:userid_enc/:reset_token', aN.authenticateAppByApiKey, mw.required('password', 'passwordConfirmation'), users.resetPassword); // Reset password

  app.post('/subscriptions/new_token', aN.authenticateAppByApiKey, mw.required('email'), subscriptions.sendNewTokenByEmail);

  /**
   * Routes without expiration validation
   */
  app.post('/subscriptions/refresh_token', aN.authenticateUserAndAppByJwtNoExpiry(), subscriptions.refreshTokenByEmail);

  /**
   * NotImplemented response.
   */
  var NotImplemented = function(req, res, next) {
    return next(new errors.NotImplemented('Not implemented yet.'));
  };

  /**
   * For testing the email templates
   */
  app.get('/email/:template', test.generateTestEmail);

  /**
   * Users.
   */
  app.post('/users', aN.authenticateAppByApiKey, aZ.appAccess(0.5), mw.required('user'), users.create); // Create a user.
  app.get('/users/:userid', aN.authenticateUserByJwt(), users.show); // Get a user.
  app.put('/users/:userid', aN.authenticateAppByApiKey, mw.required('user'), users.updateUserWithoutLoggedIn); // Update a user.
  app.put('/users/:userid/password', aZ.authorizeUserToAccessUser(), mw.required('password', 'passwordConfirmation'), users.updatePassword); // Update a user password.
  app.put('/users/:userid/paypalemail', mw.required('paypalEmail'), aZ.authorizeUserToAccessUser(), users.updatePaypalEmail); // Update a user paypal email.
  app.put('/users/:userid/avatar', mw.required('avatar'), aZ.authorizeUserToAccessUser(), users.updateAvatar); // Update a user's avatar
  app.get('/users/:userid/email', NotImplemented); // Confirm a user's email.
  // TODO why is this route duplicated?
  app.post('/users', aN.authenticateAppByApiKey, aZ.appAccess(0.5), mw.required('user'), users.create); // Create a user.

  /**
   * Authentication.
   */
  app.post('/authenticate', aN.authenticateAppByApiKey, aN.authenticateUserByPassword, users.getToken); // Authenticate user to get a token.
  app.post('/authenticate/refresh', NotImplemented); // Refresh the token (using a valid token OR a expired token + refresh_token).
  app.post('/authenticate/reset', NotImplemented); // Reset the refresh_token.

  /**
   * Credit card.
   *
   *  Let's assume for now a card is linked to a user.
   */
  app.get('/users/:userid/cards', aZ.authorizeUserToAccessUser(), cards.getCards); // Get a user's cards.
  app.post('/users/:userid/cards', NotImplemented); // Create a user's card.
  app.put('/users/:userid/cards/:cardid', NotImplemented); // Update a user's card.
  app.delete('/users/:userid/cards/:cardid', NotImplemented); // Delete a user's card.

  /**
   * Paypal Preapproval.
   */
  app.get('/users/:userid/paypal/preapproval', aZ.authorizeUserToAccessUser(), paypal.getPreapprovalKey); // Get a user's preapproval key.
  app.post('/users/:userid/paypal/preapproval/:preapprovalkey', aZ.authorizeUserToAccessUser(), paypal.confirmPreapproval); // Confirm a preapproval key.
  app.get('/users/:userid/paypal/preapproval/:preapprovalkey', aZ.authorizeUserToAccessUser(), paypal.getDetails); // Get a preapproval key details.

  /**
   * Groups.
   */
  app.post('/groups', aN.authenticateUserByJwt(), mw.required('group'), groups.create); // Create a group. Option `role` to assign the caller directly (default to null).
  app.get('/groups/:groupid', aZ.authorizeAccessToGroup({authIfPublic: true}), groups.getOne);
  app.get('/groups/:groupid/users', aZ.authorizeAccessToGroup({authIfPublic: true}),  mw.cache(60), groups.getUsers); // Get group users
  app.put('/groups/:groupid', aZ.authorizeAccessToGroup({userRoles: [HOST, MEMBER], bypassUserRolesCheckIfAuthenticatedAsAppAndNotUser: true}), mw.required('group'), groups.update); // Update a group.
  app.delete('/groups/:groupid', NotImplemented); // Delete a group.
  app.post('/groups/:groupid/payments', aN.authenticateUserOrApp(), mw.required('payment'), mw.getOrCreateUser, payments.post); // Make a payment/donation.
  app.post('/groups/:groupid/payments/paypal', aN.authenticateUserOrApp(), mw.required('payment'), payments.paypal); // Make a payment/donation.

  /**
   * UserGroup.
   *
   *  Relations between a group and a user.
   */
  app.get('/users/:userid/groups', aZ.authorizeUserToAccessUser(), users.getGroups); // Get user's groups.
  app.post('/groups/:groupid/users/:userid', aZ.authorizeAccessToGroup({userRoles: [HOST]}), groups.addUser); // Add a user to a group.
  app.put('/groups/:groupid/users/:userid', aZ.authorizeAccessToGroup({userRoles: [HOST]}), groups.updateUser); // Update a user's role in a group.
  app.delete('/groups/:groupid/users/:userid', aZ.authorizeAccessToGroup({userRoles: [HOST]}), groups.deleteUser); // Remove a user from a group.

  /**
   * Transactions (financial).
   */
  app.get('/groups/:groupid/transactions', aZ.authorizeAccessToGroup({authIfPublic: true}), mw.paginate(), mw.sorting({key: 'createdAt', dir: 'DESC'}), groups.getTransactions); // Get a group's transactions.

  app.use(mw.apiKey, jwt, mw.identifyFromToken, mw.checkJWTExpiration);

  // xdamman: having two times the same route is a mess (hard to read and error prone if we forget to return)
  // This is caused by mw.authorizeIfGroupPublic that is doing a next('route')
  // We should refactor this.
  app.post('/groups/:groupid/transactions', mw.authorizeIfGroupPublic, mw.authorizeAuthUserOrApp, mw.authorizeGroup, mw.required('transaction'), mw.getOrCreateUser, groups.createTransaction); // Create a transaction for a group.
  app.post('/groups/:groupid/transactions', mw.required('transaction'), mw.getOrCreateUser, groups.createTransaction); // Create a transaction for a group.

  app.get('/groups/:groupid/transactions/:transactionid', mw.authorizeIfGroupPublic, mw.authorizeAuthUserOrApp, mw.authorizeGroup, mw.authorizeTransaction, groups.getTransaction); // Get a transaction.
  app.get('/groups/:groupid/transactions/:transactionid', groups.getTransaction); // Get a transaction.
  app.put('/groups/:groupid/transactions/:transactionid', mw.authorizeAuthUserOrApp, mw.authorizeGroup, mw.authorizeTransaction, mw.required('transaction'), groups.updateTransaction); // Update a transaction.
  app.delete('/groups/:groupid/transactions/:transactionid', mw.authorizeAuthUserOrApp, mw.authorizeGroup, mw.authorizeGroupRoles(roles.HOST), mw.authorizeTransaction, groups.deleteTransaction); // Delete a transaction.

  app.post('/groups/:groupid/transactions/:transactionid/approve', mw.authorizeAuthUserOrApp, mw.authorizeGroup, mw.authorizeTransaction, mw.required('approved'), transactions.approve); // Approve a transaction.
  app.post('/groups/:groupid/transactions/:transactionid/pay', mw.authorizeAuthUser, mw.authorizeGroup, mw.authorizeGroupRoles([roles.HOST, roles.MEMBER]), mw.authorizeTransaction, mw.required('service'), transactions.pay); // Pay a transaction.
  app.get('/groups/:groupid/transactions/:transactionid/paykey', mw.authorizeAuthUserOrApp, mw.authorizeGroup, mw.authorizeGroupRoles([roles.HOST, roles.MEMBER]), mw.authorizeTransaction, transactions.getPayKey); // Get a transaction's pay key.
  app.post('/groups/:groupid/transactions/:transactionid/paykey/:paykey', mw.authorizeAuthUserOrApp, mw.authorizeGroup, mw.authorizeGroupRoles([roles.HOST, roles.MEMBER]), mw.authorizeTransaction, transactions.confirmPayment); // Confirm a transaction's payment.
  app.post('/groups/:groupid/transactions/:transactionid/attribution/:userid', mw.authorizeAuthUserOrApp, mw.authorizeGroup, mw.authorizeTransaction, mw.authorizeGroupRoles([roles.HOST, roles.MEMBER]), transactions.attributeUser); // Attribute a transaction to a user.
  app.get('/groups/:groupid/transactions/:paranoidtransactionid/callback', payments.paypalCallback); // Callback after a payment

  /**
   * Activities.
   *
   *  An activity is any action linked to a User or a Group.
   */
  app.get('/groups/:groupid/activities', mw.authorizeAuthUserOrApp, mw.authorizeGroup, mw.paginate(), mw.sorting({key: 'createdAt', dir: 'DESC'}), activities.group); // Get a group's activities.
  app.get('/users/:userid/activities', mw.authorizeAuthUser, mw.authorizeUser, mw.paginate(), mw.sorting({key: 'createdAt', dir: 'DESC'}), activities.user); // Get a user's activities.

  /**
   * Notifications.
   *
   *  A user can subscribe by email to any type of activity of a Group.
   */
  app.post('/groups/:groupid/activities/:activityType/subscribe', mw.authorizeAuthUser, mw.authorizeGroup, notifications.subscribe); // Subscribe to a group's activities
  app.post('/groups/:groupid/activities/:activityType/unsubscribe', mw.authorizeAuthUser, mw.authorizeGroup, notifications.unsubscribe); // Unsubscribe to a group's activities

  /**
   * Separate route for uploading images to S3
   */
  app.post('/images', mw.authorizeAuthUser, images.upload);

  /**
   * Webhook for stripe when it gets a new subscription invoice
   */
  app.post('/webhooks/stripe', webhooks.stripe);

  /**
   * Stripe oAuth
   */

  app.get('/stripe/authorize', mw.authorizeAuthUser, stripe.authorize);
  app.get('/stripe/oauth/callback', stripe.callback);

  /**
   * Reset test-api database
   */
  app.get('/database/reset', test.resetTestDatabase);

  /**
   * Stripe subscriptions (recurring payments)
   */
  app.get('/subscriptions', mw.jwtScope('subscriptions'), subscriptions.getAll);
  app.post('/subscriptions/:subscriptionid/cancel', mw.jwtScope('subscriptions'), subscriptions.cancel);

  /**
   * Leaderboard
   */
  app.get('/leaderboard', mw.required('api_key'), mw.authorizeApp, groups.getLeaderboard); // Create a user.

  /**
   * Error handler.
   */
  app.use(function(err, req, res, next) {

    if (res.headersSent) {
      return next(err);
    }

    var name = err.name;

    if (name === 'UnauthorizedError') // because of jwt-express
      err.code = err.status;
    res.header('Cache-Control', 'no-cache');

    // Validation error.
    var e = name && name.toLowerCase ? name.toLowerCase() : '';
    if (e.indexOf('validation') !== -1)
      err = new errors.ValidationFailed(null, _.map(err.errors, function(e) { return e.path; }), err.message);
    else if (e.indexOf('uniqueconstraint') !== -1)
      err = new errors.ValidationFailed(null, _.map(err.errors, function(e) { return e.path; }), 'Unique Constraint Error.');

    if (!err.code) {
      var code = (err.type && err.type.indexOf('Stripe') > -1) ? 400 : 500;
      err.code = err.status || code;
    }

    console.error('Error Express : ', err);
    if (err.stack) console.log(err.stack);
    res.status(err.code).send({error: err});
  });

};
<|endoftext|>


<commit_before><commit_msg>reload vector WMS layer upon load<commit_after><|endoftext|>


<commit_before>/*
This file is part of the Juju GUI, which lets users view and manage Juju
environments within a graphical interface (https://launchpad.net/juju-gui).
Copyright (C) 2012-2013 Canonical Ltd.

This program is free software: you can redistribute it and/or modify it under
the terms of the GNU Affero General Public License version 3, as published by
the Free Software Foundation.

This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero
General Public License for more details.

You should have received a copy of the GNU Affero General Public License along
with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

'use strict';

describe('deployer bar view', function() {
  var Y, container, dbObj, ECS, ecs, mockEvent, testUtils, utils, views,
      view, View, bundleHelpers;

  before(function(done) {
    Y = YUI(GlobalConfig).use(['deployer-bar',
                               'juju-views',
                               'juju-tests-utils',
                               'bundle-import-helpers',
                               'environment-change-set',
                               'event-simulate',
                               'node-event-simulate',
                               'node'], function(Y) {

      utils = Y.namespace('juju-tests.utils');
      views = Y.namespace('juju.views');
      ECS = Y.namespace('juju').EnvironmentChangeSet;
      View = views.DeployerBarView;
      bundleHelpers = Y.namespace('juju.BundleHelpers');
      mockEvent = { halt: function() {} };
      done();
    });
  });

  beforeEach(function() {
    ecs = new ECS({
      db: dbObj
    });
    container = utils.makeContainer(this, 'deployer-bar');
    view = new View({container: container, ecs: ecs}).render();
  });

  afterEach(function() {
    window.clearTimeout(ecs.descriptionTimer);
    ecs.destroy();
    container.remove(true);
    view.destroy();
  });

  it('should exist in the views namespace', function() {
    assert(views.DeployerBarView);
  });

  it('should apply the wrapping class to the container', function() {
    assert.equal(container.hasClass('deployer-bar'), true);
  });

  it('should increase changes when a service is added', function() {
    ecs.changeSet.abc123 = { foo: 'foo' };
    assert.equal(view._getChangeCount(ecs), 1);
  });

  it('should disabled the deploy button if there are no changes', function() {
    assert.equal(container.one('.deploy-button').hasClass('disabled'), true);
  });

  it('should enable the deploy button if there are changes', function() {
    ecs.lazyAddUnits(['django', 1]);
    assert.equal(container.one('.deploy-button').hasClass('disabled'), false);
  });

  it('should not show the summary panel when there are no changes', function() {
    container.one('.deploy-button').simulate('click');
    assert.equal(container.hasClass('summary-open'), false);
  });

  it('should show the summary panel when there are changes', function() {
    ecs.lazyAddUnits(['django', 1]);
    container.one('.deploy-button').simulate('click');
    assert.equal(container.hasClass('summary-open'), true);
  });

  it('shows a summary of major uncommitted changes for deployment', function() {
    // We must add at least one major change
    var machine = {};
    ecs.lazyAddMachines([[machine]], { modelId: 'new-0' });

    var changesStub = utils.makeStubMethod(view, '_getChangeCount', 0),
        deployStub = utils.makeStubMethod(view, '_getDeployedServices', []),
        relationsStub = utils.makeStubMethod(view, '_getAddRelations', []);
    this._cleanups.push(changesStub.reset);
    this._cleanups.push(deployStub.reset);
    this._cleanups.push(relationsStub.reset);
    view._showSummary();
    assert.equal(
        container.hasClass('summary-open'), true,
        'Summary is not open.'
    );
    assert.notEqual(
        container.one('.summary-panel'), null,
        'Summary panel HTML is not present.'
    );
    assert.notEqual(
        container.one('.post-summary'), null,
        'Deployment confirmation not present.'
    );
  });

  it('shows only the changelog if there are no major changes', function() {
    // We must add at least one chan
    var containerMachine = {
      parentId: 'new-0', containerType: 'lxc'
    };
    ecs.lazyAddMachines([[containerMachine]], { modelId: 'new-1' });
    var changesStub = utils.makeStubMethod(view, '_getChangeCount', 0),
        deployStub = utils.makeStubMethod(view, '_getDeployedServices', []),
        relationsStub = utils.makeStubMethod(view, '_getAddRelations', []);
    this._cleanups.push(changesStub.reset);
    this._cleanups.push(deployStub.reset);
    this._cleanups.push(relationsStub.reset);
    view._showSummary();
    assert.equal(
        container.hasClass('summary-open'), true,
        'Summary is not open.'
    );
    assert.equal(
        container.one('.summary-panel'), null,
        'Summary panel HTML exists when it should not.'
    );
    assert.notEqual(
        container.one('.post-summary'), null,
        'Deployment confirmation not present.'
    );
    assert.equal(
        container.one('.panel.summary .changes').hasClass('open'), true,
        'Changelog not open when it should be.'
    );
  });

  it('can toggle the recent changes in the summary panel', function() {
    var majorChangeStub = utils.makeStubMethod(view, '_hasMajorChanges', true);
    this._cleanups.push(majorChangeStub);
    view._showSummary();
    var changesNode = container.one('.panel.summary .changes');
    var toggleNode = changesNode.one('.toggle');
    assert.equal(changesNode.hasClass('open'), false,
        'The changes should initially be closed');
    toggleNode.simulate('click');
    assert.equal(changesNode.hasClass('open'), true,
        'The changes node should have had the open class added');
    toggleNode.simulate('click');
    assert.equal(changesNode.hasClass('open'), false,
        'The changes node should have had the open class removed');
  });

  it('can show a list of recent changes', function() {
    var changesStub = utils.makeStubMethod(view,
        '_generateAllChangeDescriptions', []);
    this._cleanups.push(changesStub.reset);
    view._showChanges();
    assert.equal(container.hasClass('changes-open'), true,
        'Changes should be open.');
    assert.notEqual(container.one('.panel.changes'), null,
        'Summary panel HTML should be present.');
  });

  it('should commit on confirmation', function() {
    var stubCommit = utils.makeStubMethod(ecs, 'commit');
    this._cleanups.push(stubCommit.reset);
    view.deploy(mockEvent);
    assert.equal(stubCommit.calledOnce(), true,
                 'ECS commit not called');
    assert.equal(container.hasClass('summary-open'), false,
                 'summary-open class still present');
  });

  it('closes the summary', function() {
    view.hideSummary(mockEvent);
    assert.equal(container.hasClass('summary-open'), false);
  });

  it('can convert relation endpoints to their real names', function() {
    var args = [
      ['wordpress', {
        name: 'db',
        role: 'server'
      }],
      ['84882221$', {
        name: 'db',
        role: 'client'
      }],
      function() {}
    ];
    view.set('db', {
      services: new Y.ModelList()
    });
    view.get('db').services.add([
      { id: 'foobar' },
      { id: '84882221$', displayName: '(mysql)' },
      { id: 'wordpress', displayName: 'wordpress' }
    ]);
    var services = view._getRealRelationEndpointNames(args);
    assert.deepEqual(services, ['mysql', 'wordpress']);
  });

  it('can generate descriptions for any change type', function() {
    var tests = [{
      icon: 'changes-service-added',
      msg: ' bar has been added.',
      change: {
        command: {
          method: '_deploy',
          args: ['foo', 'bar']
        }
      },
      time: '12:34 PM'
    }, {
      icon: 'changes-service-added',
      msg: ' 1 foo unit has been added.',
      change: {
        command: {
          method: '_add_unit',
          args: ['foo', 1]
        }
      }
    }, {
      icon: 'changes-service-added',
      msg: ' 2 foo units have been added.',
      change: {
        command: {
          method: '_add_unit',
          args: ['foo', 2]
        }
      }
    }, {
      icon: 'changes-relation-added',
      msg: 'bar relation added between foo and baz.',
      change: {
        command: {
          method: '_add_relation',
          args: [
            ['foo', { name: 'bar' }],
            ['baz']
          ]
        }
      }
    }, {
      icon: 'changes-container-created',
      msg: '1 container has been added.',
      change: {
        command: {
          method: '_addMachines',
          args: [[{ parentId: 1 }]]
        }
      }
    }, {
      icon: 'changes-container-created',
      msg: '2 containers have been added.',
      change: {
        command: {
          method: '_addMachines',
          args: [[{ parentId: 1 }, { parentId: 1 }]]
        }
      }
    }, {
      icon: 'changes-machine-created',
      msg: '1 machine has been added.',
      change: {
        command: {
          method: '_addMachines',
          args: [[{}]]
        }
      }
    }, {
      icon: 'changes-machine-created',
      msg: '2 machines have been added.',
      change: {
        command: {
          method: '_addMachines',
          args: [[{}, {}]]
        }
      }
    }, {
      icon: 'changes-config-changed',
      msg: 'Configuration values changed for foo.',
      change: {
        command: {
          method: '_set_config',
          args: ['foo']
        }
      }
    }, {
      icon: 'changes-service-exposed',
      msg: 'An unknown change has been made to this enviroment via the CLI.',
      change: {
        command: {
          method: '_anUnknownMethod'
        }
      }
    }];
    // This method needs to be stubbed out for the add relation path.
    var endpointNames = utils.makeStubMethod(
        view, '_getRealRelationEndpointNames', ['foo', 'baz']);
    this._cleanups.push(endpointNames.reset);
    tests.forEach(function(test) {
      var change = view._generateChangeDescription(test.change, true);
      assert.equal(change.icon, test.icon);
      assert.equal(change.description, test.msg);
      if (test.timestamp) {
        assert.equal(change.time, test.time);
      } else {
        assert.equal(change.time, '00:00');
      }
    });
  });

  it('can display the constraints', function() {
    ecs.lazyAddMachines([[{
      modelId: 'new0',
      constraints: {
        mem: 1024
      }
    }]], { modelId: 'new-0' });
    view._showSummary();
    assert.equal(container.one('.summary-panel li').get(
        'text').replace(/\s+/g, ' ').trim(),
        'new-0 created with constraints ( mem=1024MB )');
  });

  it('displays correctly with null constraints', function() {
    ecs.lazyAddMachines([[{
      modelId: 'new0',
      constraints: {
        mem: null
      }
    }]], { modelId: 'new-0' });
    view._showSummary();
    assert.equal(container.one('.summary-panel li').get(
        'text').replace(/\s+/g, ' ').trim(),
        'new-0 created');
  });

  it('can generate descriptions for all the changes in the ecs', function() {
    var stubDescription = utils.makeStubMethod(
        view,
        '_generateChangeDescription');
    this._cleanups.push(stubDescription.reset);
    ecs.changeSet = { foo: {}, bar: {} };
    view._generateAllChangeDescriptions(ecs);
    assert.equal(stubDescription.callCount(), 2);
  });

  it('provides a way to retrieve the service icon', function() {
    var url = view._getServiceIconUrl('django');
    assert.strictEqual(
        url,
        'https://manage.jujucharms.com' +
        '/api/3/charm/precise/django/file/icon.svg'
    );
  });

  it('retrieves all the unit changes', function() {
    ecs.lazyAddUnits(['django', 1]);
    ecs.lazyAddUnits(['rails', 2]);
    var results = view._getChanges(ecs).addUnits;
    assert.lengthOf(results, 2);
    assert.deepEqual(results[0], {
      icon: 'https://manage.jujucharms.com' +
          '/api/3/charm/precise/django/file/icon.svg',
      numUnits: 1,
      serviceName: 'django'
    });
    assert.deepEqual(results[1], {
      icon: 'https://manage.jujucharms.com' +
          '/api/3/charm/precise/rails/file/icon.svg',
      numUnits: 2,
      serviceName: 'rails'
    });
  });

  it('retrieves all the machine changes', function() {
    var machine = {};
    ecs.lazyAddMachines([[machine]], { modelId: 'new-0' });
    ecs.lazyAddMachines([[machine]], { modelId: 'new-1' });
    var results = view._getChanges(ecs).addMachines;
    assert.lengthOf(results, 2);
    assert.deepEqual(results[0], machine);
    assert.deepEqual(results[1], machine);
  });

  it('ignores container changes in the summary', function() {
    var machine = {};
    ecs.lazyAddMachines([[machine]], { modelId: 'new-0' });
    var container = {
      parentId: 'new-0', containerType: 'lxc'
    };
    ecs.lazyAddMachines([[container]], { modelId: 'new-1' });
    var results = view._getChanges(ecs).addMachines;
    assert.lengthOf(results, 1);
    assert.deepEqual(results[0], machine);
  });

  it('can export the environment', function() {
    var exportStub = utils.makeStubMethod(bundleHelpers, 'exportYAML');
    this._cleanups.push(exportStub.reset);
    container.one('.export').simulate('click');
    assert.equal(exportStub.calledOnce(), true,
        'exportYAML should have been called');
  });

  it('can open the import file dialogue', function() {
    // The file input is hidden and the dialogue is opened by a
    // javascript click event. Stubbing out the click so we can test
    // that it is called.
    var clickStub = utils.makeStubMethod(container.one(
        '.import-file').getDOMNode(), 'click');
    this._cleanups.push(clickStub.reset);
    container.one('.import').simulate('click');
    assert.equal(clickStub.calledOnce(), true,
        'the file input should have been clicked');
  });

  it('can import a bundle file', function() {
    var importStub = utils.makeStubMethod(bundleHelpers, 'deployBundleFiles');
    this._cleanups.push(importStub.reset);
    container.one('.import-file').simulate('change');
    assert.equal(importStub.calledOnce(), true,
        'deployBundleFiles should have been called');
  });

  it('can set the height mode to small', function() {
    assert.equal(container.hasClass('mode-min'), false);
    container.one('.action-list .min').simulate('click');
    assert.equal(container.hasClass('mode-min'), true);
  });

  it('can set the height mode to medium', function() {
    assert.equal(container.hasClass('mode-mid'), false);
    container.one('.action-list .mid').simulate('click');
    assert.equal(container.hasClass('mode-mid'), true);
  });

  it('can set the height mode to large', function() {
    assert.equal(container.hasClass('mode-max'), false);
    container.one('.action-list .max').simulate('click');
    assert.equal(container.hasClass('mode-max'), true);
  });

  it('changes the deploy label after the first deploy', function() {
    var deployButton = container.one('.deploy-button');
    assert.equal(deployButton.get('text').trim(), 'Deploy');
    view.deploy({halt: utils.makeStubFunction()});
    assert.equal(deployButton.get('text').trim(), 'Commit');
  });

  it('shows the commit label after deploy and re-render', function() {
    var deployButton = container.one('.deploy-button');
    view.deploy({halt: utils.makeStubFunction()});
    assert.equal(deployButton.get('text').trim(), 'Commit');
    view.render();
    assert.equal(deployButton.get('text').trim(), 'Commit');
  });
});
<commit_msg>Fix tests for change descriptions<commit_after>/*
This file is part of the Juju GUI, which lets users view and manage Juju
environments within a graphical interface (https://launchpad.net/juju-gui).
Copyright (C) 2012-2013 Canonical Ltd.

This program is free software: you can redistribute it and/or modify it under
the terms of the GNU Affero General Public License version 3, as published by
the Free Software Foundation.

This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero
General Public License for more details.

You should have received a copy of the GNU Affero General Public License along
with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

'use strict';

describe('deployer bar view', function() {
  var Y, container, dbObj, ECS, ecs, mockEvent, testUtils, utils, views,
      view, View, bundleHelpers;

  before(function(done) {
    Y = YUI(GlobalConfig).use(['deployer-bar',
                               'juju-views',
                               'juju-tests-utils',
                               'bundle-import-helpers',
                               'environment-change-set',
                               'event-simulate',
                               'node-event-simulate',
                               'node'], function(Y) {

      utils = Y.namespace('juju-tests.utils');
      views = Y.namespace('juju.views');
      ECS = Y.namespace('juju').EnvironmentChangeSet;
      View = views.DeployerBarView;
      bundleHelpers = Y.namespace('juju.BundleHelpers');
      mockEvent = { halt: function() {} };
      done();
    });
  });

  beforeEach(function() {
    ecs = new ECS({
      db: dbObj
    });
    container = utils.makeContainer(this, 'deployer-bar');
    view = new View({container: container, ecs: ecs}).render();
  });

  afterEach(function() {
    window.clearTimeout(ecs.descriptionTimer);
    ecs.destroy();
    container.remove(true);
    view.destroy();
  });

  it('should exist in the views namespace', function() {
    assert(views.DeployerBarView);
  });

  it('should apply the wrapping class to the container', function() {
    assert.equal(container.hasClass('deployer-bar'), true);
  });

  it('should increase changes when a service is added', function() {
    ecs.changeSet.abc123 = { foo: 'foo' };
    assert.equal(view._getChangeCount(ecs), 1);
  });

  it('should disabled the deploy button if there are no changes', function() {
    assert.equal(container.one('.deploy-button').hasClass('disabled'), true);
  });

  it('should enable the deploy button if there are changes', function() {
    ecs.lazyAddUnits(['django', 1]);
    assert.equal(container.one('.deploy-button').hasClass('disabled'), false);
  });

  it('should not show the summary panel when there are no changes', function() {
    container.one('.deploy-button').simulate('click');
    assert.equal(container.hasClass('summary-open'), false);
  });

  it('should show the summary panel when there are changes', function() {
    ecs.lazyAddUnits(['django', 1]);
    container.one('.deploy-button').simulate('click');
    assert.equal(container.hasClass('summary-open'), true);
  });

  it('shows a summary of major uncommitted changes for deployment', function() {
    // We must add at least one major change
    var machine = {};
    ecs.lazyAddMachines([[machine]], { modelId: 'new-0' });

    var changesStub = utils.makeStubMethod(view, '_getChangeCount', 0),
        deployStub = utils.makeStubMethod(view, '_getDeployedServices', []),
        relationsStub = utils.makeStubMethod(view, '_getAddRelations', []);
    this._cleanups.push(changesStub.reset);
    this._cleanups.push(deployStub.reset);
    this._cleanups.push(relationsStub.reset);
    view._showSummary();
    assert.equal(
        container.hasClass('summary-open'), true,
        'Summary is not open.'
    );
    assert.notEqual(
        container.one('.summary-panel'), null,
        'Summary panel HTML is not present.'
    );
    assert.notEqual(
        container.one('.post-summary'), null,
        'Deployment confirmation not present.'
    );
  });

  it('shows only the changelog if there are no major changes', function() {
    // We must add at least one chan
    var containerMachine = {
      parentId: 'new-0', containerType: 'lxc'
    };
    ecs.lazyAddMachines([[containerMachine]], { modelId: 'new-1' });
    var changesStub = utils.makeStubMethod(view, '_getChangeCount', 0),
        deployStub = utils.makeStubMethod(view, '_getDeployedServices', []),
        relationsStub = utils.makeStubMethod(view, '_getAddRelations', []);
    this._cleanups.push(changesStub.reset);
    this._cleanups.push(deployStub.reset);
    this._cleanups.push(relationsStub.reset);
    view._showSummary();
    assert.equal(
        container.hasClass('summary-open'), true,
        'Summary is not open.'
    );
    assert.equal(
        container.one('.summary-panel'), null,
        'Summary panel HTML exists when it should not.'
    );
    assert.notEqual(
        container.one('.post-summary'), null,
        'Deployment confirmation not present.'
    );
    assert.equal(
        container.one('.panel.summary .changes').hasClass('open'), true,
        'Changelog not open when it should be.'
    );
  });

  it('can toggle the recent changes in the summary panel', function() {
    var majorChangeStub = utils.makeStubMethod(view, '_hasMajorChanges', true);
    this._cleanups.push(majorChangeStub);
    view._showSummary();
    var changesNode = container.one('.panel.summary .changes');
    var toggleNode = changesNode.one('.toggle');
    assert.equal(changesNode.hasClass('open'), false,
        'The changes should initially be closed');
    toggleNode.simulate('click');
    assert.equal(changesNode.hasClass('open'), true,
        'The changes node should have had the open class added');
    toggleNode.simulate('click');
    assert.equal(changesNode.hasClass('open'), false,
        'The changes node should have had the open class removed');
  });

  it('can show a list of recent changes', function() {
    var changesStub = utils.makeStubMethod(view,
        '_generateAllChangeDescriptions', []);
    this._cleanups.push(changesStub.reset);
    view._showChanges();
    assert.equal(container.hasClass('changes-open'), true,
        'Changes should be open.');
    assert.notEqual(container.one('.panel.changes'), null,
        'Summary panel HTML should be present.');
  });

  it('should commit on confirmation', function() {
    var stubCommit = utils.makeStubMethod(ecs, 'commit');
    this._cleanups.push(stubCommit.reset);
    view.deploy(mockEvent);
    assert.equal(stubCommit.calledOnce(), true,
                 'ECS commit not called');
    assert.equal(container.hasClass('summary-open'), false,
                 'summary-open class still present');
  });

  it('closes the summary', function() {
    view.hideSummary(mockEvent);
    assert.equal(container.hasClass('summary-open'), false);
  });

  it('can convert relation endpoints to their real names', function() {
    var args = [
      ['wordpress', {
        name: 'db',
        role: 'server'
      }],
      ['84882221$', {
        name: 'db',
        role: 'client'
      }],
      function() {}
    ];
    view.set('db', {
      services: new Y.ModelList()
    });
    view.get('db').services.add([
      { id: 'foobar' },
      { id: '84882221$', displayName: '(mysql)' },
      { id: 'wordpress', displayName: 'wordpress' }
    ]);
    var services = view._getRealRelationEndpointNames(args);
    assert.deepEqual(services, ['mysql', 'wordpress']);
  });

  it('can generate descriptions for any change type', function() {
    var tests = [{
      icon: 'changes-service-added',
      msg: ' bar has been added.',
      change: {
        command: {
          method: '_deploy',
          args: ['foo', 'bar']
        }
      },
      time: '12:34 PM'
    }, {
      icon: 'changes-units-added',
      msg: ' 1 foo unit has been added.',
      change: {
        command: {
          method: '_add_unit',
          args: ['foo', 1]
        }
      }
    }, {
      icon: 'changes-units-removed',
      msg: '1 unit has been removed from foo',
      change: {
        command: {
          method: '_remove_units',
          args: [['foo/0']]
        }
      }
    }, {
      icon: 'changes-units-added',
      msg: ' 2 foo units have been added.',
      change: {
        command: {
          method: '_add_unit',
          args: ['foo', 2]
        }
      }
    }, {
      icon: 'changes-relation-added',
      msg: 'bar relation added between foo and baz.',
      change: {
        command: {
          method: '_add_relation',
          args: [
            ['foo', { name: 'bar' }],
            ['baz']
          ]
        }
      }
    }, {
      icon: 'changes-container-created',
      msg: '1 container has been added.',
      change: {
        command: {
          method: '_addMachines',
          args: [[{ parentId: 1 }]]
        }
      }
    }, {
      icon: 'changes-container-created',
      msg: '2 containers have been added.',
      change: {
        command: {
          method: '_addMachines',
          args: [[{ parentId: 1 }, { parentId: 1 }]]
        }
      }
    }, {
      icon: 'changes-machine-created',
      msg: '1 machine has been added.',
      change: {
        command: {
          method: '_addMachines',
          args: [[{}]]
        }
      }
    }, {
      icon: 'changes-machine-created',
      msg: '2 machines have been added.',
      change: {
        command: {
          method: '_addMachines',
          args: [[{}, {}]]
        }
      }
    }, {
      icon: 'changes-config-changed',
      msg: 'Configuration values changed for foo.',
      change: {
        command: {
          method: '_set_config',
          args: ['foo']
        }
      }
    }, {
      icon: 'changes-service-exposed',
      msg: 'An unknown change has been made to this enviroment via the CLI.',
      change: {
        command: {
          method: '_anUnknownMethod'
        }
      }
    }];
    // This method needs to be stubbed out for the add relation path.
    var endpointNames = utils.makeStubMethod(
        view, '_getRealRelationEndpointNames', ['foo', 'baz']);
    this._cleanups.push(endpointNames.reset);
    tests.forEach(function(test) {
      var change = view._generateChangeDescription(test.change, true);
      assert.equal(change.icon, test.icon);
      assert.equal(change.description, test.msg);
      if (test.timestamp) {
        assert.equal(change.time, test.time);
      } else {
        assert.equal(change.time, '00:00');
      }
    });
  });

  it('can display the constraints', function() {
    ecs.lazyAddMachines([[{
      modelId: 'new0',
      constraints: {
        mem: 1024
      }
    }]], { modelId: 'new-0' });
    view._showSummary();
    assert.equal(container.one('.summary-panel li').get(
        'text').replace(/\s+/g, ' ').trim(),
        'new-0 created with constraints ( mem=1024MB )');
  });

  it('displays correctly with null constraints', function() {
    ecs.lazyAddMachines([[{
      modelId: 'new0',
      constraints: {
        mem: null
      }
    }]], { modelId: 'new-0' });
    view._showSummary();
    assert.equal(container.one('.summary-panel li').get(
        'text').replace(/\s+/g, ' ').trim(),
        'new-0 created');
  });

  it('can generate descriptions for all the changes in the ecs', function() {
    var stubDescription = utils.makeStubMethod(
        view,
        '_generateChangeDescription');
    this._cleanups.push(stubDescription.reset);
    ecs.changeSet = { foo: {}, bar: {} };
    view._generateAllChangeDescriptions(ecs);
    assert.equal(stubDescription.callCount(), 2);
  });

  it('provides a way to retrieve the service icon', function() {
    var url = view._getServiceIconUrl('django');
    assert.strictEqual(
        url,
        'https://manage.jujucharms.com' +
        '/api/3/charm/precise/django/file/icon.svg'
    );
  });

  it('retrieves all the unit changes', function() {
    ecs.lazyAddUnits(['django', 1]);
    ecs.lazyAddUnits(['rails', 2]);
    var results = view._getChanges(ecs).addUnits;
    assert.lengthOf(results, 2);
    assert.deepEqual(results[0], {
      icon: 'https://manage.jujucharms.com' +
          '/api/3/charm/precise/django/file/icon.svg',
      numUnits: 1,
      serviceName: 'django'
    });
    assert.deepEqual(results[1], {
      icon: 'https://manage.jujucharms.com' +
          '/api/3/charm/precise/rails/file/icon.svg',
      numUnits: 2,
      serviceName: 'rails'
    });
  });

  it('retrieves all the machine changes', function() {
    var machine = {};
    ecs.lazyAddMachines([[machine]], { modelId: 'new-0' });
    ecs.lazyAddMachines([[machine]], { modelId: 'new-1' });
    var results = view._getChanges(ecs).addMachines;
    assert.lengthOf(results, 2);
    assert.deepEqual(results[0], machine);
    assert.deepEqual(results[1], machine);
  });

  it('ignores container changes in the summary', function() {
    var machine = {};
    ecs.lazyAddMachines([[machine]], { modelId: 'new-0' });
    var container = {
      parentId: 'new-0', containerType: 'lxc'
    };
    ecs.lazyAddMachines([[container]], { modelId: 'new-1' });
    var results = view._getChanges(ecs).addMachines;
    assert.lengthOf(results, 1);
    assert.deepEqual(results[0], machine);
  });

  it('can export the environment', function() {
    var exportStub = utils.makeStubMethod(bundleHelpers, 'exportYAML');
    this._cleanups.push(exportStub.reset);
    container.one('.export').simulate('click');
    assert.equal(exportStub.calledOnce(), true,
        'exportYAML should have been called');
  });

  it('can open the import file dialogue', function() {
    // The file input is hidden and the dialogue is opened by a
    // javascript click event. Stubbing out the click so we can test
    // that it is called.
    var clickStub = utils.makeStubMethod(container.one(
        '.import-file').getDOMNode(), 'click');
    this._cleanups.push(clickStub.reset);
    container.one('.import').simulate('click');
    assert.equal(clickStub.calledOnce(), true,
        'the file input should have been clicked');
  });

  it('can import a bundle file', function() {
    var importStub = utils.makeStubMethod(bundleHelpers, 'deployBundleFiles');
    this._cleanups.push(importStub.reset);
    container.one('.import-file').simulate('change');
    assert.equal(importStub.calledOnce(), true,
        'deployBundleFiles should have been called');
  });

  it('can set the height mode to small', function() {
    assert.equal(container.hasClass('mode-min'), false);
    container.one('.action-list .min').simulate('click');
    assert.equal(container.hasClass('mode-min'), true);
  });

  it('can set the height mode to medium', function() {
    assert.equal(container.hasClass('mode-mid'), false);
    container.one('.action-list .mid').simulate('click');
    assert.equal(container.hasClass('mode-mid'), true);
  });

  it('can set the height mode to large', function() {
    assert.equal(container.hasClass('mode-max'), false);
    container.one('.action-list .max').simulate('click');
    assert.equal(container.hasClass('mode-max'), true);
  });

  it('changes the deploy label after the first deploy', function() {
    var deployButton = container.one('.deploy-button');
    assert.equal(deployButton.get('text').trim(), 'Deploy');
    view.deploy({halt: utils.makeStubFunction()});
    assert.equal(deployButton.get('text').trim(), 'Commit');
  });

  it('shows the commit label after deploy and re-render', function() {
    var deployButton = container.one('.deploy-button');
    view.deploy({halt: utils.makeStubFunction()});
    assert.equal(deployButton.get('text').trim(), 'Commit');
    view.render();
    assert.equal(deployButton.get('text').trim(), 'Commit');
  });
});
<|endoftext|>


<commit_before>
var renderer, scene, camera, pointLight;

// field variables
var fieldWidth = 400,
    fieldHeight = 200;

// paddle variables
var paddleWidth, paddleHeight, paddleDepth, paddleQuality, paddle1, paddle2;
var paddle1DirY = 0,
    paddle2DirY = 0,
    paddleSpeed = 3;

// ball variables
var ball;
var ballDirX = 1,
    ballDirY = 1,
    ballSpeed = 3;

// game-related variables
var difficulty = 0.2;
var score1 = 0,
    score2 = 0,
    maxScore = 7;

function setup() {

    // set up all the 3D objects in the scene
    createScene();

    // and let's get cracking!
    draw();
}

function createScene() {
    // set the scene size
    var WIDTH = 640,
        HEIGHT = 360;

    // set camera attributes
    var VIEW_ANGLE = 50,
        ASPECT = WIDTH / HEIGHT,
        NEAR = 0.1,
        FAR = 10000;

    var c = document.getElementById("gameCanvas");

    // create a WebGL renderer, camera and scene
    renderer = new THREE.WebGLRenderer();
    scene = new THREE.Scene();
    camera = new THREE.PerspectiveCamera(VIEW_ANGLE, ASPECT, NEAR, FAR);

    // add the camera to the scene
    scene.add(camera);

    // set a default position for the camera
    // not doing this somehow messes up shadow rendering
    camera.position.z = 320;

    // start the renderer
    renderer.setSize(WIDTH, HEIGHT);

    // attach the render-supplied DOM element (the gameCanvas)
    c.appendChild(renderer.domElement);

    // set up the sphere vars
    // lower 'segment' and 'rings' values will increase performance
    var radius = 5,
        segments = 6,
        rings = 6;

    // create the sphere's material
    var sphereMaterial = new THREE.MeshLambertMaterial({color: 0xD43001});

    // Create a ball with sphere geometry
    ball = new THREE.Mesh(
        new THREE.SphereGeometry(radius, segments, rings),
        sphereMaterial
    );

    // add the sphere to the scene
    scene.add(ball);

    // create a point light
    pointLight = new THREE.PointLight(0xF8D898);

    // set its position
    pointLight.position.x = -1000;
    pointLight.position.y = 0;
    pointLight.position.z = 1000;
    pointLight.intensity = 2.9;
    pointLight.distance = 10000;

    // add to the scene
    scene.add(pointLight);

    var planeWidth = fieldWidth,
        planeHeight = fieldHeight,
        planeQuality = 10;

    // create the plane's material
    var planeMaterial = new THREE.MeshLambertMaterial({color: 0x4BD121});

    // create the playing surface plane
    var plane = new THREE.Mesh(
        new THREE.PlaneGeometry(planeWidth * 0.95, planeHeight, planeQuality, planeQuality),
        planeMaterial
    );

    scene.add(plane);

    // set up the paddle vars
    paddleWidth = 10;
    paddleHeight = 30;
    paddleDepth = 10;
    paddleQuality = 1;

    // create the paddle1's material
    var paddle1Material = new THREE.MeshLambertMaterial({color: 0x1B32C0});
    // create the paddle2's material
    var paddle2Material = new THREE.MeshLambertMaterial({color: 0xFF4045});

    // set up paddle 1
    paddle1 = new THREE.Mesh(
        new THREE.CubeGeometry(paddleWidth, paddleHeight, paddleDepth, paddleQuality, paddleQuality, paddleQuality),
        paddle1Material
    );

    // add the padle to the scene
    scene.add(paddle1);

    // set up paddle 2
    paddle2 = new THREE.Mesh(
        new THREE.CubeGeometry(paddleWidth, paddleHeight, paddleDepth, paddleQuality, paddleQuality, paddleQuality),
        paddle2Material
    );

    // add the second padle to the scene
    scene.add(paddle2);

    // set paddles on each side of the table
    paddle1.position.x = -fieldWidth/2 + paddleWidth;
    paddle2.position.x = fieldWidth/2 - paddleWidth;

    // lift paddles over playing surface
    paddle1.position.z = paddleDepth;
    paddle2.position.z = paddleDepth;
}

function draw() {
    // draw THREE.JS scene
    renderer.render(scene, camera);

    // loop the draw() function
    requestAnimationFrame(draw);

    // process game logic
    ballPhysics();
    paddlePhysics();
    playerPaddleMovement();
    opponentPaddleMovement();
}

function ballPhysics() {
    // if ball goes off the 'left' side (Player's side)
    if (ball.position.x <= -fieldWidth/2) {
        // CPU scores
        score2++;
        // update scoreboard
        document.getElementById("scores").innerHTML = score1 + "-" + score2;
        // reset ball
        resetBall(2);
        // check if match over
        matchScoreCheck();
    }
    // if ball goes off the 'right' side (CPU's side)
    if (ball.position.x >= fieldWidth/2) {
        // Player scores
        score1++;
        // update scoreboard
        document.getElementById("scores").innerHTML = score1 + "-" + score2;
        // reset ball
        resetBall(1);
        // check if match over
        matchScoreCheck();
    }

    // if ball goes off the top side (side of table)
    if (ball.position.y <= -fieldHeight/2) {
        ballDirY = -ballDirY;
    }

    // if ball goes off the bottom side (side of table)
    if (ball.position.y >= fieldHeight/2) {
        ballDirY = -ballDirY;
    }

    // update ball position over time
    ball.position.x += ballDirX * ballSpeed;
    ball.position.y += ballDirY * ballSpeed;

    // limit ball's y-speed to 2x the x-speed
    // this is so the ball doesn't speed from left to right super fast
    // keeps game playable for humans
    if (ballDirY > ballSpeed * 2) {
        ballDirY = ballSpeed * 2;
    } else if (ballDirY < -ballSpeed * 2) {
        ballDirY = -ballSpeed * 2;
    }
}

function playerPaddleMovement() {
    // move left
    if (Key.isDown(Key.A)) {
        // if paddle is not touching the side of table
        // we move
        if (paddle1.position.y < fieldHeight * 0.45) {
            paddle1DirY = paddleSpeed * 0.5;
        }
        // else we do not move and stretch the paddle
        // to indicate we can't move
        else {
            paddle1DirY = 0;
            paddle1.scale.z += (10 - paddle1.scale.z) * 0.2;
        }
    }
    // move right
    else if (Key.isDown(Key.D)) {
        // if paddle is not touching the side of table
        // we move
        if (paddle1.position.y > -fieldHeight * 0.45) {
            paddle1DirY = -paddleSpeed * 0.5;
        }
        // else we do not move and stretch the paddle
        // to indicate we can't move
        else {
            paddle1DirY = 0;
            paddle1.scale.z += (10 - paddle1.scale.z) * 0.2;
        }
    }
    // else don't move paddle
    else {
        paddle1DirY = 0;
    }

    paddle1.scale.y += (1 - paddle1.scale.y) * 0.2;
    paddle1.scale.z += (1 - paddle1.scale.z) * 0.2;

    paddle1.position.y += paddle1DirY;
}

function opponentPaddleMovement() {
    // lerp towards the ball on the y plane
    paddle2DirY = (ball.position.y - paddle2.position.y) * difficulty;

    // in case the Lerp function produces a value above max paddle speed, we clamp it
    if (Math.abs(paddle2DirY) <= paddleSpeed) {
        paddle2.position.y += paddle2DirY;
    }
    // if the lerp value is too high, we have to limit speed to paddleSpeed
    else {
        // if paddle is lerping in +ve direction
        if (paddle2DirY > paddleSpeed) {
            paddle2.position.y += paddleSpeed;
        }
        // if paddle is lerping in -ve direction
        else if (paddle2DirY < -paddleSpeed) {
            paddle2.position.y -= paddleSpeed;
        }
    }

    // we lerp the scale back to 1
    // this is done because we stretch the paddle at some points
    // stretching is done when paddle touches side of table and when paddle hits ball
    // by doing this here, we ensure paddle always comes back to default size
    paddle2.scale.y += (1 - paddle2.scale.y) * 0.2;
}

// Handles paddle collision logic
function paddlePhysics() {
    // Player paddle logic

    // if ball is aligned with paddle1 on x plane
    // remember the position is the CENTER of the object
    // we only check between the front and the middle of the paddle (one-way collision)
    if (ball.position.x <= paddle1.position.x + paddleWidth &&
        ball.position.x >= paddle1.position.x) {
        // and if ball is aligned with paddle1 on y plane
        if (ball.position.y <= paddle1.position.y + paddleHeight/2 &&
            ball.position.y >= paddle1.position.y - paddleHeight/2) {
            // ball is intersecting with the front half of the paddle
            // and if ball is travelling towards player (-ve direction)
            if (ballDirX < 0) {
                // stretch the paddle to indicate a hit
                paddle1.scale.y = 5;

                // switch direction of ball travel to create bounce
                ballDirX = -ballDirX;

                // we impact ball angle when hitting it
                // this is not realistic physics, just spices up the gameplay
                // allows you to 'slice' the ball to beat the opponent
                ballDirY -= paddle1DirY * 0.7;
            }
        }
    }

    // Opponent paddle logic

    // if ball is aligned with paddle2 on x plane
    // remember the position is the CENTER of the object
    // we only check between the front and the middle of the paddle (one-way collision)
    if (ball.position.x <= paddle2.position.x + paddleWidth &&
        ball.position.x >= paddle2.position.x) {
        // and if ball is aligned with paddle2 on y plane
        if (ball.position.y <= paddle2.position.y + paddleHeight/2 &&
            ball.position.y >= paddle2.position.y - paddleHeight/2) {
            // ball is intersecting with the front half of the paddle
            // and if ball is travelling towards opponent (+ve direction)
            if (ballDirX > 0) {
                // stretch the paddle to indicate a hit
                paddle2.scale.y = 5;

                // switch direction of ball travel to create bounce
                ballDirX = -ballDirX;

                // we impact ball angle when hitting it
                // this is not realistic physics, just spices up the gameplay
                // allows you to 'slice' the ball to beat the opponent
                ballDirY -= paddle2DirY * 0.7;
            }
        }
    }
}

// resets the ball's position to the centre of the play area
// also sets the ball direction speed towards the last point winner
function resetBall(loser) {
    // position the ball in the center of the table
    ball.position.x = 0;
    ball.position.y = 0;

    // if player lost the last point, we send the ball to the opponent
    if (loser == 1) {
        ballDirX = -1;
    }
    // else if opponent lost, we send the ball to player
    else {
        ballDirX = 1;
    }

    // set the ball to move +ve in y plane (towards left from the camera)
    ballDirY = 1;
}

// checks if either player or opponent has reached 7 points
function matchScoreCheck() {
    // if player has 7 points
    if (score1 >= maxScore) {
        // stop the ball
        ballSpeed = 0;
        // write to the banner
        document.getElementById("scores").innerHTML = "Player wins!";
        document.getElementById("winnerBoard").innerHTML = "Refresh to play again";
    }
    // else if opponent has 7 points
    else if (score2 >= maxScore) {
        // stop the ball
        ballSpeed = 0;
        // write to the banner
        document.getElementById("scores").innerHTML = "CPU wins!";
        document.getElementById("winnerBoard").innerHTML = "Refresh to play again";
    }
}<commit_msg>prettifying the game<commit_after>
var renderer, scene, camera, pointLight, spotLight;

// field variables
var fieldWidth = 400,
    fieldHeight = 200;

// paddle variables
var paddleWidth, paddleHeight, paddleDepth, paddleQuality, paddle1, paddle2;
var paddle1DirY = 0,
    paddle2DirY = 0,
    paddleSpeed = 3;

// ball variables
var ball;
var ballDirX = 1,
    ballDirY = 1,
    ballSpeed = 3;

// game-related variables
var difficulty = 0.2;
var score1 = 0,
    score2 = 0,
    maxScore = 7;

function setup() {
    document.getElementById("winnerBoard").innerHTML = "First to " + maxScore + "wins!";

    // set up all the 3D objects in the scene
    createScene();

    // and let's get cracking!
    draw();
}

function createScene() {
    // set the scene size
    var WIDTH = 640,
        HEIGHT = 360;

    // set camera attributes
    var VIEW_ANGLE = 50,
        ASPECT = WIDTH / HEIGHT,
        NEAR = 0.1,
        FAR = 10000;

    var c = document.getElementById("gameCanvas");

    // create a WebGL renderer, camera and scene
    renderer = new THREE.WebGLRenderer();
    scene = new THREE.Scene();
    camera = new THREE.PerspectiveCamera(VIEW_ANGLE, ASPECT, NEAR, FAR);

    // add the camera to the scene
    scene.add(camera);

    // set a default position for the camera
    // not doing this somehow messes up shadow rendering
    camera.position.z = 320;

    // start the renderer
    renderer.setSize(WIDTH, HEIGHT);

    // attach the render-supplied DOM element (the gameCanvas)
    c.appendChild(renderer.domElement);

    // set up the sphere vars
    // lower 'segment' and 'rings' values will increase performance
    var radius = 5,
        segments = 6,
        rings = 6;

    // create the sphere's material
    var sphereMaterial = new THREE.MeshLambertMaterial({color: 0xD43001});

    // Create a ball with sphere geometry
    ball = new THREE.Mesh(
        new THREE.SphereGeometry(radius, segments, rings),
        sphereMaterial
    );

    // add the sphere to the scene
    scene.add(ball);

    ball.position.x = 0;
    ball.position.y = 0;
    // set ball above the table surface
    ball.position.z = radius;

    ball.receiveShadow = true;
    ball.castShadow = true;

    // create a point light
    pointLight = new THREE.PointLight(0xF8D898);

    // set its position
    pointLight.position.x = -1000;
    pointLight.position.y = 0;
    pointLight.position.z = 1000;
    pointLight.intensity = 2.9;
    pointLight.distance = 10000;

    // add to the scene
    scene.add(pointLight);

    // add a spot light
    // this is important for casting shadows
    spotLight = new THREE.SpotLight(0xF8D898);
    spotLight.position.set(0, 0, 460);
    spotLight.intensity = 1.5;
    spotLight.castShadow = true;
    scene.add(spotLight);

    renderer.shadowMapEnabled = true;

    var planeWidth = fieldWidth,
        planeHeight = fieldHeight,
        planeQuality = 10;

    // create the plane's material
    var planeMaterial = new THREE.MeshLambertMaterial({color: 0x4BD121});

    // create the playing surface plane
    var plane = new THREE.Mesh(
        new THREE.PlaneGeometry(planeWidth * 0.95, planeHeight, planeQuality, planeQuality),
        planeMaterial
    );

    scene.add(plane);

    plane.receiveShadow = true;
    plane.castShadow = true;

    // set up the paddle vars
    paddleWidth = 10;
    paddleHeight = 30;
    paddleDepth = 10;
    paddleQuality = 1;

    // create the paddle1's material
    var paddle1Material = new THREE.MeshLambertMaterial({color: 0x1B32C0});
    // create the paddle2's material
    var paddle2Material = new THREE.MeshLambertMaterial({color: 0xFF4045});

    // set up paddle 1
    paddle1 = new THREE.Mesh(
        new THREE.CubeGeometry(paddleWidth, paddleHeight, paddleDepth, paddleQuality, paddleQuality, paddleQuality),
        paddle1Material
    );

    // add the padle to the scene
    scene.add(paddle1);

    paddle1.receiveShadow = true;
    paddle1.castShadow = true;

    // set up paddle 2
    paddle2 = new THREE.Mesh(
        new THREE.CubeGeometry(paddleWidth, paddleHeight, paddleDepth, paddleQuality, paddleQuality, paddleQuality),
        paddle2Material
    );

    // add the second padle to the scene
    scene.add(paddle2);

    paddle2.receiveShadow = true;
    paddle2.castShadow = true;

    // set paddles on each side of the table
    paddle1.position.x = -fieldWidth/2 + paddleWidth;
    paddle2.position.x = fieldWidth/2 - paddleWidth;

    // lift paddles over playing surface
    paddle1.position.z = paddleDepth;
    paddle2.position.z = paddleDepth;
}

function draw() {
    // draw THREE.JS scene
    renderer.render(scene, camera);

    // loop the draw() function
    requestAnimationFrame(draw);

    // process game logic
    ballPhysics();
    paddlePhysics();
    cameraPhysics();
    playerPaddleMovement();
    opponentPaddleMovement();
}

function ballPhysics() {
    // if ball goes off the 'left' side (Player's side)
    if (ball.position.x <= -fieldWidth/2) {
        // CPU scores
        score2++;
        // update scoreboard
        document.getElementById("scores").innerHTML = score1 + "-" + score2;
        // reset ball
        resetBall(2);
        // check if match over
        matchScoreCheck();
    }
    // if ball goes off the 'right' side (CPU's side)
    if (ball.position.x >= fieldWidth/2) {
        // Player scores
        score1++;
        // update scoreboard
        document.getElementById("scores").innerHTML = score1 + "-" + score2;
        // reset ball
        resetBall(1);
        // check if match over
        matchScoreCheck();
    }

    // if ball goes off the top side (side of table)
    if (ball.position.y <= -fieldHeight/2) {
        ballDirY = -ballDirY;
    }

    // if ball goes off the bottom side (side of table)
    if (ball.position.y >= fieldHeight/2) {
        ballDirY = -ballDirY;
    }

    // update ball position over time
    ball.position.x += ballDirX * ballSpeed;
    ball.position.y += ballDirY * ballSpeed;

    // limit ball's y-speed to 2x the x-speed
    // this is so the ball doesn't speed from left to right super fast
    // keeps game playable for humans
    if (ballDirY > ballSpeed * 2) {
        ballDirY = ballSpeed * 2;
    } else if (ballDirY < -ballSpeed * 2) {
        ballDirY = -ballSpeed * 2;
    }
}

function playerPaddleMovement() {
    // move left
    if (Key.isDown(Key.A)) {
        // if paddle is not touching the side of table
        // we move
        if (paddle1.position.y < fieldHeight * 0.45) {
            paddle1DirY = paddleSpeed * 0.5;
        }
        // else we do not move and stretch the paddle
        // to indicate we can't move
        else {
            paddle1DirY = 0;
            paddle1.scale.z += (10 - paddle1.scale.z) * 0.2;
        }
    }
    // move right
    else if (Key.isDown(Key.D)) {
        // if paddle is not touching the side of table
        // we move
        if (paddle1.position.y > -fieldHeight * 0.45) {
            paddle1DirY = -paddleSpeed * 0.5;
        }
        // else we do not move and stretch the paddle
        // to indicate we can't move
        else {
            paddle1DirY = 0;
            paddle1.scale.z += (10 - paddle1.scale.z) * 0.2;
        }
    }
    // else don't move paddle
    else {
        paddle1DirY = 0;
    }

    paddle1.scale.y += (1 - paddle1.scale.y) * 0.2;
    paddle1.scale.z += (1 - paddle1.scale.z) * 0.2;

    paddle1.position.y += paddle1DirY;
}

function opponentPaddleMovement() {
    // lerp towards the ball on the y plane
    paddle2DirY = (ball.position.y - paddle2.position.y) * difficulty;

    // in case the Lerp function produces a value above max paddle speed, we clamp it
    if (Math.abs(paddle2DirY) <= paddleSpeed) {
        paddle2.position.y += paddle2DirY;
    }
    // if the lerp value is too high, we have to limit speed to paddleSpeed
    else {
        // if paddle is lerping in +ve direction
        if (paddle2DirY > paddleSpeed) {
            paddle2.position.y += paddleSpeed;
        }
        // if paddle is lerping in -ve direction
        else if (paddle2DirY < -paddleSpeed) {
            paddle2.position.y -= paddleSpeed;
        }
    }

    // we lerp the scale back to 1
    // this is done because we stretch the paddle at some points
    // stretching is done when paddle touches side of table and when paddle hits ball
    // by doing this here, we ensure paddle always comes back to default size
    paddle2.scale.y += (1 - paddle2.scale.y) * 0.2;
}

// Handles paddle collision logic
function paddlePhysics() {
    // Player paddle logic

    // if ball is aligned with paddle1 on x plane
    // remember the position is the CENTER of the object
    // we only check between the front and the middle of the paddle (one-way collision)
    if (ball.position.x <= paddle1.position.x + paddleWidth &&
        ball.position.x >= paddle1.position.x) {
        // and if ball is aligned with paddle1 on y plane
        if (ball.position.y <= paddle1.position.y + paddleHeight/2 &&
            ball.position.y >= paddle1.position.y - paddleHeight/2) {
            // ball is intersecting with the front half of the paddle
            // and if ball is travelling towards player (-ve direction)
            if (ballDirX < 0) {
                // stretch the paddle to indicate a hit
                paddle1.scale.y = 5;

                // switch direction of ball travel to create bounce
                ballDirX = -ballDirX;

                // we impact ball angle when hitting it
                // this is not realistic physics, just spices up the gameplay
                // allows you to 'slice' the ball to beat the opponent
                ballDirY -= paddle1DirY * 0.7;
            }
        }
    }

    // Opponent paddle logic

    // if ball is aligned with paddle2 on x plane
    // remember the position is the CENTER of the object
    // we only check between the front and the middle of the paddle (one-way collision)
    if (ball.position.x <= paddle2.position.x + paddleWidth &&
        ball.position.x >= paddle2.position.x) {
        // and if ball is aligned with paddle2 on y plane
        if (ball.position.y <= paddle2.position.y + paddleHeight/2 &&
            ball.position.y >= paddle2.position.y - paddleHeight/2) {
            // ball is intersecting with the front half of the paddle
            // and if ball is travelling towards opponent (+ve direction)
            if (ballDirX > 0) {
                // stretch the paddle to indicate a hit
                paddle2.scale.y = 5;

                // switch direction of ball travel to create bounce
                ballDirX = -ballDirX;

                // we impact ball angle when hitting it
                // this is not realistic physics, just spices up the gameplay
                // allows you to 'slice' the ball to beat the opponent
                ballDirY -= paddle2DirY * 0.7;
            }
        }
    }
}

function cameraPhysics() {
    // we can easily notice shadows if we dynamically move lights during the game
    spotLight.position.x = ball.position.x;
    spotLight.position.y = ball.position.y;

    // move to behind the player's paddle
    camera.position.x = paddle1.position.x - 100;
    camera.position.y += (paddle1.position.y - camera.position.y) * 0.05;
    camera.position.z = paddle1.position.z + 100 + 0.04 * (-ball.position.x + paddle1.position.x);

    // rotate to face towards the opponent
    camera.rotation.x = -0.01 * (ball.position.y) * Math.PI/180;
    camera.rotation.y = -60 * Math.PI/180;
    camera.rotation.z = -90 * Math.PI/180;
}

// resets the ball's position to the centre of the play area
// also sets the ball direction speed towards the last point winner
function resetBall(loser) {
    // position the ball in the center of the table
    ball.position.x = 0;
    ball.position.y = 0;

    // if player lost the last point, we send the ball to the opponent
    if (loser == 1) {
        ballDirX = -1;
    }
    // else if opponent lost, we send the ball to player
    else {
        ballDirX = 1;
    }

    // set the ball to move +ve in y plane (towards left from the camera)
    ballDirY = 1;
}

// checks if either player or opponent has reached 7 points
function matchScoreCheck() {
    // if player has 7 points
    if (score1 >= maxScore) {
        // stop the ball
        ballSpeed = 0;
        // write to the banner
        document.getElementById("scores").innerHTML = "Player wins!";
        document.getElementById("winnerBoard").innerHTML = "Refresh to play again";
    }
    // else if opponent has 7 points
    else if (score2 >= maxScore) {
        // stop the ball
        ballSpeed = 0;
        // write to the banner
        document.getElementById("scores").innerHTML = "CPU wins!";
        document.getElementById("winnerBoard").innerHTML = "Refresh to play again";
    }
}<|endoftext|>


<commit_before>exports.CloudServerOpenStackEndpoint = function(token, url) {
	var https = require('https');
	var rackapi = require('../lib/api.js');

	this.token = token;
	this.url = require('url').parse(url);

	this.reqOpts = {
		hostname: this.url.hostname,
		port: 443,
		headers: {
			"Content-Type": "application/json",
			"X-Auth-Token": this.token.id
		}
	};

	this.images = function(params, callback) {
		params = (typeof params === "object" ? rackapi.serializeQueryString(params) : "");

		var o = this.reqOpts;
		o.path = this.url.pathname + "/images" + params;
		o.method = "GET";

		rackapi.callApi(o, null, function(res) {
			callback(res.images);
		});
	};

	this.imageDetails = function(id, callback) {
		var o = this.reqOpts;
		o.path = this.url.pathname + "/images/" + id;
		o.method = "GET";

		rackapi.callApi(o, null, function(res) {
			callback(res.image);
		});
	};

	this.deleteImage = function(id, callback) {
		var o = this.reqOpts;
		o.path = this.url.pathname + "/images/" + id;
		o.method = "DELETE";

		rackapi.callApi(o, null, function(res) {
			callback(res.image);
		});
	};

	this.flavors = function(params, callback) {
		params = (typeof params === "object" ? rackapi.serializeQueryString(params) : "");

		var o = this.reqOpts;
		o.path = this.url.pathname + "/flavors" + params;
		o.method = "GET";

		rackapi.callApi(o, null, function(res) {
			callback(res.flavors);
		});
	};

	this.flavorDetails = function(id, callback) {
		var o = this.reqOpts;
		o.path = this.url.pathname + "/flavors/" + id;
		o.method = "GET";

		rackapi.callApi(o, null, function(res) {
			callback(res.flavor);
		});
	};

	this.servers = function(params, callback) {
		params = (typeof params === "object" ? rackapi.serializeQueryString(params) : "");

		var o = this.reqOpts;
		o.path = this.url.pathname + "/servers/detail" + params;
		o.method = "GET";

		rackapi.callApi(o, null, function(res) {
			callback(res.servers);
		});
	};

	this.serverDetails = function(id, callback) {
		var o = this.reqOpts;
		o.path = this.url.pathname + "/servers/" + id;
		o.method = "GET";

		rackapi.callApi(o, null, function(res) {
			callback(res.server);
		});
	};

	this.serverIPs = function(id, network, callback) {
		network = (typeof network === "undefined" ? "" : "/" + network);

		var o = this.reqOpts;
		o.path = this.url.pathname + "/servers/" + id + "/ips" + network;
		o.method = "GET";

		rackapi.callApi(o, null, function(res) {
			callback(res.addresses);
		});
	};

	this.createServer = function(data, callback) {
		var o = this.reqOpts;
		o.path = this.url.pathname + "/servers"
		o.method = "POST";

		rackapi.callApi(o, data, function(res) {
			callback(res.server);
		});
	};

	this.updateServer = function(id, params, data, callback) {
		params = (typeof params === "object" ? rackapi.serializeQueryString(params) : "");

		var o = this.reqOpts;
		o.path = this.url.pathname + "/servers/" + id + params;
		o.method = "PUT";

		rackapi.callApi(o, data, function(res) {
			callback(res.server);
		});
	};

	this.deleteServer = function(id, callback) {
		var o = this.reqOpts;
		o.path = this.url.pathname + "/servers/" + id;
		o.method = "DELETE";

		rackapi.callApi(o, null, function(res) {
			callback(res);
		});
	};

	this.serverAction = function(id, data, callback) {
		var o = this.reqOpts;
		o.path = this.url.pathname + "/servers/" + id + "/action";
		o.method = "POST";

		rackapi.callApi(o, data, function(res) {
			callback(res);
		});
	};

	this.volumes = function(id, callback) {
		var o = this.reqOpts;
		o.path = this.url.pathname + "/servers/" + id + "/os-volume_attachments"
		o.method = "GET";

		rackapi.callApi(o, null, function(res) {
			callback(res.volumeAttachments);
		});
	};

	this.volumeDetails = function(id, attachmentId, callback) {
		var o = this.reqOpts;
		o.path = this.url.pathname + "/servers/" + id + "/os-volume_attachments/" + attachmentId;
		o.method = "GET";

		rackapi.callApi(o, null, function(res) {
			callback(res.volumeAttachment);
		});
	};

	this.attachVolume = function(id, params, data, callback) {
		params = (typeof params === "object" ? rackapi.serializeQueryString(params) : "");

		var o = this.reqOpts;
		o.path = this.url.pathname + "/servers/" + id + "/os-volume_attachments" + params;
		o.method = "POST";

		rackapi.callApi(o, data, function(res) {
			callback(res.volumeAttachment);
		});
	};

	this.detachVolume = function(id, attachmentId, callback) {
		var o = this.reqOpts;
		o.path = this.url.pathname + "/servers/" + id + "/os-volume_attachments/" + attachmentId;
		o.method = "DELETE";

		rackapi.callApi(o, null, function(res) {
			callback(res);
		});
	};

	this.metadata = function(id, data, callback) {
		var o = this.reqOpts;
		o.path = this.url.pathname + "/servers/" + id + "/metadata";
		o.method = "GET";

		rackapi.callApi(o, data, function(res) {
			callback(res.metadata);
		});
	};

	this.setMetadata = function(id, data, callback) {
		var o = this.reqOpts;
		o.path = this.url.pathname + "/servers/" + id + "/metadata";
		o.method = "PUT";

		rackapi.callApi(o, data, function(res) {
			callback(res.metadata);
		});
	};

	this.updateMetadata = function(id, data, callback) {
		var o = this.reqOpts;
		o.path = this.url.pathname + "/servers/" + id + "/metadata";
		o.method = "POST";

		rackapi.callApi(o, data, function(res) {
			callback(res.metadata);
		});
	};

	this.getMetadataItem = function(id, key, callback) {
		var o = this.reqOpts;
		o.path = this.url.pathname + "/servers/" + id + "/metadata/" + key;
		o.method = "GET";

		rackapi.callApi(o, null, function(res) {
			callback(res.metadata);
		});
	};

	this.setMetadataItem = function(id, key, callback) {
		var o = this.reqOpts;
		o.path = this.url.pathname + "/servers/" + id + "/metadata/" + key;
		o.method = "PUT";

		rackapi.callApi(o, null, function(res) {
			callback(res.metadata);
		});
	};

	this.deleteMetadataItem = function(id, key, callback) {
		var o = this.reqOpts;
		o.path = this.url.pathname + "/servers/" + id + "/metadata/" + key;
		o.method = "DELETE";

		rackapi.callApi(o, null, function(res) {
			callback(res.metadata);
		});
	};
}
<commit_msg>Fixed?<commit_after>exports.CloudServerOpenStackEndpoint = function(token, url) {
	var https = require('https');
	var rackapi = require('../lib/api.js');

	this.token = token;
	this.url = require('url').parse(url);

	this.reqOpts = {
		hostname: this.url.hostname,
		port: 443,
		headers: {
			"Content-Type": "application/json",
			"X-Auth-Token": this.token.id
		}
	};

	this.images = function(params, callback) {
		params = (typeof params === "object" ? rackapi.serializeQueryString(params) : "");

		var o = this.reqOpts;
		o.path = this.url.pathname + "/images" + params;
		o.method = "GET";

		rackapi.callApi(o, null, function(res) {
			callback(res);
		});
	};

	this.imageDetails = function(id, callback) {
		var o = this.reqOpts;
		o.path = this.url.pathname + "/images/" + id;
		o.method = "GET";

		rackapi.callApi(o, null, function(res) {
			callback(res);
		});
	};

	this.deleteImage = function(id, callback) {
		var o = this.reqOpts;
		o.path = this.url.pathname + "/images/" + id;
		o.method = "DELETE";

		rackapi.callApi(o, null, function(res) {
			callback(res);
		});
	};

	this.flavors = function(params, callback) {
		params = (typeof params === "object" ? rackapi.serializeQueryString(params) : "");

		var o = this.reqOpts;
		o.path = this.url.pathname + "/flavors" + params;
		o.method = "GET";

		rackapi.callApi(o, null, function(res) {
			callback(res);
		});
	};

	this.flavorDetails = function(id, callback) {
		var o = this.reqOpts;
		o.path = this.url.pathname + "/flavors/" + id;
		o.method = "GET";

		rackapi.callApi(o, null, function(res) {
			callback(res);
		});
	};

	this.servers = function(params, callback) {
		params = (typeof params === "object" ? rackapi.serializeQueryString(params) : "");

		var o = this.reqOpts;
		o.path = this.url.pathname + "/servers/detail" + params;
		o.method = "GET";

		rackapi.callApi(o, null, function(res) {
			callback(res);
		});
	};

	this.serverDetails = function(id, callback) {
		var o = this.reqOpts;
		o.path = this.url.pathname + "/servers/" + id;
		o.method = "GET";

		rackapi.callApi(o, null, function(res) {
			callback(res);
		});
	};

	this.serverIPs = function(id, network, callback) {
		network = (typeof network === "undefined" ? "" : "/" + network);

		var o = this.reqOpts;
		o.path = this.url.pathname + "/servers/" + id + "/ips" + network;
		o.method = "GET";

		rackapi.callApi(o, null, function(res) {
			callback(res);
		});
	};

	this.createServer = function(data, callback) {
		var o = this.reqOpts;
		o.path = this.url.pathname + "/servers"
		o.method = "POST";

		rackapi.callApi(o, data, function(res) {
			callback(res);
		});
	};

	this.updateServer = function(id, params, data, callback) {
		params = (typeof params === "object" ? rackapi.serializeQueryString(params) : "");

		var o = this.reqOpts;
		o.path = this.url.pathname + "/servers/" + id + params;
		o.method = "PUT";

		rackapi.callApi(o, data, function(res) {
			callback(res);
		});
	};

	this.deleteServer = function(id, callback) {
		var o = this.reqOpts;
		o.path = this.url.pathname + "/servers/" + id;
		o.method = "DELETE";

		rackapi.callApi(o, null, function(res) {
			callback(res);
		});
	};

	this.serverAction = function(id, data, callback) {
		var o = this.reqOpts;
		o.path = this.url.pathname + "/servers/" + id + "/action";
		o.method = "POST";

		rackapi.callApi(o, data, function(res) {
			callback(res);
		});
	};

	this.volumes = function(id, callback) {
		var o = this.reqOpts;
		o.path = this.url.pathname + "/servers/" + id + "/os-volume_attachments"
		o.method = "GET";

		rackapi.callApi(o, null, function(res) {
			callback(res);
		});
	};

	this.volumeDetails = function(id, attachmentId, callback) {
		var o = this.reqOpts;
		o.path = this.url.pathname + "/servers/" + id + "/os-volume_attachments/" + attachmentId;
		o.method = "GET";

		rackapi.callApi(o, null, function(res) {
			callback(res);
		});
	};

	this.attachVolume = function(id, params, data, callback) {
		params = (typeof params === "object" ? rackapi.serializeQueryString(params) : "");

		var o = this.reqOpts;
		o.path = this.url.pathname + "/servers/" + id + "/os-volume_attachments" + params;
		o.method = "POST";

		rackapi.callApi(o, data, function(res) {
			callback(res);
		});
	};

	this.detachVolume = function(id, attachmentId, callback) {
		var o = this.reqOpts;
		o.path = this.url.pathname + "/servers/" + id + "/os-volume_attachments/" + attachmentId;
		o.method = "DELETE";

		rackapi.callApi(o, null, function(res) {
			callback(res);
		});
	};

	this.metadata = function(id, data, callback) {
		var o = this.reqOpts;
		o.path = this.url.pathname + "/servers/" + id + "/metadata";
		o.method = "GET";

		rackapi.callApi(o, data, function(res) {
			callback(res);
		});
	};

	this.setMetadata = function(id, data, callback) {
		var o = this.reqOpts;
		o.path = this.url.pathname + "/servers/" + id + "/metadata";
		o.method = "PUT";

		rackapi.callApi(o, data, function(res) {
			callback(res);
		});
	};

	this.updateMetadata = function(id, data, callback) {
		var o = this.reqOpts;
		o.path = this.url.pathname + "/servers/" + id + "/metadata";
		o.method = "POST";

		rackapi.callApi(o, data, function(res) {
			callback(res);
		});
	};

	this.getMetadataItem = function(id, key, callback) {
		var o = this.reqOpts;
		o.path = this.url.pathname + "/servers/" + id + "/metadata/" + key;
		o.method = "GET";

		rackapi.callApi(o, null, function(res) {
			callback(res);
		});
	};

	this.setMetadataItem = function(id, key, callback) {
		var o = this.reqOpts;
		o.path = this.url.pathname + "/servers/" + id + "/metadata/" + key;
		o.method = "PUT";

		rackapi.callApi(o, null, function(res) {
			callback(res);
		});
	};

	this.deleteMetadataItem = function(id, key, callback) {
		var o = this.reqOpts;
		o.path = this.url.pathname + "/servers/" + id + "/metadata/" + key;
		o.method = "DELETE";

		rackapi.callApi(o, null, function(res) {
			callback(res);
		});
	};
}
<|endoftext|>


<commit_before>import React, { Component } from "react";
import JSONTree from "react-json-tree";
import moment from "moment";

import NodeStore from "../../stores/NodeStore";
import Pagination from "../../components/Pagination";

class FrameRow extends Component {
  render() {
    const theme = {
      scheme: 'google',
      author: 'seth wright (http://sethawright.com)',
      base00: '#1d1f21',
      base01: '#282a2e',
      base02: '#373b41',
      base03: '#969896',
      base04: '#b4b7b4',
      base05: '#c5c8c6',
      base06: '#e0e0e0',
      base07: '#ffffff',
      base08: '#CC342B',
      base09: '#F96A38',
      base0A: '#FBA922',
      base0B: '#198844',
      base0C: '#3971ED',
      base0D: '#3971ED',
      base0E: '#A36AC7',
      base0F: '#3971ED',
    }

    const data = {
      phyPayload: this.props.frame.phyPayload,
    };

    let rxtx = {};
    let dir = "";

    if (typeof(this.props.frame.txInfo) !== "undefined" && this.props.frame.txInfo !== null) {
      rxtx["txInfo"] = this.props.frame.txInfo;
      dir = "down";
    } else {
      rxtx["rxInfoSet"] = this.props.frame.rxInfoSet;
      dir = "up";
    }

    const createdAt = moment(this.props.frame.createdAt).format("LLLL");

    return(

          <span className={`glyphicon glyphicon-arrow-${dir}`} aria-hidden="true">

        {createdAt}

          <JSONTree data={rxtx} theme={theme} hideRoot={true} />

          <JSONTree data={data} theme={theme} hideRoot={true} />

    );
  }
}

class NodeFrameLogs extends Component {
  constructor() {
    super();

    this.state = {
      frames: [],
      pageSize: 20,
      pageNumber: 1,
      pages: 1,
    };

    this.updatePage = this.updatePage.bind(this);
  }

  componentDidMount() {
    this.updatePage(this.props);
  }

  componentWillReceiveProps(nextProps) {
    this.updatePage(nextProps);
  }

  updatePage(props) {
    const page = (props.location.query.page === undefined) ? 1 : props.location.query.page;

    NodeStore.getFrameLogs(this.props.params.devEUI, this.state.pageSize, (page-1) * this.state.pageSize, (totalCount, frames) => {
      this.setState({
        frames: frames,
        pageNumber: page,
        pages: Math.ceil(totalCount / this.state.pageSize),
      });
      window.scrollTo(0, 0);
    });
  }

  render () {
    const FrameRows = this.state.frames.map((frame, i) => <FrameRow key={new Date().getTime() + i} frame={frame} />);

    if (FrameRows.length > 0) {
      return (

                  Created at
                  RX / TX parameters
                  Frame

                {FrameRows}

          <Pagination pages={this.state.pages} currentPage={this.state.pageNumber} pathname={`/organizations/${this.props.params.organizationID}/applications/${this.props.params.applicationID}/nodes/${this.props.params.devEUI}/frames`} />

      );
    } else {
      return (

            No frames sent / received yet or LoRa Server has frame logging disabled.

      );
    }
  }
}

export default NodeFrameLogs;
<commit_msg>Fix json tree top and bottom padding.<commit_after>import React, { Component } from "react";
import JSONTree from "react-json-tree";
import moment from "moment";

import NodeStore from "../../stores/NodeStore";
import Pagination from "../../components/Pagination";

class FrameRow extends Component {
  render() {
    const theme = {
      scheme: 'google',
      author: 'seth wright (http://sethawright.com)',
      base00: '#1d1f21',
      base01: '#282a2e',
      base02: '#373b41',
      base03: '#969896',
      base04: '#b4b7b4',
      base05: '#c5c8c6',
      base06: '#e0e0e0',
      base07: '#ffffff',
      base08: '#CC342B',
      base09: '#F96A38',
      base0A: '#FBA922',
      base0B: '#198844',
      base0C: '#3971ED',
      base0D: '#3971ED',
      base0E: '#A36AC7',
      base0F: '#3971ED',
    }

    const data = {
      phyPayload: this.props.frame.phyPayload,
    };

    let rxtx = {};
    let dir = "";

    if (typeof(this.props.frame.txInfo) !== "undefined" && this.props.frame.txInfo !== null) {
      rxtx["txInfo"] = this.props.frame.txInfo;
      dir = "down";
    } else {
      rxtx["rxInfoSet"] = this.props.frame.rxInfoSet;
      dir = "up";
    }

    const createdAt = moment(this.props.frame.createdAt).format("LLLL");
    const treeStyle = {
      paddingTop: '0',
      paddingBottom: '0',
    };

    return(

          <span className={`glyphicon glyphicon-arrow-${dir}`} aria-hidden="true">

        {createdAt}

          <JSONTree data={rxtx} theme={theme} hideRoot={true} />

          <JSONTree data={data} theme={theme} hideRoot={true} />

    );
  }
}

class NodeFrameLogs extends Component {
  constructor() {
    super();

    this.state = {
      frames: [],
      pageSize: 20,
      pageNumber: 1,
      pages: 1,
    };

    this.updatePage = this.updatePage.bind(this);
  }

  componentDidMount() {
    this.updatePage(this.props);
  }

  componentWillReceiveProps(nextProps) {
    this.updatePage(nextProps);
  }

  updatePage(props) {
    const page = (props.location.query.page === undefined) ? 1 : props.location.query.page;

    NodeStore.getFrameLogs(this.props.params.devEUI, this.state.pageSize, (page-1) * this.state.pageSize, (totalCount, frames) => {
      this.setState({
        frames: frames,
        pageNumber: page,
        pages: Math.ceil(totalCount / this.state.pageSize),
      });
      window.scrollTo(0, 0);
    });
  }

  render () {
    const FrameRows = this.state.frames.map((frame, i) => <FrameRow key={new Date().getTime() + i} frame={frame} />);

    if (FrameRows.length > 0) {
      return (

                  Created at
                  RX / TX parameters
                  Frame

                {FrameRows}

          <Pagination pages={this.state.pages} currentPage={this.state.pageNumber} pathname={`/organizations/${this.props.params.organizationID}/applications/${this.props.params.applicationID}/nodes/${this.props.params.devEUI}/frames`} />

      );
    } else {
      return (

            No frames sent / received yet or LoRa Server has frame logging disabled.

      );
    }
  }
}

export default NodeFrameLogs;
<|endoftext|>