<commit_before>// Constants
const maximumAllowedRotationSpeed = 5,
    canvasWidth = 960,
    canvasHeight = 540;

// Prep Kinetic.Stage and layers
var asteroidsKineticStage = new Kinetic.Stage({
    container: 'kinetic-canvas',
    width: canvasWidth,
    height: canvasHeight
});

var bgLayer = new Kinetic.Layer();
var playLayer = new Kinetic.Layer();
var asteroidsLayer = new Kinetic.Layer();
var shotsLayer = new Kinetic.Layer();

asteroidsKineticStage.add(bgLayer);
asteroidsKineticStage.add(playLayer);
asteroidsKineticStage.add(shotsLayer);
asteroidsKineticStage.add(asteroidsLayer);

// Asteroids
var asteroids = [];

// Prep Player Ship
var shipSize = {
    width: 10,
    height: 20
};

var playerShip = new Kinetic.Line({
    points: [0, shipSize.height, shipSize.width / 2, 0, shipSize.width, shipSize.height],
    x: (asteroidsKineticStage.getWidth() - shipSize.width) / 2,
    y: (asteroidsKineticStage.getHeight() - shipSize.height) / 2,
    stroke: 'white',
    offset: { x: 5, y: 10 },
    closed: true
});

playLayer.add(playerShip);

function getPlayerToken() {
    let playerToken = {
        token: playerShip,
        shots: [],
        movement: {
            forwardAcceleration: 1.1,
            forwardDeceleration: 0.006,
            forwardVelocity: 0,
            maxForwardVelocity: 3,
            yawAcceleration: 2.0,
            yawDeceleration: 0.01,
            yawVelocity: 0,
            maxYawVelocity: 5,
            angleOfRotation: 0,
            forwardDirections: []
        },
        lives: 5,
        score: 0
    };

    return playerToken;
}

function adjustPlayerTokenAngle(currentValue) {
    // angle must be between 0 - 360
    if (+currentValue > 360) {
        currentValue -= 360;
    } else if (+currentValue < 0) {
        currentValue += 360;
    }
    return currentValue;
}

var playerToken = getPlayerToken();

function keepObjectOnCanvas(object, sizeOfObject) {
    const sizeMod = 0.8;
    // Left - Right
    if (object.token.getX() + sizeOfObject.width * sizeMod < 0) {
        object.token.setX(asteroidsKineticStage.getWidth() + sizeOfObject.width * sizeMod);
    } else if (object.token.getX() - sizeOfObject.width * sizeMod > asteroidsKineticStage.getWidth()) {
        object.token.setX(-sizeOfObject.width * sizeMod);
    }
    // Up - Down
    if (object.token.getY() + sizeOfObject.height * sizeMod < 0) {
        object.token.setY(asteroidsKineticStage.getHeight() + sizeOfObject.height * sizeMod);
    } else if (object.token.getY() - sizeOfObject.height * sizeMod > asteroidsKineticStage.height()) {
        object.token.setY(-sizeOfObject.height * sizeMod);
    }
}

function nextPlayerFrame() {
    // Apply each forward motion to the ship
    let motionsArrayLength = playerToken.movement.forwardDirections.length;
    let totalSpeed = {
        x: 0,
        y: 0
    };

    for (let index = motionsArrayLength - 1; index >= 0; index -= 1) {
        let currentMotion = playerToken.movement.forwardDirections[index];

        playerToken.token.setX(playerToken.token.getX() + (currentMotion.deltaX * currentMotion.velocity));
        playerToken.token.setY(playerToken.token.getY() + (currentMotion.deltaY * currentMotion.velocity));

        totalSpeed.x += (currentMotion.deltaX * currentMotion.velocity);
        totalSpeed.y += (currentMotion.deltaY * currentMotion.velocity);

        // Decelerate
        playerToken.movement.forwardDirections[index].velocity -= playerToken.movement.forwardDeceleration;

        // Remove the motion if it no longer applies
        if (playerToken.movement.forwardDirections[index].velocity < 0) {
            playerToken.movement.forwardDirections[index].velocity = 0;
            // delete playerToken.movement.forwardDirections[index];
        }

        keepObjectOnCanvas(playerToken, shipSize);
    }

    playerToken.movement.forwardDirections.filter(x => x.velocity !== 0);

    // Ship Rotation
    playerToken.token.rotate(playerToken.movement.yawVelocity);
    playerToken.movement.angleOfRotation += playerToken.movement.yawVelocity;

    // Adjust current Angle
    playerToken.movement.angleOfRotation = adjustPlayerTokenAngle(playerToken.movement.angleOfRotation);
}

// Move shots -> Constant velocity
// Destroy shot when out of canvas
// Check for collision - > when asteroids are implemented
function nextShotsFrame() {
    let velocity = 8.5;
    let numberOfShots = playerToken.shots.length;

    for (let index in playerToken.shots) {
        // Move
        playerToken.shots[index].token.setX(playerToken.shots[index].token.getX() + playerToken.shots[index].direction.deltaX * velocity);
        playerToken.shots[index].token.setY(playerToken.shots[index].token.getY() + playerToken.shots[index].direction.deltaY * velocity);

        // Destroy if needed
        if (playerToken.shots[index].token.getX() < -10 || playerToken.shots[index].token.getX() > asteroidsKineticStage.getWidth() + 10) {
            // playLayer.remove(playerToken.shots[index].token);
            delete playerToken.shots[index];
        } else if (playerToken.shots[index].token.getY() < -10 || playerToken.shots[index].token.getY() > asteroidsKineticStage.getHeight() + 10) {
            delete playerToken.shots[index];
        }
    }
}

function nextAsteroidsFrame() {
    for (let asteroid in asteroids) {
        asteroids[asteroid].token.setX(asteroids[asteroid].token.getX() + asteroids[asteroid].direction.deltaX * asteroids[asteroid].direction.velocity);
        asteroids[asteroid].token.setY(asteroids[asteroid].token.getY() + asteroids[asteroid].direction.deltaY * asteroids[asteroid].direction.velocity);
        keepObjectOnCanvas(asteroids[asteroid], asteroids[asteroid].size);
    }
}

// Controls functions
function getGameOver() {
    gameOver = true;
}

// Rotation the Ship
function getRotateLeft() {
    playerToken.movement.yawVelocity = playerToken.movement.yawVelocity < -playerToken.movement.maxYawVelocity
        ? -playerToken.movement.maxYawVelocity
        : playerToken.movement.yawVelocity -= playerToken.movement.yawAcceleration;
}

function getRotateRight() {
    playerToken.movement.yawVelocity = playerToken.movement.yawVelocity > playerToken.movement.maxYawVelocity
        ? playerToken.movement.maxYawVelocity
        : playerToken.movement.yawVelocity += playerToken.movement.yawAcceleration;
}

function decelerateYawVelocity() {
    let modifier = playerToken.movement.yawDeceleration;

    if (playerToken.movement.yawVelocity < 0) {
        modifier = -modifier;
    }

    if (playerToken.movement.yawVelocity !== 0) {

        if (Math.abs(playerToken.movement.yawVelocity - modifier) < Math.abs(modifier)) {
            playerToken.movement.yawVelocity = 0;
        } else {
            playerToken.movement.yawVelocity -= modifier;
        }
    }
}

function getCurrentDirection() {
    let directionOfMovement = {
        deltaX: ((((shipSize.height / 2) * Math.sin(playerToken.movement.angleOfRotation * Math.PI / 180))) / 10),
        deltaY: -((((shipSize.height / 2) * Math.cos(playerToken.movement.angleOfRotation * Math.PI / 180))) / 10),
        velocity: playerToken.movement.forwardAcceleration
    };

    return directionOfMovement;
}
// X = xcircle + (r * sin(angle))
// Y = ycircle + (r * cos(angle))
// Moving the Ship
function createNewForwardMotion() {
    // Get direction
    let currentDirection = getCurrentDirection();

    // console.log(currentDirection); // DELETE THIS LINE
    // console.log(playerToken.movement.angleOfRotation);

    // If old direction -> accelerate
    // if (playerToken.movement.forwardDirections.length > 0) {
    //     // Adjust existing motion
    //     let length = playerToken.movement.forwardDirections.length;

    //     if (playerToken.movement.forwardDirections[length - 1].deltaX === currentDirection.deltaX &&
    //         playerToken.movement.forwardDirections[length - 1].deltaY === currentDirection.deltaY) {

    //         playerToken.movement.forwardDirections[length - 1].velocity += playerToken.movement.forwardAcceleration;

    //         // Stay within maxForwardVelocity limit
    //         if (playerToken.movement.forwardDirections[length - 1].velocity > playerToken.movement.maxForwardVelocity) {
    //             playerToken.movement.forwardDirections[length - 1].velocity = playerToken.movement.maxForwardVelocity;
    //         }

    //         // Exit function
    //         return;
    //     }
    // }

    // If new direction push to the stack of forward motion
    playerToken.movement.forwardDirections.push(currentDirection);
}

// Direction of movement
// All shots have the same speed
// build a Visual token ( line ? dot ? square ? )
function createNewShot() {
    // Starts at current location
    // Direction is current angle
    // Destroyed on out of player area
    let shotToken = new Kinetic.Rect({
        x: 20,
        y: 20,
        width: 4,
        height: 4,
        offset: { x: 2, y: 2 },
        fill: 'limegreen'
    }).rotate(playerToken.movement.angleOfRotation);

    let shotDirection = getCurrentDirection();

    let shot = {
        token: shotToken,
        direction: shotDirection
    };

    playerToken.shots.push(shot);

    shot.token.setX(playerToken.token.getX() + shot.direction.deltaX * 10);
    shot.token.setY(playerToken.token.getY() + shot.direction.deltaY * 10);

    // Testing
    shotsLayer.add(shotToken);
    shotsLayer.draw();
}

function createNewAsteroid() {
    // kinetic token
    // constant speed
    let angle = 45; // TODO: Generate random angle

    let directionOfMovement = {
        deltaX: ((((shipSize.height / 2) * Math.sin(angle * Math.PI / 180))) / 10),
        deltaY: -((((shipSize.height / 2) * Math.cos(angle * Math.PI / 180))) / 10),
        velocity: 1
    };

    let largeAsteroid = new Kinetic.Rect({
        x: 0,
        y: 0,
        width: 200,
        height: 200,
        offset: { x: 100, y: 100 },
        stroke: 'white'
    }).rotate(angle);

    let newAsteroid = {
        token: largeAsteroid,
        size: {
            width: 200,
            height: 200
        },
        direction: directionOfMovement
    };

    // Adjust position and direction of travel
    // origin ( off canvas ) - affected by stay on canvas ?
    // direction of travel
    newAsteroid.token.setX(150);
    newAsteroid.token.setY(150);

    asteroidsLayer.add(newAsteroid.token);
    asteroids.push(newAsteroid);
}

// Get Next Frame
var gameOver = false;
function nextFrame() {

    // Collisions
    // Shots with asteroids
    // Ship with asteroids

    // Player Ship
    nextPlayerFrame();

    // Asteroids
    nextAsteroidsFrame();

    // Update shots
    nextShotsFrame();

    // Decelerate Ship
    decelerateYawVelocity();

    // ReDraw
    playLayer.draw();
    shotsLayer.draw();
    asteroidsLayer.draw();

    // End condition
    if (gameOver) {
        return;
    }

    requestAnimationFrame(nextFrame);
}

function startGame() {
    // prep ui etc.

    // create player ship

    // create asteroids
    createNewAsteroid();

    nextFrame();
}

// Read input when available
document.getElementById('controls')
    .addEventListener('keydown', function (input) {
        // GameOver = true;
        if (input.key === 'Escape') {
            getGameOver();
        } else if (input.key === 'ArrowLeft') {
            getRotateLeft();
        } else if (input.key === 'ArrowRight') {
            getRotateRight();
        } else if (input.key === ' ') {
            console.log('space');
            createNewShot();
        } else if (input.key === 'ArrowUp') {
            createNewForwardMotion();
        } else if (input.key === 's') {
            startGame();
        }
    });<commit_msg>Update:  Split asteroids<commit_after>// Constants
const maximumAllowedRotationSpeed = 5,
    canvasWidth = 960,
    canvasHeight = 540;

// Prep Kinetic.Stage and layers
var asteroidsKineticStage = new Kinetic.Stage({
    container: 'kinetic-canvas',
    width: canvasWidth,
    height: canvasHeight
});

var bgLayer = new Kinetic.Layer();
var playLayer = new Kinetic.Layer();
var asteroidsLayer = new Kinetic.Layer();
var shotsLayer = new Kinetic.Layer();

asteroidsKineticStage.add(bgLayer);
asteroidsKineticStage.add(playLayer);
asteroidsKineticStage.add(shotsLayer);
asteroidsKineticStage.add(asteroidsLayer);

// Asteroids
var asteroids = [];

// Prep Player Ship
var shipSize = {
    width: 10,
    height: 20
};

var playerShip = new Kinetic.Line({
    points: [0, shipSize.height, shipSize.width / 2, 0, shipSize.width, shipSize.height],
    x: (asteroidsKineticStage.getWidth() - shipSize.width) / 2,
    y: (asteroidsKineticStage.getHeight() - shipSize.height) / 2,
    stroke: 'white',
    offset: { x: 5, y: 10 },
    closed: true
});

playLayer.add(playerShip);

function getPlayerToken() {
    let playerToken = {
        token: playerShip,
        shots: [],
        movement: {
            forwardAcceleration: 1.1,
            forwardDeceleration: 0.006,
            forwardVelocity: 0,
            maxForwardVelocity: 2.7,
            yawAcceleration: 1.6,
            yawDeceleration: 0.01,
            yawVelocity: 0,
            maxYawVelocity: 5,
            angleOfRotation: 0,
            forwardDirections: []
        },
        lives: 5,
        score: 0
    };

    return playerToken;
}

function adjustPlayerTokenAngle(currentValue) {
    // angle must be between 0 - 360
    if (+currentValue > 360) {
        currentValue -= 360;
    } else if (+currentValue < 0) {
        currentValue += 360;
    }
    return currentValue;
}

var playerToken = getPlayerToken();

function keepObjectOnCanvas(object, sizeOfObject) {
    const sizeMod = 0.8;
    // Left - Right
    if (object.token.getX() + sizeOfObject.width * sizeMod < 0) {
        object.token.setX(asteroidsKineticStage.getWidth() + sizeOfObject.width * sizeMod);
    } else if (object.token.getX() - sizeOfObject.width * sizeMod > asteroidsKineticStage.getWidth()) {
        object.token.setX(-sizeOfObject.width * sizeMod);
    }
    // Up - Down
    if (object.token.getY() + sizeOfObject.height * sizeMod < 0) {
        object.token.setY(asteroidsKineticStage.getHeight() + sizeOfObject.height * sizeMod);
    } else if (object.token.getY() - sizeOfObject.height * sizeMod > asteroidsKineticStage.height()) {
        object.token.setY(-sizeOfObject.height * sizeMod);
    }
}

function nextPlayerFrame() {
    // Apply each forward motion to the ship
    let motionsArrayLength = playerToken.movement.forwardDirections.length;
    let totalSpeed = {
        x: 0,
        y: 0
    };

    for (let index = motionsArrayLength - 1; index >= 0; index -= 1) {
        let currentMotion = playerToken.movement.forwardDirections[index];

        playerToken.token.setX(playerToken.token.getX() + (currentMotion.deltaX * currentMotion.velocity));
        playerToken.token.setY(playerToken.token.getY() + (currentMotion.deltaY * currentMotion.velocity));

        totalSpeed.x += (currentMotion.deltaX * currentMotion.velocity);
        totalSpeed.y += (currentMotion.deltaY * currentMotion.velocity);

        // Decelerate
        playerToken.movement.forwardDirections[index].velocity -= playerToken.movement.forwardDeceleration;

        // Remove the motion if it no longer applies
        if (playerToken.movement.forwardDirections[index].velocity < 0) {
            playerToken.movement.forwardDirections[index].velocity = 0;
            // delete playerToken.movement.forwardDirections[index];
        }

        keepObjectOnCanvas(playerToken, shipSize);
    }

    playerToken.movement.forwardDirections.filter(x => x.velocity !== 0);

    // Ship Rotation
    playerToken.token.rotate(playerToken.movement.yawVelocity);
    playerToken.movement.angleOfRotation += playerToken.movement.yawVelocity;

    // Adjust current Angle
    playerToken.movement.angleOfRotation = adjustPlayerTokenAngle(playerToken.movement.angleOfRotation);
}

// Move shots -> Constant velocity
// Destroy shot when out of canvas
// Check for collision - > when asteroids are implemented
function nextShotsFrame() {
    let velocity = 8.5;
    let numberOfShots = playerToken.shots.length;

    for (let index in playerToken.shots) {
        // Move
        playerToken.shots[index].token.setX(playerToken.shots[index].token.getX() + playerToken.shots[index].direction.deltaX * velocity);
        playerToken.shots[index].token.setY(playerToken.shots[index].token.getY() + playerToken.shots[index].direction.deltaY * velocity);

        // Destroy if needed
        if (playerToken.shots[index].token.getX() < -10 || playerToken.shots[index].token.getX() > asteroidsKineticStage.getWidth() + 10) {
            // playLayer.remove(playerToken.shots[index].token);
            delete playerToken.shots[index];
        } else if (playerToken.shots[index].token.getY() < -10 || playerToken.shots[index].token.getY() > asteroidsKineticStage.getHeight() + 10) {
            delete playerToken.shots[index];
        }
    }
}

function nextAsteroidsFrame() {
    for (let asteroid in asteroids) {
        asteroids[asteroid].token.setX(asteroids[asteroid].token.getX() + asteroids[asteroid].direction.deltaX * asteroids[asteroid].direction.velocity);
        asteroids[asteroid].token.setY(asteroids[asteroid].token.getY() + asteroids[asteroid].direction.deltaY * asteroids[asteroid].direction.velocity);
        keepObjectOnCanvas(asteroids[asteroid], asteroids[asteroid].size);
    }
}

// Controls functions
function getGameOver() {
    gameOver = true;
}

// Rotation the Ship
function getRotateLeft() {
    playerToken.movement.yawVelocity = playerToken.movement.yawVelocity < -playerToken.movement.maxYawVelocity
        ? -playerToken.movement.maxYawVelocity
        : playerToken.movement.yawVelocity -= playerToken.movement.yawAcceleration;
}

function getRotateRight() {
    playerToken.movement.yawVelocity = playerToken.movement.yawVelocity > playerToken.movement.maxYawVelocity
        ? playerToken.movement.maxYawVelocity
        : playerToken.movement.yawVelocity += playerToken.movement.yawAcceleration;
}

function decelerateYawVelocity() {
    let modifier = playerToken.movement.yawDeceleration;

    if (playerToken.movement.yawVelocity < 0) {
        modifier = -modifier;
    }

    if (playerToken.movement.yawVelocity !== 0) {

        if (Math.abs(playerToken.movement.yawVelocity - modifier) < Math.abs(modifier)) {
            playerToken.movement.yawVelocity = 0;
        } else {
            playerToken.movement.yawVelocity -= modifier;
        }
    }
}

function getCurrentDirection() {
    let directionOfMovement = {
        deltaX: ((((shipSize.height / 2) * Math.sin(playerToken.movement.angleOfRotation * Math.PI / 180))) / 10),
        deltaY: -((((shipSize.height / 2) * Math.cos(playerToken.movement.angleOfRotation * Math.PI / 180))) / 10),
        velocity: playerToken.movement.forwardAcceleration
    };

    return directionOfMovement;
}
// X = xcircle + (r * sin(angle))
// Y = ycircle + (r * cos(angle))
// Moving the Ship
function createNewForwardMotion() {
    // Get direction
    let currentDirection = getCurrentDirection();

    // console.log(currentDirection); // DELETE THIS LINE
    // console.log(playerToken.movement.angleOfRotation);

    // If old direction -> accelerate
    // if (playerToken.movement.forwardDirections.length > 0) {
    //     // Adjust existing motion
    //     let length = playerToken.movement.forwardDirections.length;

    //     if (playerToken.movement.forwardDirections[length - 1].deltaX === currentDirection.deltaX &&
    //         playerToken.movement.forwardDirections[length - 1].deltaY === currentDirection.deltaY) {

    //         playerToken.movement.forwardDirections[length - 1].velocity += playerToken.movement.forwardAcceleration;

    //         // Stay within maxForwardVelocity limit
    //         if (playerToken.movement.forwardDirections[length - 1].velocity > playerToken.movement.maxForwardVelocity) {
    //             playerToken.movement.forwardDirections[length - 1].velocity = playerToken.movement.maxForwardVelocity;
    //         }

    //         // Exit function
    //         return;
    //     }
    // }

    // If new direction push to the stack of forward motion
    playerToken.movement.forwardDirections.push(currentDirection);
}

// Direction of movement
// All shots have the same speed
// build a Visual token ( line ? dot ? square ? )
function createNewShot() {
    // Starts at current location
    // Direction is current angle
    // Destroyed on out of player area
    let shotToken = new Kinetic.Rect({
        x: 20,
        y: 20,
        width: 4,
        height: 4,
        offset: { x: 2, y: 2 },
        fill: 'limegreen'
    }).rotate(playerToken.movement.angleOfRotation);

    let shotDirection = getCurrentDirection();

    let shot = {
        token: shotToken,
        direction: shotDirection
    };

    playerToken.shots.push(shot);

    shot.token.setX(playerToken.token.getX() + shot.direction.deltaX * 10);
    shot.token.setY(playerToken.token.getY() + shot.direction.deltaY * 10);

    // Testing
    shotsLayer.add(shotToken);
    shotsLayer.draw();
}

function getRandomInt(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
}

function createSmallerAsteroids(size, amount) {
    let output = [];

    for (let i = 0; i < amount; i += 1) {
        let newAsteroid = new Kinetic.Rect({
            x: 0,
            y: 0,
            width: size,
            height: size,
            offset: { x: size / 2, y: size / 2 },
            stroke: 'white',
        });

        output[i] = newAsteroid;
    }

    return output;
}

function getSmallerAsteroidsPositions(position, size) {
    let newPositions = [
        { x: position.x - size, y: position.y - size },
        { x: position.x - size, y: position.y },
        { x: position.x, y: position.y - size },
        { x: position.x, y: position.y }
    ];

    return newPositions;
}

function splitAsteroid(indexInAsteroids) {
    // Split into 4 equal parts
    let newSize = asteroids[indexInAsteroids].token.getWidth() / 2;

    if (asteroids[indexInAsteroids].token.getWidth() === 25) {
        // Destroy and return
        return;
    }

    let newAsteroids = createSmallerAsteroids(newSize, 4);
    let newAsteroidsPosition = getSmallerAsteroidsPositions(asteroids[indexInAsteroids].token.position());

    // Set new Asteroids Direction/ Velocity
}

function checkForShotsCollision(arrayOfShots) {
    for (let index in arrayOfShots) {

        if (asteroidsLayer.getIntersection(arrayOfShots[index].token.position())) {
            // Find the asteroid
            for (let asteroidIndex in asteroids) {
                // Find the asteroid to break up
                if (asteroids[asteroidIndex].token.intersects(arrayOfShots[index].token.position())) {
                    console.log(asteroids[asteroidIndex].token.getRotation());
                    splitAsteroid(asteroidIndex);
                }
            }

            console.log('hit');

            // Destroy the shot
            arrayOfShots[index].token.remove();
            delete arrayOfShots[index];
        }
    }
}

// TODO: Game over
function checkForPlayerCollision(player) {
    if (asteroidsLayer.getIntersection(player.token.position())) {
        gameOver = true;
    }
}

function createNewAsteroid() {
    // kinetic token
    // constant speed
    let angle = getRandomInt(0, 360); // TODO: Generate random angle

    let directionOfMovement = {
        deltaX: ((((shipSize.height / 2) * Math.sin(angle * Math.PI / 180))) / 10),
        deltaY: -((((shipSize.height / 2) * Math.cos(angle * Math.PI / 180))) / 10),
        velocity: 1
    };

    let largeAsteroid = new Kinetic.Rect({
        x: 0,
        y: 0,
        width: 200,
        height: 200,
        offset: { x: 100, y: 100 },
        stroke: 'white',
    }).rotate(angle);

    let newAsteroid = {
        token: largeAsteroid,
        size: {
            width: 200,
            height: 200
        },
        direction: directionOfMovement
    };

    // Adjust position and direction of travel
    // origin ( off canvas ) - affected by stay on canvas ?
    // direction of travel
    newAsteroid.token.setX(150);
    newAsteroid.token.setY(150);

    asteroidsLayer.add(newAsteroid.token);
    asteroids.push(newAsteroid);
}

// Get Next Frame
var gameOver = false;
function nextFrame() {

    // Collisions
    // Shots with asteroids
    checkForShotsCollision(playerToken.shots);

    // Ship with asteroids
    checkForPlayerCollision(playerToken);

    // Player Ship
    nextPlayerFrame();

    // Asteroids
    nextAsteroidsFrame();

    // Update shots
    nextShotsFrame();

    // Decelerate Ship
    decelerateYawVelocity();

    // ReDraw
    playLayer.draw();
    shotsLayer.draw();
    asteroidsLayer.draw();

    // End condition
    if (gameOver) {
        return;
    }

    requestAnimationFrame(nextFrame);
}

function startGame() {
    // prep ui etc.

    // create player ship

    // create asteroids
    createNewAsteroid();

    nextFrame();
}

// Read input when available
document.getElementById('controls')
    .addEventListener('keydown', function (input) {
        // GameOver = true;
        if (input.key === 'Escape') {
            getGameOver();
        } else if (input.key === 'ArrowLeft') {
            getRotateLeft();
        } else if (input.key === 'ArrowRight') {
            getRotateRight();
        } else if (input.key === ' ') {
            console.log('space');
            createNewShot();
        } else if (input.key === 'ArrowUp') {
            createNewForwardMotion();
        } else if (input.key === 's') {
            startGame();
        }
    });<|endoftext|>


<commit_before>'use strict';

var querystring = require('querystring');
var proxy = require('../proxy');

exports.proxy = proxy;

/**
 * Proxies a request to the image backend that will return a stream with a
 * download of the image in a large size.
 * id is the entire id (Catalog alias / asset id), ex DNT/123
 * size is a parameter that
 * next is a callback method for potential errors
 */
exports.proxyDownload = (id, params) => {
  var url = '/asset/download/' + id;
  if (params.options) {
    url += '?options=' + params.options;
  }
  return proxy(url);
};

/**
 * Proxies a request to the image backend that will return a stream with a
 * thumbnail.
 * id is the entire id (Catalog alias / asset id), ex DNT/123
 * next is a callback method for potential errors
 */
exports.proxyThumbnail = (id) => {
    return proxy('/preview/thumbnail/' + id);
};
<commit_msg>Changed the download proxies 2. argument from params to size<commit_after>'use strict';

var querystring = require('querystring');
var proxy = require('../proxy');

exports.proxy = proxy;

/**
 * Proxies a request to the image backend that will return a stream with a
 * download of the image in a large size.
 * id is the entire id (Catalog alias / asset id), ex DNT/123
 * size is a parameter that
 * next is a callback method for potential errors
 */
exports.proxyDownload = (id, size) => {
  var url = '/asset/download/' + id;
  if (typeof(size) === 'string') {
    url += '?options=' + size;
  } else if (typeof(size) === 'number') {
    url += '?size=' + size;
  }
  return proxy(url);
};

/**
 * Proxies a request to the image backend that will return a stream with a
 * thumbnail.
 * id is the entire id (Catalog alias / asset id), ex DNT/123
 * next is a callback method for potential errors
 */
exports.proxyThumbnail = (id) => {
    return proxy('/preview/thumbnail/' + id);
};
<|endoftext|>


<commit_before>'use strict';

angular.module('loginApp')
.controller('LoginCtrl', [ '$scope', '$state', '$modalInstance' , '$window', 'Auth',
function($scope, $state, $modalInstance, $window, Auth ) {
	$scope.credentials = {};
	$scope.loginForm = {};
	$scope.error = false;

	//when the form is submitted
	$scope.submit = function() {
		$scope.submitted = true;
		if (!$scope.loginForm.$invalid) {
			$scope.login($scope.credentials);
		} else {
			$scope.error = true;
			return;
		}
	};

	//Performs the login function, by sending a request to the server with the Auth service
	$scope.login = function(credentials) {
		$scope.error = false;
		Auth.login(credentials, function(user) {
			//success function
			$modalInstance.close();
		}, function(err) {
			console.log("error");
			$scope.error = true;
		});
	};

	// if a session exists for current user (page was refreshed)
	// log him in again
	if ($window.sessionStorage["userInfo"]) {
		var credentials = JSON.parse($window.sessionStorage["userInfo"]);
		$scope.login(credentials);
	}

} ]);
<commit_msg>redirect to home after successful login<commit_after>'use strict';

angular.module('loginApp')
.controller('LoginCtrl', [ '$scope', '$state', '$modalInstance' , '$window', 'Auth',
function($scope, $state, $modalInstance, $window, Auth ) {
	$scope.credentials = {};
	$scope.loginForm = {};
	$scope.error = false;

	//when the form is submitted
	$scope.submit = function() {
		$scope.submitted = true;
		if (!$scope.loginForm.$invalid) {
			$scope.login($scope.credentials);
		} else {
			$scope.error = true;
			return;
		}
	};

	//Performs the login function, by sending a request to the server with the Auth service
	$scope.login = function(credentials) {
		$scope.error = false;
		Auth.login(credentials, function(user) {
			//success function
			$modalInstance.close();
			$state.go('home');
		}, function(err) {
			console.log("error");
			$scope.error = true;
		});
	};

	// if a session exists for current user (page was refreshed)
	// log him in again
	if ($window.sessionStorage["userInfo"]) {
		var credentials = JSON.parse($window.sessionStorage["userInfo"]);
		$scope.login(credentials);
	}

} ]);
<|endoftext|>


<commit_before>'use strict';

const Boom = require('boom');
const Team = require('../models/Team');
const User = require('../../users/models/User');

module.exports = [{
  method: 'GET',
  path: '/teams',
  config: {
    auth: 'jwt'
  },
  handler: (req, res) => {
    Team.find().populate('manager.user frontend.user backend.user', 'id username is_searching project team').exec((err, team) => {
      res(team);
    });
  }
}, {
  method: 'POST',
  path: '/teams',
  config: {
    // Validate the payload against the Joi schema
    // pre: [{
    //   method:
    // }],

    validate: {
      // payload: surveySchema
    },
    auth: 'jwt'
  },
  handler: (req, res) => {
    let team = new Team();
    team.manager.push({user: req.payload.manager, role: req.payload.manager_role});
    team.frontend.push({user: req.payload.frontend, role: req.payload.frontend_role});
    team.frontend.push({user: req.payload.backend, role: req.payload.backend_role});
    team.save((err, team) => {
      if (err) {
        console.log(err);
        res(err);
      }else {
        res({msg: 'created', id: team.id});
      }
    });
  }
}, {
  method: 'PUT',
  path: '/teams',
  config: {
    // Validate the payload against the Joi schema
    // pre: [{
    //   method:
    // }],

    validate: {
      // payload: surveySchema
    },
    auth: 'jwt'
  },
  handler: (req, res) => {
    res();
  }
}, {
  method: 'DELETE',
  path: '/teams',
  config: {
    // Validate the payload against the Joi schema
    // pre: [{
    //   method:
    // }],

    validate: {
      // payload: surveySchema
    },
    auth: 'jwt'
  },
  handler: (req, res) => {
    res();
  }
}];
<commit_msg>Add multiple people in roles<commit_after>'use strict';

const Boom = require('boom');
const Team = require('../models/Team');
const User = require('../../users/models/User');
const teamSchema = require('../schemas/teamSchema');
const Functions = require('../util/teamFunctions');
const getRoles = Functions.getRoles;
const arrayChecker = Functions.arrayChecker;

module.exports = [{
  method: 'GET',
  path: '/teams',
  config: {
    auth: 'jwt'
  },
  handler: (req, res) => {
    Team.find().populate('manager.user frontend.user backend.user', 'id username is_searching project team').exec((err, team) => {
      res(team);
    });
  }
}, {
  method: 'POST',
  path: '/teams',
  config: {
    // Validate the payload against the Joi schema
    validate: {
      payload: teamSchema
    },
    // pre: [{
    //   method:
    // }],
    auth: 'jwt'
  },
  handler: (req, res) => {
    let team = new Team();
    let p = req.payload;
    console.log(p);
    if (!arrayChecker([p.manager , p.backend, p.frontend], [p.manager_role, p.backend_role, p.frontend_role])) {
      res(Boom.badRequest('arrays of "role" and "*_role" must be the same length'));
      return;
    }else {
      team.owner = req.Token.id;
      team.manager = getRoles(p.manager, p.manager_role);
      team.backend = getRoles(p.backend, p.backend_role);
      team.frontend = getRoles(p.frontend, p.frontend_role);
      team.save((err, team) => {
        if (err) {
          console.log(err);
          res(err);
        }else {
          res({msg: 'created', id: team.id});
        }
      });
    }
  }
}, {
  method: 'PUT',
  path: '/teams/{id}',
  config: {
    // Validate the payload against the Joi schema
    // pre: [{
    //   method:
    // }],

    validate: {
      payload: teamSchema
    },
    auth: 'jwt'
  },
  handler: (req, res) => {
    Team.findById(req.params.id, (err, team) => {
      if (req.Token.id !== team.owner) {
        res(Boom.unauthorized('only team owner can update team details'));
      }else {
        if (req.payload.owner) {
          team.owner = req.payload.owner;
        }
        if (req.payload.manager && req.payload.manager_role) {}
        team.manager.push({user: req.payload.manager, role: req.payload.manager_role});
        team.frontend.push({user: req.payload.frontend, role: req.payload.frontend_role});
        team.backend.push({user: req.payload.backend, role: req.payload.backend_role});
      }
    });
  }
}, {
  method: 'DELETE',
  path: '/teams/{id}',
  config: {
    // Validate the payload against the Joi schema
    auth: 'jwt'
  },
  handler: (req, res) => {
    Team.findByIdAndRemove(req.params.id, (err, team) => {
      res({msg: 'succes', team: team});
    });
  }
}];
<|endoftext|>


<commit_before>const { errorResponse } = require('../utils');
const numeral = require('numeral');
const fakeDailyQuote = require('./fixtures/dailyQuote.json');
const fakeIntradayQuote = require('./fixtures/intradayQuote.json');
const merge = require('lodash/merge');
const moment = require('moment-timezone');
const {NEW_YORK_TIME_ZONE, isMarketOpened, lastWeekDay, yesterday, isAfterMarketClose} = require('../utils/time');

const {dailyQuote: callDailyQuoteApi, intraDayQuote: callIntraDayQuoteApi} = require('./external-api/alpha-vantage');
process.moment = moment;

/**
 * GET /historical-quotes
 */
const Quote = require('../models/Quote.js');
const QuoteMeta = require('../models/QuoteMeta.js');

const handleError = (err) => {
    console.log(err);
};

const isString = value => (typeof value === 'string');
const num = value => numeral(value).value();
const isValidNum = value => num(value) >= 0;

/**
 * Validate the quote form
 *
 * @param {object} payload - the HTTP body message
 * @returns {object} The result of validation. Object contains a boolean validation result,
 *                   errors tips, and a global message for the whole form.
 */
const validateQuoteForm = (payload) => {
    const errors = {};
    let isFormValid = true;
    let message = '';

    if (!payload) {
        return {
            success: false,
            errors: {},
            message: 'Form is empty'
        };
    }

    if (!isString(payload.symbol) || payload.symbol.trim().length === 0) {
        isFormValid = false;
        errors.symbol = 'Please provide ticker symbol.';
    }

    if (!isString(payload.date) || payload.date.trim().length === 0) {
        isFormValid = false;
        // TODO more robust date validation.
        // Currently only expect DD-MM-YYYY
        errors.symbol = 'Please provide date.';
    }

    if (!isString(payload.currency) || (payload.currency !== 'USD' &&
        payload.currency !== 'CAD')) {
        isFormValid = false;
        errors.currency = 'Currency can only be CAD or USD';
    }

    if (!isValidNum(payload.price)) {
        isFormValid = false;
        errors.amount = 'Price is invalid';
    }

    if (!isValidNum(payload.change)) {
        isFormValid = false;
        errors.amount = 'Change is invalid';
    }

    if (!isValidNum(payload.changePercent)) {
        isFormValid = false;
        errors.amount = 'Change percent is invalid';
    }

    if (!isFormValid) {
        message = 'Check the form for errors.';
    }

    return {
        success: isFormValid,
        message,
        errors
    };
};

const returnError = (message, errors) => ({
    success: false,
    message,
    errors
});

const createOrEditQuotes = isEdit => (req, res) => {
    const keys = ['symbol', 'currency', 'price', 'date', 'change', 'changePercent'];
    const data = {};
    keys.forEach((key) => {
        data[key] = req.body[key];
    });

    const validationResult = validateQuoteForm(data);
    if (!validationResult.success) {
        return res.status(400).json(returnError(validationResult.message, validationResult.errors));
    }

    data._user = req.user._id;

    if (!isEdit) {
        new Quote(data).save((err, savedTsc) => {
            return res.json({ result: savedTsc });
        });
    } else {
        Quote.findById(req.body._id, (error, tsc) => {
            if (error) {
                return res.status(400).json(returnError(`Cannot find transaction ${req.body._id}`, error));
            }

            Object.assign(tsc, data);
            tsc.save((error, updatedTsc) => {
                if (error) {
                    return res.status(400).json(returnError(`Cannot update transaction ${req.body._id}`, error));
                }
                return res.json({ result: updatedTsc });
            });
        });
    }
};

exports.createQuotes = createOrEditQuotes(false);
exports.editQuotes = createOrEditQuotes(true);

exports.deleteQuotes = (req, res) => {
    Quote.remove({ _id: req.body.id, _user: req.user._id }, (err) => {
        if (err) return handleError(err);
        // removed!
        res.json({ result: { message: 'removed' } });
    });
};

const normalizeAPIResult = (response) => {
    const result = {};
    ['1. open', '2. high', '3. low', '4. close', '5. volume'].forEach((key) => {
        const normalKey = key.split('. ')[1];
        result[normalKey] = response[key];
    });
    return result;
};

const removePrefix = (symbol) => {
    if (symbol && symbol.startsWith('TSX:')) {
        return symbol.replace('TSX:', '');
    }
    return symbol;
};

const getQuoteFromDb = (_user, searchSymbol, asFallback) => {
    const result = {
        meta: {},
        data: {},
        from: {}
    };
    let params;
    let searchAll = !searchSymbol;
    let symbol;
    if (searchAll) {
        params = {_user};
    } else {
        symbol = removePrefix(searchSymbol);
        params = {symbol, _user};
    }

    const quotePromise = Quote.find(params).exec().then(quotes => {
        if (quotes && Array.isArray(quotes)) {
            quotes.forEach(quote => {
                symbol = quote && quote.symbol;
                result.data[symbol] = result.data[symbol] || {};
                result.data[symbol][quote.date] = quote;
            });
        }
    });
    let metaPromise;
    const saveMeta = meta => {
        if (meta) {
            symbol = meta.symbol;
            result.meta[symbol] = meta;
            result.from[symbol] = asFallback ? 'db as fallback' : 'db';
        }
    };
    if (searchAll) {
        metaPromise = QuoteMeta.find(params).exec().then(metas => {
            if (metas && Array.isArray(metas)) {
                metas.forEach(saveMeta);
            }
        });
    } else {
        metaPromise = QuoteMeta.findOne(params).exec().then(saveMeta);
    }
    return Promise.all([quotePromise, metaPromise]).then(
        () => {
            return result;
        },
        error => {
            return Promise.reject(error);
        }
    );
};

const saveQuotesFromAPI = (isIntraday, _user) => (response) => {
    const meta = response['Meta Data'];
    isIntraday = !!isIntraday;
    const dataKey = Object.keys(response).find(key => key.includes('Time Series'));
    const quotes = response[dataKey];
    const result = {
        meta: {},
        data: {},
        from: {}
    };
    if (meta && quotes) {
        let symbol = removePrefix(meta['2. Symbol']);
        let lastRefreshed = meta['3. Last Refreshed'];
        const promises = Object.keys(quotes).map((date) => {
            return Quote.findOne({ date, symbol, _user }).exec().then(foundQuote => {
                if (foundQuote) {
                    result[symbol] = result[symbol] || {};
                    result[symbol][date] = foundQuote;
                } else {
                    const data = Object.assign({
                        _user,
                        isIntraday,
                        symbol,
                        date
                    }, normalizeAPIResult(quotes[date]));
                    return new Quote(data).save().then(savedQuote => {
                        result[symbol] = result[symbol] || {};
                        result[symbol][date] = savedQuote;
                    });
                }
            }, error => {
                // TO-DO should I send db error back?
                console.log(error);
            });

        });
        const metaPromise = QuoteMeta.findOne({symbol, _user}).exec()
        .then(foundMeta => {
            let quoteMeta;
            let refereshTimeKey = isIntraday ? 'lastRefreshedIntraday' : 'lastRefreshedDaily';
            if (foundMeta) {
                quoteMeta = foundMeta;
                quoteMeta[refereshTimeKey] = isIntraday ? lastRefreshed : moment.tz(lastRefreshed, NEW_YORK_TIME_ZONE).format('YYYY-MM-DD');
            } else {
                quoteMeta = new QuoteMeta({
                    _user,
                    isIntraday,
                    symbol,
                    [refereshTimeKey]: lastRefreshed
                });
            }
            return quoteMeta.save().then(savedMeta => result.meta[symbol] = savedMeta);
        });
        promises.push(metaPromise);
        return Promise.all(promises).then(
            () => {
                result.from[symbol] = 'api';
                return result;
            },
            error => {
                return Promise.reject(error);
            }
        );
    }
    return Promise.reject({message: 'api response is not valid'});
};

/**
 * Should call api if:
 * Daily
 * 1. market is open, last refresh is earlier than last week day.
 * 2. market is closed, last refresh is earlier than today.
 * Intraday
 * 1. market is open and last refresh is more than 15 minutes ago.
 * @param {string} symbol No prefix.
 * @param {boolean} isIntraday intraday or daily
 * @param {object} _user user id.
 * @return {promise}
 */
const shouldCallApi = (symbol, isIntraday, _user) => {
    symbol = removePrefix(symbol);
    return QuoteMeta.findOne({symbol, _user}).exec().then(foundMeta => {
        if (!foundMeta) {
            return true;
        }
        const lastRefreshedIntraday = foundMeta.lastRefreshedIntraday;
        const lastRefreshedDaily = foundMeta.lastRefreshedDaily;
        const marketOpened = isMarketOpened();
        const afterMarketClose = isAfterMarketClose();
        if (isIntraday) {
            if (!lastRefreshedIntraday) {
                return true;
            }
            if (marketOpened) {
                if (moment.tz(lastRefreshedIntraday, NEW_YORK_TIME_ZONE).isBefore(moment().subtract(5, 'minutes'))) {
                    return true;
                }
            }
        } else {
            if (!lastRefreshedDaily) {
                return true;
            }
            // After market is closed, expect to have today's closing price.
            // Next day before market is open or weekend, expect previous weekday's closing price.
            const expectedDate = afterMarketClose ? lastWeekDay() : lastWeekDay(yesterday());
            if (moment.tz(lastRefreshedDaily, NEW_YORK_TIME_ZONE).isBefore(expectedDate, 'day')) {
                return true;
            }
        }
        return false;
    });
};

/**
 * Get quotes for single symbol
 * @param {string} symbol expected TSX as prefix
 * @param {boolean} isIntraday 'intraday' or 'daily'
 * @param {object} userId
 */
const downloadSingleQuote = (symbol, isIntraday, userId) => {
    const fakeData = isIntraday ? fakeIntradayQuote : fakeDailyQuote;
    const callApi = isIntraday ? callIntraDayQuoteApi : callDailyQuoteApi;
    // return Promise.resolve(fakeData)
    return shouldCallApi(symbol, isIntraday, userId).then(necessary => {
        if (necessary) {
            return callApi(symbol).then(saveQuotesFromAPI(isIntraday, userId)).catch(() => {
                return getQuoteFromDb(userId, symbol, true);
            });
        }
        return getQuoteFromDb(userId, symbol);
    });
};

/**
 * Get quotes for multiple symbols
 * @param {array} symbols
 * @param {boolean} isIntraday 'intraday' or 'daily'
 * @param {object} userId
 */
const downloadMultipleQuotes = (symbols, isIntraday, userId) => {
    return result => {
        if (!result) {
            result = {
                meta: {},
                data: {},
                // TODO can be deleted, added here to see if logic works well.
                dailyFrom: {},
                intradayFrom: {}
            };
        }
        return symbols.reduce((p, symbol) => {
            return p.then(downloadSingleQuote.bind(null, symbol, isIntraday, userId))
            .then(quote => {
                if (quote) {
                    symbol = removePrefix(symbol);
                    ['meta', 'data'].forEach(key => {
                        result[key] = merge({}, result[key], quote[key]);
                    });
                    const fromKey = isIntraday ? 'intradayFrom' : 'dailyFrom';
                    result[fromKey][symbol] = quote.from[symbol];
                }
                return result;
            });
        }, Promise.resolve());
    };
};

const downloadMultipleQuotesAllTypes = (symbols, userId) => {
    // TODO move this intraday loop into downloadSingleQuote.
    return [true, false].reduce((promise, isIntraday) => {
        return promise.then(downloadMultipleQuotes(symbols, isIntraday, userId));
    }, Promise.resolve({
        meta: {},
        data: {},
        // TODO can be deleted, added here to see if logic works well.
        dailyFrom: {},
        intradayFrom: {}
    }));
};

/**
 * TODO should be POST!!!!
 * Get /api/download-quotes
 */
exports.downloadQuotes = (req, res) => {
    const symbols = req.query.symbols && req.query.symbols.split(',');
    if (!(symbols && symbols.length)) {
        return res.json(errorResponse('parameter symbols must be comma separated'));
    }
    const userId = req.user._id;
    downloadMultipleQuotesAllTypes(symbols, userId)
    .then((result) => {
        res.json({success: true, result});
    }).catch(error => {
        res.json(errorResponse(error.message, null, {error}));
    });
};

exports.getQuotes = (req, res) => {
    const userId = req.user._id;
    getQuoteFromDb(userId)
    .then((result) => {
        res.json({success: true, result});
    }).catch(error => {
        res.json(errorResponse(error.message, null, {error}));
    });
};
<commit_msg>Fix bug download quotes from API doesn't update to latest quote<commit_after>const { errorResponse } = require('../utils');
const numeral = require('numeral');
const fakeDailyQuote = require('./fixtures/dailyQuote.json');
const fakeIntradayQuote = require('./fixtures/intradayQuote.json');
const merge = require('lodash/merge');
const moment = require('moment-timezone');
const {NEW_YORK_TIME_ZONE, isMarketOpened, lastWeekDay, yesterday, isAfterMarketClose} = require('../utils/time');

const {dailyQuote: callDailyQuoteApi, intraDayQuote: callIntraDayQuoteApi} = require('./external-api/alpha-vantage');
process.moment = moment;

/**
 * GET /historical-quotes
 */
const Quote = require('../models/Quote.js');
const QuoteMeta = require('../models/QuoteMeta.js');

const handleError = (err) => {
    console.log(err);
};

const isString = value => (typeof value === 'string');
const num = value => numeral(value).value();
const isValidNum = value => num(value) >= 0;

/**
 * Validate the quote form
 *
 * @param {object} payload - the HTTP body message
 * @returns {object} The result of validation. Object contains a boolean validation result,
 *                   errors tips, and a global message for the whole form.
 */
const validateQuoteForm = (payload) => {
    const errors = {};
    let isFormValid = true;
    let message = '';

    if (!payload) {
        return {
            success: false,
            errors: {},
            message: 'Form is empty'
        };
    }

    if (!isString(payload.symbol) || payload.symbol.trim().length === 0) {
        isFormValid = false;
        errors.symbol = 'Please provide ticker symbol.';
    }

    if (!isString(payload.date) || payload.date.trim().length === 0) {
        isFormValid = false;
        // TODO more robust date validation.
        // Currently only expect DD-MM-YYYY
        errors.symbol = 'Please provide date.';
    }

    if (!isString(payload.currency) || (payload.currency !== 'USD' &&
        payload.currency !== 'CAD')) {
        isFormValid = false;
        errors.currency = 'Currency can only be CAD or USD';
    }

    if (!isValidNum(payload.price)) {
        isFormValid = false;
        errors.amount = 'Price is invalid';
    }

    if (!isValidNum(payload.change)) {
        isFormValid = false;
        errors.amount = 'Change is invalid';
    }

    if (!isValidNum(payload.changePercent)) {
        isFormValid = false;
        errors.amount = 'Change percent is invalid';
    }

    if (!isFormValid) {
        message = 'Check the form for errors.';
    }

    return {
        success: isFormValid,
        message,
        errors
    };
};

const returnError = (message, errors) => ({
    success: false,
    message,
    errors
});

const createOrEditQuotes = isEdit => (req, res) => {
    const keys = ['symbol', 'currency', 'price', 'date', 'change', 'changePercent'];
    const data = {};
    keys.forEach((key) => {
        data[key] = req.body[key];
    });

    const validationResult = validateQuoteForm(data);
    if (!validationResult.success) {
        return res.status(400).json(returnError(validationResult.message, validationResult.errors));
    }

    data._user = req.user._id;

    if (!isEdit) {
        new Quote(data).save((err, savedTsc) => {
            return res.json({ result: savedTsc });
        });
    } else {
        Quote.findById(req.body._id, (error, tsc) => {
            if (error) {
                return res.status(400).json(returnError(`Cannot find transaction ${req.body._id}`, error));
            }

            Object.assign(tsc, data);
            tsc.save((error, updatedTsc) => {
                if (error) {
                    return res.status(400).json(returnError(`Cannot update transaction ${req.body._id}`, error));
                }
                return res.json({ result: updatedTsc });
            });
        });
    }
};

exports.createQuotes = createOrEditQuotes(false);
exports.editQuotes = createOrEditQuotes(true);

exports.deleteQuotes = (req, res) => {
    Quote.remove({ _id: req.body.id, _user: req.user._id }, (err) => {
        if (err) return handleError(err);
        // removed!
        res.json({ result: { message: 'removed' } });
    });
};

const normalizeAPIResult = (response) => {
    const result = {};
    ['1. open', '2. high', '3. low', '4. close', '5. volume'].forEach((key) => {
        const normalKey = key.split('. ')[1];
        result[normalKey] = response[key];
    });
    return result;
};

const removePrefix = (symbol) => {
    if (symbol && symbol.startsWith('TSX:')) {
        return symbol.replace('TSX:', '');
    }
    return symbol;
};

const getQuoteFromDb = (_user, searchSymbol, asFallback) => {
    const result = {
        meta: {},
        data: {},
        from: {}
    };
    let params;
    let searchAll = !searchSymbol;
    let symbol;
    if (searchAll) {
        params = {_user};
    } else {
        symbol = removePrefix(searchSymbol);
        params = {symbol, _user};
    }

    const quotePromise = Quote.find(params).exec().then(quotes => {
        if (quotes && Array.isArray(quotes)) {
            quotes.forEach(quote => {
                symbol = quote && quote.symbol;
                result.data[symbol] = result.data[symbol] || {};
                result.data[symbol][quote.date] = quote;
            });
        }
    });
    let metaPromise;
    const saveMeta = meta => {
        if (meta) {
            symbol = meta.symbol;
            result.meta[symbol] = meta;
            result.from[symbol] = asFallback ? 'db as fallback' : 'db';
        }
    };
    if (searchAll) {
        metaPromise = QuoteMeta.find(params).exec().then(metas => {
            if (metas && Array.isArray(metas)) {
                metas.forEach(saveMeta);
            }
        });
    } else {
        metaPromise = QuoteMeta.findOne(params).exec().then(saveMeta);
    }
    return Promise.all([quotePromise, metaPromise]).then(
        () => {
            return result;
        },
        error => {
            return Promise.reject(error);
        }
    );
};

const saveQuotesFromAPI = (isIntraday, _user) => (response) => {
    const meta = response['Meta Data'];
    isIntraday = !!isIntraday;
    const dataKey = Object.keys(response).find(key => key.includes('Time Series'));
    const quotes = response[dataKey];
    const result = {
        meta: {},
        data: {},
        from: {}
    };
    if (meta && quotes) {
        const symbol = removePrefix(meta['2. Symbol']);
        const lastRefreshed = meta['3. Last Refreshed'];
        const data = result.data;
        const promises = Object.keys(quotes).map((date) => {
            return Quote.findOne({ date, symbol, _user }).exec().then(foundQuote => {
                if (foundQuote) {
                    data[symbol] = data[symbol] || {};
                    data[symbol][date] = foundQuote;
                } else {
                    const newQuote = Object.assign({
                        _user,
                        isIntraday,
                        symbol,
                        date
                    }, normalizeAPIResult(quotes[date]));
                    return new Quote(newQuote).save().then(savedQuote => {
                        data[symbol] = data[symbol] || {};
                        data[symbol][date] = savedQuote;
                    });
                }
            }, error => {
                // TO-DO should I send db error back?
                console.log(error);
            });

        });
        const metaPromise = QuoteMeta.findOne({symbol, _user}).exec()
        .then(foundMeta => {
            let quoteMeta;
            let refereshTimeKey = isIntraday ? 'lastRefreshedIntraday' : 'lastRefreshedDaily';
            if (foundMeta) {
                quoteMeta = foundMeta;
                quoteMeta[refereshTimeKey] = isIntraday ? lastRefreshed : moment.tz(lastRefreshed, NEW_YORK_TIME_ZONE).format('YYYY-MM-DD');
            } else {
                quoteMeta = new QuoteMeta({
                    _user,
                    isIntraday,
                    symbol,
                    [refereshTimeKey]: lastRefreshed
                });
            }
            return quoteMeta.save().then(savedMeta => result.meta[symbol] = savedMeta);
        });
        promises.push(metaPromise);
        return Promise.all(promises).then(
            () => {
                result.from[symbol] = 'api';
                return result;
            },
            error => {
                return Promise.reject(error);
            }
        );
    }
    return Promise.reject({message: 'api response is not valid'});
};

/**
 * Should call api if:
 * Daily
 * 1. market is open, last refresh is earlier than last week day.
 * 2. market is closed, last refresh is earlier than today.
 * Intraday
 * 1. market is open and last refresh is more than 15 minutes ago.
 * @param {string} symbol No prefix.
 * @param {boolean} isIntraday intraday or daily
 * @param {object} _user user id.
 * @return {promise}
 */
const shouldCallApi = (symbol, isIntraday, _user) => {
    symbol = removePrefix(symbol);
    return QuoteMeta.findOne({symbol, _user}).exec().then(foundMeta => {
        if (!foundMeta) {
            return true;
        }
        const lastRefreshedIntraday = foundMeta.lastRefreshedIntraday;
        const lastRefreshedDaily = foundMeta.lastRefreshedDaily;
        const marketOpened = isMarketOpened();
        const afterMarketClose = isAfterMarketClose();
        if (isIntraday) {
            if (!lastRefreshedIntraday) {
                return true;
            }
            if (marketOpened) {
                if (moment.tz(lastRefreshedIntraday, NEW_YORK_TIME_ZONE).isBefore(moment().subtract(5, 'minutes'))) {
                    return true;
                }
            }
        } else {
            if (!lastRefreshedDaily) {
                return true;
            }
            // After market is closed, expect to have today's closing price.
            // Next day before market is open or weekend, expect previous weekday's closing price.
            const expectedDate = afterMarketClose ? lastWeekDay() : lastWeekDay(yesterday());
            if (moment.tz(lastRefreshedDaily, NEW_YORK_TIME_ZONE).isBefore(expectedDate, 'day')) {
                return true;
            }
        }
        return false;
    });
};

/**
 * Get quotes for single symbol
 * @param {string} symbol expected TSX as prefix
 * @param {boolean} isIntraday 'intraday' or 'daily'
 * @param {object} userId
 */
const downloadSingleQuote = (symbol, isIntraday, userId) => {
    const fakeData = isIntraday ? fakeIntradayQuote : fakeDailyQuote;
    const callApi = isIntraday ? callIntraDayQuoteApi : callDailyQuoteApi;
    // return Promise.resolve(fakeData)
    return shouldCallApi(symbol, isIntraday, userId).then(necessary => {
        if (necessary) {
            return callApi(symbol).then(saveQuotesFromAPI(isIntraday, userId)).catch(() => {
                return getQuoteFromDb(userId, symbol, true);
            });
        }
        return getQuoteFromDb(userId, symbol);
    });
};

/**
 * Get quotes for multiple symbols
 * @param {array} symbols
 * @param {boolean} isIntraday 'intraday' or 'daily'
 * @param {object} userId
 */
const downloadMultipleQuotes = (symbols, isIntraday, userId) => {
    return result => {
        if (!result) {
            result = {
                meta: {},
                data: {},
                // TODO can be deleted, added here to see if logic works well.
                dailyFrom: {},
                intradayFrom: {}
            };
        }
        return symbols.reduce((p, symbol) => {
            return p.then(downloadSingleQuote.bind(null, symbol, isIntraday, userId))
            .then(quote => {
                if (quote) {
                    symbol = removePrefix(symbol);
                    ['meta', 'data'].forEach(key => {
                        result[key] = merge({}, result[key], quote[key]);
                    });
                    const fromKey = isIntraday ? 'intradayFrom' : 'dailyFrom';
                    result[fromKey][symbol] = quote.from[symbol];
                }
                return result;
            });
        }, Promise.resolve());
    };
};

const downloadMultipleQuotesAllTypes = (symbols, userId) => {
    // TODO move this intraday loop into downloadSingleQuote.
    return [true, false].reduce((promise, isIntraday) => {
        return promise.then(downloadMultipleQuotes(symbols, isIntraday, userId));
    }, Promise.resolve({
        meta: {},
        data: {},
        // TODO can be deleted, added here to see if logic works well.
        dailyFrom: {},
        intradayFrom: {}
    }));
};

/**
 * TODO should be POST!!!!
 * Get /api/download-quotes
 */
exports.downloadQuotes = (req, res) => {
    const symbols = req.query.symbols && req.query.symbols.split(',');
    if (!(symbols && symbols.length)) {
        return res.json(errorResponse('parameter symbols must be comma separated'));
    }
    const userId = req.user._id;
    downloadMultipleQuotesAllTypes(symbols, userId)
    .then((result) => {
        res.json({success: true, result});
    }).catch(error => {
        res.json(errorResponse(error.message, null, {error}));
    });
};

exports.getQuotes = (req, res) => {
    const userId = req.user._id;
    getQuoteFromDb(userId)
    .then((result) => {
        res.json({success: true, result});
    }).catch(error => {
        res.json(errorResponse(error.message, null, {error}));
    });
};
<|endoftext|>


<commit_before>/* eslint-disable no-catch-shadow, no-unused-vars */
var R = require('ramda'),
  debug = require('debug')('freecc:cntr:story'),
  Story = require('./../models/Story'),
  Comment = require('./../models/Comment'),
  User = require('./../models/User'),
  moment = require('../public/js/lib/moment/moment.js'),
  resources = require('./resources'),
  mongodb = require('mongodb'),
  MongoClient = mongodb.MongoClient,
  secrets = require('../config/secrets'),
  sanitizeHtml = require('sanitize-html');

function hotRank(timeValue, rank) {
  /*
   * Hotness ranking algorithm: http://amix.dk/blog/post/19588
   * tMS = postedOnDate - foundationTime;
   * Ranking...
   * f(ts, 1, rank) = log(10)z + (ts)/45000;
   */
  var hotness;
  var z = Math.log(rank) / Math.log(10);
  hotness = z + (timeValue / 115200000);
  return hotness;

}

exports.hotJSON = function(req, res) {
  var story = Story.find({}).sort({'timePosted': -1}).limit(1000);
  story.exec(function(err, stories) {
    if (err) {
      return res.sendStatus(500);
    }

    var foundationDate = 1413298800000;

    var sliceVal = stories.length >= 100 ? 100 : stories.length;
    return res.json(stories.map(function(elem) {
      return elem;
    }).sort(function(a, b) {
      return hotRank(b.timePosted - foundationDate, b.rank, b.headline)
        - hotRank(a.timePosted - foundationDate, a.rank, a.headline);
    }).slice(0, sliceVal));

  });
};

exports.recentJSON = function(req, res, next) {
  var story = Story.find({}).sort({'timePosted': -1}).limit(100);
  story.exec(function(err, stories) {
    if (err) {
      res.sendStatus(500);
      return next(err);
    }
    return res.json(stories);
  });
};

exports.hot = function(req, res) {
 return res.render('stories/index', {
    title: 'Hot stories currently trending on Camper News',
    page: 'hot'
  });
};

exports.submitNew = function(req, res) {
  return res.render('stories/index', {
    title: 'Submit a new story to Camper News',
    page: 'submit'
  });
};

exports.search = function(req, res) {
  return res.render('stories/index', {
    title: 'Search the archives of Camper News',
    page: 'search'
  });
};

exports.recent = function(req, res) {
  return res.render('stories/index', {
    title: 'Recently submitted stories on Camper News',
    page: 'recent'
  });
};

exports.preSubmit = function(req, res) {

  var data = req.query;
  var cleanData = sanitizeHtml(data.url, {
    allowedTags: [],
    allowedAttributes: []
  }).replace(/";/g, '"');
  if (data.url.replace(/&/g, '&') !== cleanData) {

    req.flash('errors', {
      msg: 'The data for this post is malformed'
    });
    return res.render('stories/index', {
      page: 'stories/submit'
    });
  }

  var title = data.title || '';
  var image = data.image || '';
  var description = data.description || '';
  return res.render('stories/index', {
    title: 'Confirm your Camper News story submission',
    page: 'storySubmission',
    storyURL: data.url,
    storyTitle: title,
    storyImage: image,
    storyMetaDescription: description
  });
};

exports.returnIndividualStory = function(req, res, next) {
  var dashedName = req.params.storyName;

  var storyName = dashedName.replace(/\-/g, ' ');

  Story.find({'storyLink': new RegExp(storyName, 'i')}, function(err, story) {
    if (err) {
      return next(err);
    }

    if (story.length < 1) {
      req.flash('errors', {
        msg: "404: We couldn't find a story with that name. Please double check the name."
      });

      return res.redirect('/stories/');
    }

    story = story.pop();
    var dashedNameFull = story.storyLink.toLowerCase().replace(/\s/g, '-');
    if (dashedNameFull !== dashedName) {
      return res.redirect('../stories/' + dashedNameFull);
    }

    var userVoted = false;
    try {
      var votedObj = story.upVotes.filter(function(a) {
        return a['upVotedByUsername'] === req.user['profile']['username'];
      });
      if (votedObj.length > 0) {
        userVoted = true;
      }
    } catch(err) {
      userVoted = false;
    }
    res.render('stories/index', {
      title: story.headline,
      link: story.link,
      author: story.author,
      description: story.description,
      rank: story.upVotes.length,
      upVotes: story.upVotes,
      comments: story.comments,
      id: story._id,
      timeAgo: moment(story.timePosted).fromNow(),
      image: story.image,
      page: 'show',
      storyMetaDescription: story.metaDescription,
      hasUserVoted: userVoted
    });
  });
};

exports.getStories = function(req, res) {
  MongoClient.connect(secrets.db, function(err, database) {
    if (err) {
      return res.sendStatus(500);
    }
    database.collection('stories').find({
      '$text': {
        '$search': req.body.data.searchValue
      }
    }, {
      headline: 1,
      timePosted: 1,
      link: 1,
      description: 1,
      rank: 1,
      upVotes: 1,
      author: 1,
      comments: 1,
      image: 1,
      storyLink: 1,
      metaDescription: 1,
      textScore: {
        $meta: 'textScore'
      }
    }, {
      sort: {
        textScore: {
          $meta: 'textScore'
        }
      }
    }).toArray(function(err, items) {
      if (err) {
        return res.sendStatus(500);
      }
      if (items !== null && items.length !== 0) {
        return res.json(items);
      }
      return res.sendStatus(404);
    });
  });
};

exports.upvote = function(req, res, next) {
  var data = req.body.data;
  Story.find({'_id': data.id}, function(err, story) {
    if (err) {
      res.sendStatus(500);
      return next(err);
    }
    story = story.pop();
    story.rank++;
    story.upVotes.push(
      {
        upVotedBy: data.upVoter._id,
        upVotedByUsername: data.upVoter.profile.username
      }
    );
    story.markModified('rank');
    story.save();
    User.find({'_id': story.author.userId}, function(err, user) {
      'use strict';
      if (err) {
        return next(err);
      }
      user = user.pop();
      user.progressTimestamps.push(Date.now());
      user.save();
    });
    return res.send(story);
  });
};

exports.comments = function(req, res, next) {
  var data = req.params.id;
  Comment.find({'_id': data}, function(err, comment) {
    if (err) {
      res.sendStatus(500);
      return next(err);
    }
    comment = comment.pop();
    return res.send(comment);
  });
};

exports.newStory = function(req, res) {
  if (!req.user) {
    return res.sendStatus(500);
  }
  var url = req.body.data.url;
  var cleanURL = sanitizeHtml(url, {
    allowedTags: [],
    allowedAttributes: []
  }).replace(/"/g, '"');
  if (cleanURL !== url) {
    req.flash('errors', {
      msg: "The URL you submitted doesn't appear valid"
    });
    return res.json({
      alreadyPosted: true,
      storyURL: '/stories/submit'
    });

  }
  if (url.search(/^https?:\/\//g) === -1) {
    url = 'http://' + url;
  }
  Story.find({'link': url}, function(err, story) {
    if (err) {
      return res.sendStatus(500);
    }
    if (story.length) {
      req.flash('errors', {
        msg: "Someone's already posted that link. Here's the discussion."
      });
      return res.json({
        alreadyPosted: true,
        storyURL: '/stories/' + story.pop().storyLink
      });
    }
    resources.getURLTitle(url, processResponse);
  });

  function processResponse(err, story) {
    if (err) {
      res.json({
        alreadyPosted: false,
        storyURL: url,
        storyTitle: '',
        storyImage: '',
        storyMetaDescription: ''
      });
    } else {
      res.json({
        alreadyPosted: false,
        storyURL: url,
        storyTitle: story.title,
        storyImage: story.image,
        storyMetaDescription: story.description
      });
    }
  }
};

exports.storySubmission = function(req, res) {
  var data = req.body.data;
  if (req.user._id.toString() !== data.author.userId.toString()) {
    return res.sendStatus(500);
  }
  var storyLink = data.headline
    .replace(/\'/g, '')
    .replace(/\"/g, '')
    .replace(/,/g, '')
    .replace(/[^a-z0-9]/gi, ' ')
    .replace(/\s+/g, ' ')
    .toLowerCase();
  var link = data.link;
  if (link.search(/^https?:\/\//g) === -1) {
    link = 'http://' + link;
  }
  var story = new Story({
    headline: sanitizeHtml(data.headline, {
      allowedTags: [],
      allowedAttributes: []
    }).replace(/"/g, '"'),
    timePosted: Date.now(),
    link: link,
    description: sanitizeHtml(data.description, {
      allowedTags: [],
      allowedAttributes: []
    }).replace(/"/g, '"'),
    rank: 1,
    upVotes: data.upVotes,
    author: data.author,
    comments: [],
    image: data.image,
    storyLink: storyLink,
    metaDescription: data.storyMetaDescription
  });

  req.user.progressTimestamps.push(Date.now());
  req.user.save();

  story.save(function(err) {
    if (err) {
      return res.sendStatus(500);
    }
    res.send(JSON.stringify({
      storyLink: story.storyLink.replace(/\s/g, '-').toLowerCase()
    }));
  });
};

exports.commentSubmit = function(req, res) {
  var data = req.body.data;
  if (req.user._id.toString() !== data.author.userId.toString()) {
    return res.sendStatus(500);
  }
  var sanitizedBody = sanitizeHtml(data.body,
    {
      allowedTags: [],
      allowedAttributes: []
    }).replace(/"/g, '"');
  if (data.body !== sanitizedBody) {
    req.flash('errors', {
      msg: 'HTML is not allowed'
    });
    return res.send(true);
  }
  var comment = new Comment({
    associatedPost: data.associatedPost,
    body: sanitizedBody,
    rank: 0,
    upvotes: 0,
    author: data.author,
    comments: [],
    topLevel: true,
    commentOn: Date.now()
  });
  commentSave(comment, Story, res);
};

exports.commentOnCommentSubmit = function(req, res) {
  var data = req.body.data;

  if (req.user._id.toString() !== data.author.userId.toString()) {
    return res.sendStatus(500);
  }

  var sanitizedBody = sanitizeHtml(data.body,
    {
      allowedTags: [],
      allowedAttributes: []
    }).replace(/"/g, '"');
  if (data.body !== sanitizedBody) {
    req.flash('errors', {
      msg: 'HTML is not allowed'
    });
    return res.send(true);
  }
  var comment = new Comment({
    associatedPost: data.associatedPost,
    body: sanitizedBody,
    rank: 0,
    upvotes: 0,
    author: data.author,
    comments: [],
    topLevel: false,
    commentOn: Date.now()
  });
  commentSave(comment, Comment, res);
};

function commentSave(comment, Context, res) {
  comment.save(function(err, data) {
    if (err) {
      return res.sendStatus(500);
    }
    try {
      Context.find({'_id': comment.associatedPost}, function (err, associatedStory) {
        if (err) {
          return res.sendStatus(500);
        }
        associatedStory = associatedStory.pop();
        if (associatedStory) {
          associatedStory.comments.push(data._id);
          associatedStory.save(function (err) {
            if (err) {
              return res.sendStatus(500);
            }
            res.send(true);
          });
        }
      });
    } catch (e) {
      // delete comment
      return res.sendStatus(500);
    }
  });
}
<commit_msg>Remove sending 500 response to user manually and rely on middleware instead<commit_after>/* eslint-disable no-catch-shadow, no-unused-vars */
var R = require('ramda'),
  debug = require('debug')('freecc:cntr:story'),
  Story = require('./../models/Story'),
  Comment = require('./../models/Comment'),
  User = require('./../models/User'),
  moment = require('../public/js/lib/moment/moment.js'),
  resources = require('./resources'),
  mongodb = require('mongodb'),
  MongoClient = mongodb.MongoClient,
  secrets = require('../config/secrets'),
  sanitizeHtml = require('sanitize-html');

function hotRank(timeValue, rank) {
  /*
   * Hotness ranking algorithm: http://amix.dk/blog/post/19588
   * tMS = postedOnDate - foundationTime;
   * Ranking...
   * f(ts, 1, rank) = log(10)z + (ts)/45000;
   */
  var hotness;
  var z = Math.log(rank) / Math.log(10);
  hotness = z + (timeValue / 115200000);
  return hotness;

}

exports.hotJSON = function(req, res) {
  var story = Story.find({}).sort({'timePosted': -1}).limit(1000);
  story.exec(function(err, stories) {
    if (err) {
      return res.sendStatus(500);
    }

    var foundationDate = 1413298800000;

    var sliceVal = stories.length >= 100 ? 100 : stories.length;
    return res.json(stories.map(function(elem) {
      return elem;
    }).sort(function(a, b) {
      return hotRank(b.timePosted - foundationDate, b.rank, b.headline)
        - hotRank(a.timePosted - foundationDate, a.rank, a.headline);
    }).slice(0, sliceVal));

  });
};

exports.recentJSON = function(req, res, next) {
  var story = Story.find({}).sort({'timePosted': -1}).limit(100);
  story.exec(function(err, stories) {
    if (err) {
      return next(err);
    }
    return res.json(stories);
  });
};

exports.hot = function(req, res) {
 return res.render('stories/index', {
    title: 'Hot stories currently trending on Camper News',
    page: 'hot'
  });
};

exports.submitNew = function(req, res) {
  return res.render('stories/index', {
    title: 'Submit a new story to Camper News',
    page: 'submit'
  });
};

exports.search = function(req, res) {
  return res.render('stories/index', {
    title: 'Search the archives of Camper News',
    page: 'search'
  });
};

exports.recent = function(req, res) {
  return res.render('stories/index', {
    title: 'Recently submitted stories on Camper News',
    page: 'recent'
  });
};

exports.preSubmit = function(req, res) {

  var data = req.query;
  var cleanData = sanitizeHtml(data.url, {
    allowedTags: [],
    allowedAttributes: []
  }).replace(/";/g, '"');
  if (data.url.replace(/&/g, '&') !== cleanData) {

    req.flash('errors', {
      msg: 'The data for this post is malformed'
    });
    return res.render('stories/index', {
      page: 'stories/submit'
    });
  }

  var title = data.title || '';
  var image = data.image || '';
  var description = data.description || '';
  return res.render('stories/index', {
    title: 'Confirm your Camper News story submission',
    page: 'storySubmission',
    storyURL: data.url,
    storyTitle: title,
    storyImage: image,
    storyMetaDescription: description
  });
};

exports.returnIndividualStory = function(req, res, next) {
  var dashedName = req.params.storyName;

  var storyName = dashedName.replace(/\-/g, ' ');

  Story.find({'storyLink': new RegExp(storyName, 'i')}, function(err, story) {
    if (err) {
      return next(err);
    }

    if (story.length < 1) {
      req.flash('errors', {
        msg: "404: We couldn't find a story with that name. Please double check the name."
      });

      return res.redirect('/stories/');
    }

    story = story.pop();
    var dashedNameFull = story.storyLink.toLowerCase().replace(/\s/g, '-');
    if (dashedNameFull !== dashedName) {
      return res.redirect('../stories/' + dashedNameFull);
    }

    var userVoted = false;
    try {
      var votedObj = story.upVotes.filter(function(a) {
        return a['upVotedByUsername'] === req.user['profile']['username'];
      });
      if (votedObj.length > 0) {
        userVoted = true;
      }
    } catch(err) {
      userVoted = false;
    }
    res.render('stories/index', {
      title: story.headline,
      link: story.link,
      author: story.author,
      description: story.description,
      rank: story.upVotes.length,
      upVotes: story.upVotes,
      comments: story.comments,
      id: story._id,
      timeAgo: moment(story.timePosted).fromNow(),
      image: story.image,
      page: 'show',
      storyMetaDescription: story.metaDescription,
      hasUserVoted: userVoted
    });
  });
};

exports.getStories = function(req, res) {
  MongoClient.connect(secrets.db, function(err, database) {
    if (err) {
      return res.sendStatus(500);
    }
    database.collection('stories').find({
      '$text': {
        '$search': req.body.data.searchValue
      }
    }, {
      headline: 1,
      timePosted: 1,
      link: 1,
      description: 1,
      rank: 1,
      upVotes: 1,
      author: 1,
      comments: 1,
      image: 1,
      storyLink: 1,
      metaDescription: 1,
      textScore: {
        $meta: 'textScore'
      }
    }, {
      sort: {
        textScore: {
          $meta: 'textScore'
        }
      }
    }).toArray(function(err, items) {
      if (err) {
        return res.sendStatus(500);
      }
      if (items !== null && items.length !== 0) {
        return res.json(items);
      }
      return res.sendStatus(404);
    });
  });
};

exports.upvote = function(req, res, next) {
  var data = req.body.data;
  Story.find({'_id': data.id}, function(err, story) {
    if (err) {
      return next(err);
    }
    story = story.pop();
    story.rank++;
    story.upVotes.push(
      {
        upVotedBy: data.upVoter._id,
        upVotedByUsername: data.upVoter.profile.username
      }
    );
    story.markModified('rank');
    story.save();
    User.find({'_id': story.author.userId}, function(err, user) {
      'use strict';
      if (err) {
        return next(err);
      }
      user = user.pop();
      user.progressTimestamps.push(Date.now());
      user.save();
    });
    return res.send(story);
  });
};

exports.comments = function(req, res, next) {
  var data = req.params.id;
  Comment.find({'_id': data}, function(err, comment) {
    if (err) {
      return next(err);
    }
    comment = comment.pop();
    return res.send(comment);
  });
};

exports.newStory = function(req, res) {
  if (!req.user) {
    return res.sendStatus(500);
  }
  var url = req.body.data.url;
  var cleanURL = sanitizeHtml(url, {
    allowedTags: [],
    allowedAttributes: []
  }).replace(/"/g, '"');
  if (cleanURL !== url) {
    req.flash('errors', {
      msg: "The URL you submitted doesn't appear valid"
    });
    return res.json({
      alreadyPosted: true,
      storyURL: '/stories/submit'
    });

  }
  if (url.search(/^https?:\/\//g) === -1) {
    url = 'http://' + url;
  }
  Story.find({'link': url}, function(err, story) {
    if (err) {
      return res.sendStatus(500);
    }
    if (story.length) {
      req.flash('errors', {
        msg: "Someone's already posted that link. Here's the discussion."
      });
      return res.json({
        alreadyPosted: true,
        storyURL: '/stories/' + story.pop().storyLink
      });
    }
    resources.getURLTitle(url, processResponse);
  });

  function processResponse(err, story) {
    if (err) {
      res.json({
        alreadyPosted: false,
        storyURL: url,
        storyTitle: '',
        storyImage: '',
        storyMetaDescription: ''
      });
    } else {
      res.json({
        alreadyPosted: false,
        storyURL: url,
        storyTitle: story.title,
        storyImage: story.image,
        storyMetaDescription: story.description
      });
    }
  }
};

exports.storySubmission = function(req, res) {
  var data = req.body.data;
  if (req.user._id.toString() !== data.author.userId.toString()) {
    return res.sendStatus(500);
  }
  var storyLink = data.headline
    .replace(/\'/g, '')
    .replace(/\"/g, '')
    .replace(/,/g, '')
    .replace(/[^a-z0-9]/gi, ' ')
    .replace(/\s+/g, ' ')
    .toLowerCase();
  var link = data.link;
  if (link.search(/^https?:\/\//g) === -1) {
    link = 'http://' + link;
  }
  var story = new Story({
    headline: sanitizeHtml(data.headline, {
      allowedTags: [],
      allowedAttributes: []
    }).replace(/"/g, '"'),
    timePosted: Date.now(),
    link: link,
    description: sanitizeHtml(data.description, {
      allowedTags: [],
      allowedAttributes: []
    }).replace(/"/g, '"'),
    rank: 1,
    upVotes: data.upVotes,
    author: data.author,
    comments: [],
    image: data.image,
    storyLink: storyLink,
    metaDescription: data.storyMetaDescription
  });

  req.user.progressTimestamps.push(Date.now());
  req.user.save();

  story.save(function(err) {
    if (err) {
      return res.sendStatus(500);
    }
    res.send(JSON.stringify({
      storyLink: story.storyLink.replace(/\s/g, '-').toLowerCase()
    }));
  });
};

exports.commentSubmit = function(req, res) {
  var data = req.body.data;
  if (req.user._id.toString() !== data.author.userId.toString()) {
    return res.sendStatus(500);
  }
  var sanitizedBody = sanitizeHtml(data.body,
    {
      allowedTags: [],
      allowedAttributes: []
    }).replace(/"/g, '"');
  if (data.body !== sanitizedBody) {
    req.flash('errors', {
      msg: 'HTML is not allowed'
    });
    return res.send(true);
  }
  var comment = new Comment({
    associatedPost: data.associatedPost,
    body: sanitizedBody,
    rank: 0,
    upvotes: 0,
    author: data.author,
    comments: [],
    topLevel: true,
    commentOn: Date.now()
  });
  commentSave(comment, Story, res);
};

exports.commentOnCommentSubmit = function(req, res) {
  var data = req.body.data;

  if (req.user._id.toString() !== data.author.userId.toString()) {
    return res.sendStatus(500);
  }

  var sanitizedBody = sanitizeHtml(data.body,
    {
      allowedTags: [],
      allowedAttributes: []
    }).replace(/"/g, '"');
  if (data.body !== sanitizedBody) {
    req.flash('errors', {
      msg: 'HTML is not allowed'
    });
    return res.send(true);
  }
  var comment = new Comment({
    associatedPost: data.associatedPost,
    body: sanitizedBody,
    rank: 0,
    upvotes: 0,
    author: data.author,
    comments: [],
    topLevel: false,
    commentOn: Date.now()
  });
  commentSave(comment, Comment, res);
};

function commentSave(comment, Context, res) {
  comment.save(function(err, data) {
    if (err) {
      return res.sendStatus(500);
    }
    try {
      Context.find({'_id': comment.associatedPost}, function (err, associatedStory) {
        if (err) {
          return res.sendStatus(500);
        }
        associatedStory = associatedStory.pop();
        if (associatedStory) {
          associatedStory.comments.push(data._id);
          associatedStory.save(function (err) {
            if (err) {
              return res.sendStatus(500);
            }
            res.send(true);
          });
        }
      });
    } catch (e) {
      // delete comment
      return res.sendStatus(500);
    }
  });
}
<|endoftext|>


<commit_before>/**
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @providesModule TabBarItemIOS
 * @noflow
 */
'use strict';

var Image = require('Image');
var React = require('React');
var StaticContainer = require('StaticContainer.react');
var StyleSheet = require('StyleSheet');
var View = require('View');
var resolveAssetSource = require('resolveAssetSource');

var requireNativeComponent = require('requireNativeComponent');

var TabBarItemIOS = React.createClass({
  propTypes: {
    ...View.propTypes,
    /**
     * Little red bubble that sits at the top right of the icon.
     */
    badge: React.PropTypes.oneOfType([
      React.PropTypes.string,
      React.PropTypes.number,
    ]),
    /**
     * Items comes with a few predefined system icons. Note that if you are
     * using them, the title and selectedIcon will be overriden with the
     * system ones.
     */
    systemIcon: React.PropTypes.oneOf([
      'bookmarks',
      'contacts',
      'downloads',
      'favorites',
      'featured',
      'history',
      'more',
      'most-recent',
      'most-viewed',
      'recents',
      'search',
      'top-rated',
    ]),
    /**
     * A custom icon for the tab. It is ignored when a system icon is defined.
     */
    icon: Image.propTypes.source,
    /**
     * A custom icon when the tab is selected. It is ignored when a system
     * icon is defined. If left empty, the icon will be tinted in blue.
     */
    selectedIcon: Image.propTypes.source,
    /**
     * Callback when this tab is being selected, you should change the state of your
     * component to set selected={true}.
     */
    onPress: React.PropTypes.func,
    /**
     * It specifies whether the children are visible or not. If you see a
     * blank content, you probably forgot to add a selected one.
     */
    selected: React.PropTypes.bool,
    /**
     * React style object.
     */
    style: View.propTypes.style,
    /**
     * Text that appears under the icon. It is ignored when a system icon
     * is defined.
     */
    title: React.PropTypes.string,
  },

  getInitialState: function() {
    return {
      hasBeenSelected: false,
    };
  },

  componentWillMount: function() {
    if (this.props.selected) {
      this.setState({hasBeenSelected: true});
    }
  },

  componentWillReceiveProps: function(nextProps: { selected?: boolean }) {
    if (this.state.hasBeenSelected || nextProps.selected) {
      this.setState({hasBeenSelected: true});
    }
  },

  render: function() {
    var tabContents = null;
    // if the tab has already been shown once, always continue to show it so we
    // preserve state between tab transitions
    if (this.state.hasBeenSelected) {
      tabContents =
        <StaticContainer shouldUpdate={this.props.selected}>
          {this.props.children}
        </StaticContainer>;
    } else {
      tabContents = <View />;
    }

    var badge = typeof this.props.badge === 'number' ?
      '' + this.props.badge :
      this.props.badge;

    return (
      <RCTTabBarItem
        {...this.props}
        icon={this.props.systemIcon || resolveAssetSource(this.props.icon)}
        selectedIcon={resolveAssetSource(this.props.selectedIcon)}
        badge={badge}
        style={[styles.tab, this.props.style]}>
        {tabContents}
      </RCTTabBarItem>
    );
  }
});

var styles = StyleSheet.create({
  tab: {
    position: 'absolute',
    top: 0,
    right: 0,
    bottom: 0,
    left: 0,
  }
});

var RCTTabBarItem = requireNativeComponent('RCTTabBarItem', TabBarItemIOS);

module.exports = TabBarItemIOS;
<commit_msg>Add string as possible propType for 'systemIcon'<commit_after>/**
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @providesModule TabBarItemIOS
 * @noflow
 */
'use strict';

var Image = require('Image');
var React = require('React');
var StaticContainer = require('StaticContainer.react');
var StyleSheet = require('StyleSheet');
var View = require('View');
var resolveAssetSource = require('resolveAssetSource');

var requireNativeComponent = require('requireNativeComponent');

var TabBarItemIOS = React.createClass({
  propTypes: {
    ...View.propTypes,
    /**
     * Little red bubble that sits at the top right of the icon.
     */
    badge: React.PropTypes.oneOfType([
      React.PropTypes.string,
      React.PropTypes.number,
    ]),
    /**
     * Items comes with a few predefined system icons. Note that if you are
     * using them, the title and selectedIcon will be overriden with the
     * system ones.
     */
    systemIcon: React.PropTypes.oneOf([
      'bookmarks',
      'contacts',
      'downloads',
      'favorites',
      'featured',
      'history',
      'more',
      'most-recent',
      'most-viewed',
      'recents',
      'search',
      'top-rated',
    ]),
    /**
     * A custom icon for the tab. It is ignored when a system icon is defined.
     */
    icon: React.PropTypes.oneOfType([
      React.PropTypes.string,
      Image.propTypes.source,
    ]),
    /**
     * A custom icon when the tab is selected. It is ignored when a system
     * icon is defined. If left empty, the icon will be tinted in blue.
     */
    selectedIcon: Image.propTypes.source,
    /**
     * Callback when this tab is being selected, you should change the state of your
     * component to set selected={true}.
     */
    onPress: React.PropTypes.func,
    /**
     * It specifies whether the children are visible or not. If you see a
     * blank content, you probably forgot to add a selected one.
     */
    selected: React.PropTypes.bool,
    /**
     * React style object.
     */
    style: View.propTypes.style,
    /**
     * Text that appears under the icon. It is ignored when a system icon
     * is defined.
     */
    title: React.PropTypes.string,
  },

  getInitialState: function() {
    return {
      hasBeenSelected: false,
    };
  },

  componentWillMount: function() {
    if (this.props.selected) {
      this.setState({hasBeenSelected: true});
    }
  },

  componentWillReceiveProps: function(nextProps: { selected?: boolean }) {
    if (this.state.hasBeenSelected || nextProps.selected) {
      this.setState({hasBeenSelected: true});
    }
  },

  render: function() {
    var tabContents = null;
    // if the tab has already been shown once, always continue to show it so we
    // preserve state between tab transitions
    if (this.state.hasBeenSelected) {
      tabContents =
        <StaticContainer shouldUpdate={this.props.selected}>
          {this.props.children}
        </StaticContainer>;
    } else {
      tabContents = <View />;
    }

    var badge = typeof this.props.badge === 'number' ?
      '' + this.props.badge :
      this.props.badge;

    return (
      <RCTTabBarItem
        {...this.props}
        icon={this.props.systemIcon || resolveAssetSource(this.props.icon)}
        selectedIcon={resolveAssetSource(this.props.selectedIcon)}
        badge={badge}
        style={[styles.tab, this.props.style]}>
        {tabContents}
      </RCTTabBarItem>
    );
  }
});

var styles = StyleSheet.create({
  tab: {
    position: 'absolute',
    top: 0,
    right: 0,
    bottom: 0,
    left: 0,
  }
});

var RCTTabBarItem = requireNativeComponent('RCTTabBarItem', TabBarItemIOS);

module.exports = TabBarItemIOS;
<|endoftext|>


<commit_before>var Const = require('../common/common').Const;

TaskController = function(Task) {
    this.Task = Task;
};

TaskController.prototype.sendAllTasks = function (res) {
    var thisObj = this;
    return function (err) {
        if (err) res.send(err);
        this.Task.findAll(function (err, tasks) {
            if (err) res.send(err);
            res.json(tasks);
        });
    };
};

TaskController.prototype.read = function (req, res) {
    this.sendAllTasks(res).bind(this)(null);
};

TaskController.prototype.create = function (req, res) {
    this.Task.save({
        content: req.body.content,
        isCompleted: false,
        owner: req.body.owner,
        due_date: req.body.due_date
    }, this.sendAllTasks(res).bind(this));
};

TaskController.prototype.del = function (req, res) {
    this.Task.remove(
        req.params[Const.TASK_ID_PARAM],
        this.sendAllTasks(res).bind(this));
};

TaskController.prototype.update = function (req, res) {
    this.Task.update(req.params[Const.TASK_ID_PARAM], req.body, this.sendAllTasks(res).bind(this));
};

exports.TaskController = TaskController;<commit_msg>Removed redundant variable<commit_after>var Const = require('../common/common').Const;

TaskController = function(Task) {
    this.Task = Task;
};

TaskController.prototype.sendAllTasks = function (res) {
    return function (err) {
        if (err) res.send(err);
        this.Task.findAll(function (err, tasks) {
            if (err) res.send(err);
            res.json(tasks);
        });
    };
};

TaskController.prototype.read = function (req, res) {
    this.sendAllTasks(res).bind(this)(null);
};

TaskController.prototype.create = function (req, res) {
    this.Task.save({
        content: req.body.content,
        isCompleted: false,
        owner: req.body.owner,
        due_date: req.body.due_date
    }, this.sendAllTasks(res).bind(this));
};

TaskController.prototype.del = function (req, res) {
    this.Task.remove(
        req.params[Const.TASK_ID_PARAM],
        this.sendAllTasks(res).bind(this));
};

TaskController.prototype.update = function (req, res) {
    this.Task.update(req.params[Const.TASK_ID_PARAM], req.body, this.sendAllTasks(res).bind(this));
};

exports.TaskController = TaskController;<|endoftext|>