<commit_before>'use strict';
//GLOBALS
var mainContainer,
    boardContainer,
    board,
    boardId = 'board',
    boardContainerID = 'boardCont',
    currentLvl = 3, // 3 is the starting level
    tilesCounter = 1, //Counter to hold the revealed answers
    levelBonus = 5, //holds the points that will be added if the level succeeds
    cellSize = 50,
    pointsForCorrectAnswer = 10,
    wasLevelCleared = true,
    playerCanClick = true,
    minRowsSize = 2,
    minCellsSize = 2,
    initCellsSize = 3,
    initRowsSize = 3,
    maxRowsSize = 6,
    maxCellsSize = 6,
    boardPadding = 60,
    answers = '',
    trials = 15, //how many trials user has
    score = 0, //user score
    beforeHideCellsTimeout = 1500,
    correctAnswerTimeout = 500,
    infoboxTimeout = 300,
    betweenLevelsTimeout = 2000,
    board = null,
    popup = null,
    canClickOnInfoBox = true, // if "false" the next level will start automatically
    storage = window.localStorage;//holds the browsers local storage

var boardDimArray = [[2, 2], [2, 2], [3, 3], [4, 3], [4, 4], [5, 4], [5, 5], [6, 5], [6, 6]];

var MESSAGES = {
    levelLost: 'Sorry, you missed!\nTry again with less tiles!',
    tileSucess: 'You hit it right! Guess the next tile!',
    levelSuccess: 'Congratulations! You won another level.\nTry with more tiles!',
    guess: 'Guess the next tile!',
    gameOver: 'GAME OVER!',
    scoreMessage: 'Your score is: ',
    payAttention: 'Remember the tiles positions!',
    bestResult: 'Your result will be stored as BEST RESULT.\nCongratulations!'
};

var ScoreBoardElement = function (imgURL, content, val) {
    this.imgURL = imgURL;
    this.content = content;
    this.val = val;
};

function getLvl() {
    return currentLvl;
}

function createBackground() {
    mainContainer = document.createElement('main');
    mainContainer.id = 'main-container';

    document.body.appendChild(mainContainer);
}

function createScoreBoard() {
    var scoreBoard = document.createElement('div');
    scoreBoard.id = 'score-board';
    mainContainer.appendChild(scoreBoard);
    var scoreBoardList = document.createElement('ul');
    scoreBoard.appendChild(scoreBoardList);

    var categories = [new ScoreBoardElement('', 'Tiles', getLvl()),
        new ScoreBoardElement('', 'Trials', trials),
        new ScoreBoardElement('', 'Score', score)];

    var categoriesSize = categories.length;
    for (var i = 0; i < categoriesSize; i++) {
        var li = document.createElement('li');
        li.className = 'score-board-element';
        li.innerText = categories[i].content;
        li.textContent = categories[i].content;
        scoreBoardList.appendChild(li);

        var sp = document.createElement('span');
        sp.className = 'score-board-value';
        sp.id = categories[i].content;
        sp.innerText = categories[i].val;
        sp.textContent = categories[i].val;
        li.appendChild(sp);
    }
}

function addPoints(tilePts, levelPts) {
    //increases the score by given amount of points passed as parameters
    levelPts = levelPts || 0;
    var scoreSpan = document.getElementById('Score');
    score = scoreSpan.innerText || scoreSpan.textContent;
    score = parseInt(score) + parseInt(tilePts) + parseInt(levelPts);
    scoreSpan.innerText = score;
    scoreSpan.textContent = score;
}
function updateLevelBonus(direction) {
    switch (direction) {
        case 'down':
            if (levelBonus > 5) {
                levelBonus /= 2;
            }
            break;
        case 'up':
            levelBonus *= 2;
            break;
    }
}
function getUserClick(event) {
    if(playerCanClick === true) {

        // This function handles the player click
        var element = event.target;
        // var selectedCellID = element.getAttribute('id');
        var isClicked = (element.getAttribute('data-is-clicked')) ? true : false;
        if (element.getAttribute('data-is-true') && !isClicked) {
            var currLevel = getLvl();
            if (tilesCounter < currLevel) {
                element.setAttribute('data-is-clicked', 'true');
                tilesCounter++;
                addPoints(pointsForCorrectAnswer);
                element.setAttribute('data-is-true', 'false');

            } else if (tilesCounter === currLevel) {
                element.setAttribute('data-is-clicked', 'true');
                wasLevelCleared = true;
                addPoints(pointsForCorrectAnswer, levelBonus);
                element.setAttribute('data-is-true', 'false');

                setTimeout(function () {
                    updateInfobox(MESSAGES.levelSuccess);
                    prepAndShowInfoForNextLvl();
                }, infoboxTimeout);

                updateLevelBonus('up');
            }
        } else if (isClicked) {
            return;
        } else {
            element.classList.add('incorrectAnswer');
            updateLevelBonus('down');
            playerCanClick = false;
            wasLevelCleared = false;
            setTimeout(function () {
                updateInfobox(MESSAGES.levelLost);
                prepAndShowInfoForNextLvl();
            }, infoboxTimeout);
        }
    }
}

function goToNextLvl() {
    playerCanClick = true;
    if (trials) {
        //update trials in scoreboard
        trials--;
        document.getElementById('Trials').innerHTML = trials;

        //update Tiles in scoreboard
        document.getElementById('Tiles').innerHTML = getLvl();

        //clear counter
        tilesCounter = 1;

        //generate new board => Chech which is the current level and calc the board cells and rows
        var currLvl = getLvl();
        var board = (currLvl <= boardDimArray.length) ? (currLvl - 1) : (boardDimArray.length - 1);

        // Invokes "createBoard(cells, rows)" by giving in the correct number of cells and rows
        createBoard(boardDimArray[board][0], boardDimArray[board][1]);
        updateInfobox(MESSAGES.payAttention);
    } else {
        //GAME OVER - no more trials. Function for displaying GAME OVER Screen here
        endGame();
    }
}

function updatePopupSize(width, height) {
    if (popup) {
        width = width || board.offsetWidth;
        height = height || board.offsetHeight;

        popup.style.width = width + 'px';
        popup.style.height = height + 'px';
        popup.style.left = -(width / 2) + 'px';
    }
}

function updateBoardAndPopupSizes(cells, rows) {
    var width = (cells * cellSize) + boardPadding;
    var height = (rows * cellSize) + boardPadding;
    board.style.width = width + 'px';
    board.style.height = height + 'px';

    updatePopupSize(width, height);
}

function createBoard(cells, rows) {
    if (cells) {
        cells = ((cells) && (cells > minCellsSize)) ? ((cells < maxCellsSize) ? cells : maxCellsSize) : minCellsSize;
    } else {
        cells = initCellsSize;
    }
    if (rows) {
        rows = ((rows) && (rows > minRowsSize)) ? ((rows < maxRowsSize) ? rows : maxRowsSize) : minRowsSize;
    } else {
        rows = initRowsSize;
    }

    if (!board) {
        boardContainer = document.createElement('div');
        boardContainer.id = boardContainerID;
        mainContainer.appendChild(boardContainer);

        board = document.createElement('div');
        board.id = boardId;
        boardContainer.appendChild(board);
    }

    board.innerHTML = '';

    updateBoardAndPopupSizes(cells, rows);

    setTimeout(function () {
        for (var i = 1; i <= rows; i++) {
            var row = document.createElement('div');
            row.setAttribute('id', 'row' + i);
            row.className = 'row';
            for (var j = 1; j <= cells; j++) {
                var cell = document.createElement('div');
                cell.setAttribute('id', 'cell' + i + j);
                cell.className = 'cell';
                row.appendChild(cell);
            }
            board.appendChild(row);
        }
        assignCorrectAnswers(getLvl());
    }, correctAnswerTimeout);
}

function createInfoBox() {
    var infobox = document.createElement('div');
    infobox.id = 'infobox';
    mainContainer.appendChild(infobox);
}

function updateInfobox(occasion) {
    var infobox = document.getElementById('infobox');
    infobox.innerText = occasion;
    infobox.textContent = occasion;
}

function assignCorrectAnswers(level) {

    var assignedIndexes = [],
        selectedCells = [],
        cellsArray = document.getElementsByClassName('cell'),
        canContinue = true;

    var getRandomNumber = function () {
        return Math.floor(Math.random() * cellsArray.length);
    };

    for (var i = 0; i < level; i++) {
        canContinue = true;

        while (canContinue) {
            var randomCellIndex = getRandomNumber();
            if (assignedIndexes.indexOf(randomCellIndex) === -1) {
                var cellElement = cellsArray[randomCellIndex];
                selectedCells.push(cellElement);
                cellElement.setAttribute('data-is-true', 'true');
                assignedIndexes.push(randomCellIndex);
                canContinue = false;
            }
        }
    }

    answers = (answers !== '') ? answers : '';
    answers = assignedIndexes.toString();

    //show the pattern to player
    for (var j = 0; j < selectedCells.length; j++) {
        selectedCells[j].classList.add('openAnswer');
    }

    //hide the pattern and assign onClick event listener
    setTimeout(function () {
        hidePattern(selectedCells);
    }, beforeHideCellsTimeout);
}

function hidePattern(selectedCellsPattern) {
    for (var k = 0; k < selectedCellsPattern.length; k++) {
        selectedCellsPattern[k].classList.remove('openAnswer');
    }

    for (var i = 0; i < document.getElementsByClassName('cell').length; i++) {
        document.getElementsByClassName('cell')[i].addEventListener('click', getUserClick.bind(this), false);
    }
    updateInfobox(MESSAGES.guess);
}

function closePopup() {
    popup.classList.remove('opened');
    goToNextLvl();
}

function updateCurrentLvl() {
    if (wasLevelCleared === true) {
        currentLvl++;
    } else if (currentLvl > 1) {
        currentLvl--;
    }
}

function prepAndShowInfoForNextLvl() {
    var msg = document.getElementById('infoDialogTxt');
    if (!popup) {
        popup = document.createElement('div');
        msg = document.createElement('p');
        msg.id = 'infoDialogTxt';

        popup.setAttribute('id', 'popup');
        if (canClickOnInfoBox === true) {
            popup.addEventListener('click', closePopup, false);
        }
        boardContainer.appendChild(popup);
        popup.appendChild(msg);
    }

    updateCurrentLvl();

    var msgText = '';
    if (wasLevelCleared) {
        msgText += 'Bonus Points: +' + levelBonus + '
';
    }
    msgText += 'Next: ' + getLvl() + ' tiles';
    msg.innerHTML = msgText;

    popup.classList.add('opened');
    updatePopupSize();

    if (canClickOnInfoBox !== true) {
        setTimeout(function () {
            closePopup();
        }, betweenLevelsTimeout);
    }
}

function storeMaxScore(currentScore) {
    if (typeof (storage) !== 'undefined') {
        if (typeof (storage.maxScore) !== 'undefined') {
            if (parseInt(storage.maxScore) < currentScore) {
                storage.setItem('maxScore', currentScore);
                return true;
            }
        } else {
            storage.setItem('maxScore', currentScore);
            return true;
        }
    }
    return false;
}

function endGame() {
    if (!storeMaxScore(score)) {
        $('#main-container').html("").append("

" + MESSAGES.gameOver + "
\n
" + MESSAGES.scoreMessage + score + "
\nNew game?
");
    } else {
        $('#main-container').html("").append("

" + MESSAGES.gameOver + "
\n
" + MESSAGES.scoreMessage + score + "
\n" + MESSAGES.bestResult + "
New game?
");
    }
}

createBackground();
createScoreBoard();
createBoard();
createInfoBox();<commit_msg>fixing the update of the level bonus<commit_after>'use strict';
//GLOBALS
var mainContainer,
    boardContainer,
    board,
    boardId = 'board',
    boardContainerID = 'boardCont',
    currentLvl = 3, // 3 is the starting level
    tilesCounter = 1, //Counter to hold the revealed answers
    levelBonus = 5, //holds the points that will be added if the level succeeds
    cellSize = 50,
    pointsForCorrectAnswer = 10,
    wasLevelCleared = true,
    playerCanClick = true,
    minRowsSize = 2,
    minCellsSize = 2,
    initCellsSize = 3,
    initRowsSize = 3,
    maxRowsSize = 6,
    maxCellsSize = 6,
    boardPadding = 60,
    answers = '',
    trials = 15, //how many trials user has
    score = 0, //user score
    beforeHideCellsTimeout = 1500,
    correctAnswerTimeout = 500,
    infoboxTimeout = 300,
    betweenLevelsTimeout = 2000,
    board = null,
    popup = null,
    canClickOnInfoBox = true, // if "false" the next level will start automatically
    storage = window.localStorage;//holds the browsers local storage

var boardDimArray = [[2, 2], [2, 2], [3, 3], [4, 3], [4, 4], [5, 4], [5, 5], [6, 5], [6, 6]];

var MESSAGES = {
    levelLost: 'Sorry, you missed!\nTry again with less tiles!',
    tileSucess: 'You hit it right! Guess the next tile!',
    levelSuccess: 'Congratulations! You won another level.\nTry with more tiles!',
    guess: 'Guess the next tile!',
    gameOver: 'GAME OVER!',
    scoreMessage: 'Your score is: ',
    payAttention: 'Remember the tiles positions!',
    bestResult: 'Your result will be stored as BEST RESULT.\nCongratulations!'
};

var ScoreBoardElement = function (imgURL, content, val) {
    this.imgURL = imgURL;
    this.content = content;
    this.val = val;
};

function getLvl() {
    return currentLvl;
}

function createBackground() {
    mainContainer = document.createElement('main');
    mainContainer.id = 'main-container';

    document.body.appendChild(mainContainer);
}

function createScoreBoard() {
    var scoreBoard = document.createElement('div');
    scoreBoard.id = 'score-board';
    mainContainer.appendChild(scoreBoard);
    var scoreBoardList = document.createElement('ul');
    scoreBoard.appendChild(scoreBoardList);

    var categories = [new ScoreBoardElement('', 'Tiles', getLvl()),
        new ScoreBoardElement('', 'Trials', trials),
        new ScoreBoardElement('', 'Score', score)];

    var categoriesSize = categories.length;
    for (var i = 0; i < categoriesSize; i++) {
        var li = document.createElement('li');
        li.className = 'score-board-element';
        li.innerText = categories[i].content;
        li.textContent = categories[i].content;
        scoreBoardList.appendChild(li);

        var sp = document.createElement('span');
        sp.className = 'score-board-value';
        sp.id = categories[i].content;
        sp.innerText = categories[i].val;
        sp.textContent = categories[i].val;
        li.appendChild(sp);
    }
}

function addPoints(tilePts, levelPts) {
    //increases the score by given amount of points passed as parameters
    levelPts = levelPts || 0;
    var scoreSpan = document.getElementById('Score');
    score = scoreSpan.innerText || scoreSpan.textContent;
    score = parseInt(score) + parseInt(tilePts) + parseInt(levelPts);
    scoreSpan.innerText = score;
    scoreSpan.textContent = score;
}
function updateLevelBonus(direction) {
    switch (direction) {
        case 'down':
            if (levelBonus > 5) {
                levelBonus /= 2;
            }
            break;
        case 'up':
            levelBonus *= 2;
            break;
    }
}
function getUserClick(event) {
    if(playerCanClick === true) {

        // This function handles the player click
        var element = event.target;
        // var selectedCellID = element.getAttribute('id');
        var isClicked = (element.getAttribute('data-is-clicked')) ? true : false;
        if (element.getAttribute('data-is-true') && !isClicked) {
            var currLevel = getLvl();
            if (tilesCounter < currLevel) {
                element.setAttribute('data-is-clicked', 'true');
                tilesCounter++;
                addPoints(pointsForCorrectAnswer);
                element.setAttribute('data-is-true', 'false');

            } else if (tilesCounter === currLevel) {
                element.setAttribute('data-is-clicked', 'true');
                wasLevelCleared = true;
                updateLevelBonus('up');
                addPoints(pointsForCorrectAnswer, levelBonus);
                element.setAttribute('data-is-true', 'false');

                setTimeout(function () {
                    updateInfobox(MESSAGES.levelSuccess);
                    prepAndShowInfoForNextLvl();
                }, infoboxTimeout);
            }
        } else if (isClicked) {
            return;
        } else {
            element.classList.add('incorrectAnswer');
            updateLevelBonus('down');
            playerCanClick = false;
            wasLevelCleared = false;
            setTimeout(function () {
                updateInfobox(MESSAGES.levelLost);
                prepAndShowInfoForNextLvl();
            }, infoboxTimeout);
        }
    }
}

function goToNextLvl() {
    playerCanClick = true;
    if (trials) {
        //update trials in scoreboard
        trials--;
        document.getElementById('Trials').innerHTML = trials;

        //update Tiles in scoreboard
        document.getElementById('Tiles').innerHTML = getLvl();

        //clear counter
        tilesCounter = 1;

        //generate new board => Chech which is the current level and calc the board cells and rows
        var currLvl = getLvl();
        var board = (currLvl <= boardDimArray.length) ? (currLvl - 1) : (boardDimArray.length - 1);

        // Invokes "createBoard(cells, rows)" by giving in the correct number of cells and rows
        createBoard(boardDimArray[board][0], boardDimArray[board][1]);
        updateInfobox(MESSAGES.payAttention);
    } else {
        //GAME OVER - no more trials. Function for displaying GAME OVER Screen here
        endGame();
    }
}

function updatePopupSize(width, height) {
    if (popup) {
        width = width || board.offsetWidth;
        height = height || board.offsetHeight;

        popup.style.width = width + 'px';
        popup.style.height = height + 'px';
        popup.style.left = -(width / 2) + 'px';
    }
}

function updateBoardAndPopupSizes(cells, rows) {
    var width = (cells * cellSize) + boardPadding;
    var height = (rows * cellSize) + boardPadding;
    board.style.width = width + 'px';
    board.style.height = height + 'px';

    updatePopupSize(width, height);
}

function createBoard(cells, rows) {
    if (cells) {
        cells = ((cells) && (cells > minCellsSize)) ? ((cells < maxCellsSize) ? cells : maxCellsSize) : minCellsSize;
    } else {
        cells = initCellsSize;
    }
    if (rows) {
        rows = ((rows) && (rows > minRowsSize)) ? ((rows < maxRowsSize) ? rows : maxRowsSize) : minRowsSize;
    } else {
        rows = initRowsSize;
    }

    if (!board) {
        boardContainer = document.createElement('div');
        boardContainer.id = boardContainerID;
        mainContainer.appendChild(boardContainer);

        board = document.createElement('div');
        board.id = boardId;
        boardContainer.appendChild(board);
    }

    board.innerHTML = '';

    updateBoardAndPopupSizes(cells, rows);

    setTimeout(function () {
        for (var i = 1; i <= rows; i++) {
            var row = document.createElement('div');
            row.setAttribute('id', 'row' + i);
            row.className = 'row';
            for (var j = 1; j <= cells; j++) {
                var cell = document.createElement('div');
                cell.setAttribute('id', 'cell' + i + j);
                cell.className = 'cell';
                row.appendChild(cell);
            }
            board.appendChild(row);
        }
        assignCorrectAnswers(getLvl());
    }, correctAnswerTimeout);
}

function createInfoBox() {
    var infobox = document.createElement('div');
    infobox.id = 'infobox';
    mainContainer.appendChild(infobox);
}

function updateInfobox(occasion) {
    var infobox = document.getElementById('infobox');
    infobox.innerText = occasion;
    infobox.textContent = occasion;
}

function assignCorrectAnswers(level) {

    var assignedIndexes = [],
        selectedCells = [],
        cellsArray = document.getElementsByClassName('cell'),
        canContinue = true;

    var getRandomNumber = function () {
        return Math.floor(Math.random() * cellsArray.length);
    };

    for (var i = 0; i < level; i++) {
        canContinue = true;

        while (canContinue) {
            var randomCellIndex = getRandomNumber();
            if (assignedIndexes.indexOf(randomCellIndex) === -1) {
                var cellElement = cellsArray[randomCellIndex];
                selectedCells.push(cellElement);
                cellElement.setAttribute('data-is-true', 'true');
                assignedIndexes.push(randomCellIndex);
                canContinue = false;
            }
        }
    }

    answers = (answers !== '') ? answers : '';
    answers = assignedIndexes.toString();

    //show the pattern to player
    for (var j = 0; j < selectedCells.length; j++) {
        selectedCells[j].classList.add('openAnswer');
    }

    //hide the pattern and assign onClick event listener
    setTimeout(function () {
        hidePattern(selectedCells);
    }, beforeHideCellsTimeout);
}

function hidePattern(selectedCellsPattern) {
    for (var k = 0; k < selectedCellsPattern.length; k++) {
        selectedCellsPattern[k].classList.remove('openAnswer');
    }

    for (var i = 0; i < document.getElementsByClassName('cell').length; i++) {
        document.getElementsByClassName('cell')[i].addEventListener('click', getUserClick.bind(this), false);
    }
    updateInfobox(MESSAGES.guess);
}

function closePopup() {
    popup.classList.remove('opened');
    goToNextLvl();
}

function updateCurrentLvl() {
    if (wasLevelCleared === true) {
        currentLvl++;
    } else if (currentLvl > 1) {
        currentLvl--;
    }
}

function prepAndShowInfoForNextLvl() {
    var msg = document.getElementById('infoDialogTxt');
    if (!popup) {
        popup = document.createElement('div');
        msg = document.createElement('p');
        msg.id = 'infoDialogTxt';

        popup.setAttribute('id', 'popup');
        if (canClickOnInfoBox === true) {
            popup.addEventListener('click', closePopup, false);
        }
        boardContainer.appendChild(popup);
        popup.appendChild(msg);
    }

    updateCurrentLvl();

    var msgText = '';
    if (wasLevelCleared) {
        msgText += 'Bonus Points: +' + levelBonus + '
';
    }
    msgText += 'Next: ' + getLvl() + ' tiles';
    msg.innerHTML = msgText;

    popup.classList.add('opened');
    updatePopupSize();

    if (canClickOnInfoBox !== true) {
        setTimeout(function () {
            closePopup();
        }, betweenLevelsTimeout);
    }
}

function storeMaxScore(currentScore) {
    if (typeof (storage) !== 'undefined') {
        if (typeof (storage.maxScore) !== 'undefined') {
            if (parseInt(storage.maxScore) < currentScore) {
                storage.setItem('maxScore', currentScore);
                return true;
            }
        } else {
            storage.setItem('maxScore', currentScore);
            return true;
        }
    }
    return false;
}

function endGame() {
    if (!storeMaxScore(score)) {
        $('#main-container').html("").append("

" + MESSAGES.gameOver + "
\n
" + MESSAGES.scoreMessage + score + "
\nNew game?
");
    } else {
        $('#main-container').html("").append("

" + MESSAGES.gameOver + "
\n
" + MESSAGES.scoreMessage + score + "
\n" + MESSAGES.bestResult + "
New game?
");
    }
}

createBackground();
createScoreBoard();
createBoard();
createInfoBox();<|endoftext|>


<commit_before>// Copyright © 2015 Jan Keromnes. All rights reserved.
// The following code is covered by the AGPL-3.0 license.

const jsonpatch = require('fast-json-patch');

const db = require('./db');
const docker = require('./docker');
const log = require('./log');
const metrics = require('./metrics');
const streams = require('./streams');

// List available user machines for each project, create when necessary.
exports.getAvailableMachines = function (user) {
  const machines = {};
  for (const projectId in db.get('projects')) {
    machines[projectId] = getOrCreateNewMachine(user, projectId);
  }

  return machines;
};

// Get an existing user machine with the given project and machine ID.
exports.getMachineById = function (user, projectId, machineId) {
  const machines = user.machines[projectId];
  if (!machines) {
    return null;
  }

  return machines[machineId] || null;
};

// Get an existing user machine with the given hostname and container ID.
exports.getMachineByContainer = function (user, hostname, containerId) {
  if (containerId.length < 16) {
    log(new Error('Container ID too short: ' + containerId +
      ' (while getting a machine for ' + user.email + ')'));
    return null;
  }

  for (const projectId in user.machines) {
    for (const machine of user.machines[projectId]) {
      if (machine.docker.host === hostname &&
        machine.docker.container.startsWith(containerId)) {
        return machine;
      }
    }
  }

  return null;
};

// Change the parameters of an existing project, or create a new project.
exports.setProject = function (parameters) {
  // Find the project with this ID, create it if necessary.
  const project = getProject(parameters.id, true);

  // Extract parameters that look like JSON Patch operations (RFC 6902).
  const operations = [];
  for (const name in parameters) {
    if (name[0] !== '/') {
      continue;
    }

    operations.push({
      op: 'add',
      path: name,
      value: parameters[name]
    });
  }

  // Apply the requested changes to the project.
  jsonpatch.applyPatch(project, operations);
  db.save();
};

// Rebuild the base image of a project. (Slow)
exports.rebuild = function (projectId, callback) {
  const project = getProject(projectId);
  if (!project) {
    callback(new Error('Unknown project: ' + projectId));
    return;
  }

  const { host, image, build: dockerfile } = project.docker;
  const tag = image + ':base';
  const time = Date.now();

  docker.buildImage({ host, tag, dockerfile }, (error, stream) => {
    if (error) {
      log ('rebuild', image, error);
      callback(new Error('Unable to rebuild project: ' + projectId));
      return;
    }

    log('rebuild', image, 'started');

    streams.set(project.docker, 'logs', stream);

    stream.on('error', err => {
      log('rebuild', image, err);
      error = err;
    });

    stream.on('end', () => {
      if (error) {
        log('rebuild', image, error);
        callback(new Error('Problem while rebuilding project: ' + projectId));
        return;
      }

      log('rebuild', image, 'success');
      const now = Date.now();
      metrics.set(project, 'updated', now);
      metrics.push(project, 'build-time', [ now, now - time ]);
      callback();
    });
  });
};

// Update the base image of a project.
exports.update = function (projectId, callback) {
  const project = getProject(projectId);
  if (!project) {
    callback(new Error('Unknown project: ' + projectId));
    return;
  }

  const { host, image, update: dockerfile } = project.docker;
  const tag = image + ':latest';
  const time = Date.now();

  docker.buildImage({ host, tag, dockerfile }, (error, stream) => {
    if (error) {
      log('update', image, error);
      callback(new Error('Unable to update project: ' + projectId));
      return;
    }

    log('update', image, 'started');

    streams.set(project.docker, 'logs', stream);

    stream.on('error', err => {
      log('update', image, err);
      error = err;
    });

    stream.on('end', () => {
      if (error) {
        log('update', image, error);
        callback(new Error('Problem while updating project: ' + projectId));
        return;
      }

      log('update', image, 'success');
      const now = Date.now();
      metrics.set(project, 'updated', now);
      metrics.push(project, 'update-time', [ now, now - time ]);
      callback();
    });
  });
};

// Instantiate a user machine for a project. (Fast!)
exports.spawn = function (user, projectId, callback) {
  const project = getProject(projectId);
  if (!project) {
    callback(new Error('Unknown project: ' + projectId));
    return;
  }

  const machine = getOrCreateNewMachine(user, projectId);

  // Keep track of the last project update this machine will be based on.
  metrics.set(machine, 'updated', project.data.updated);

  // Specify which ports should be exposed publicly or kept private.
  const ports = {};
  for (const projectPort in machine.docker.ports) {
    const { port, proxy } = machine.docker.ports[projectPort];
    ports[projectPort] = {
      hostPort: port,
      publish: proxy === 'none'
    };
  }

  const { host, image } = project.docker;
  const time = Date.now();

  log('spawn', image, 'started');
  docker.runContainer({ host, image, ports }, (error, container, logs) => {
    if (error) {
      log('spawn', image, error);
      machine.status = 'start-failed';
      db.save();
      callback(new Error('Unable to start machine for project: ' + projectId));
      return;
    }

    log('spawn', image, 'success', container.id.slice(0, 16));
    machine.docker.container = container.id;
    machine.docker.logs = String(logs);
    machine.status = 'started';

    const now = Date.now();
    metrics.push(project, 'spawn-time', [ now, now - time ]);
    db.save();

    // Quickly authorize the user's public SSH keys to access this container.
    deploySSHAuthorizedKeys(user, machine, error => {
      log('spawn-sshkeys', container.id.slice(0, 16), error || 'success');
      db.save();
    });

    // Install all non-empty user configuration files into this container.
    Object.keys(user.configurations).forEach(file => {
      if (!user.configurations[file]) {
        return;
      }
      exports.deployConfiguration(user, machine, file).then(() => {
        log('spawn-config', file, container.id.slice(0, 16), 'success');
      }).catch(error => {
        log('spawn-config', file, container.id.slice(0, 16), error);
      });
    });

    callback();
  });
};

// Destroy a given user machine and recycle its ports.
exports.destroy = function (user, projectId, machineId, callback) {
  const machines = user.machines[projectId];
  if (!machines) {
    callback(new Error('No machines for project: ' + projectId));
    return;
  }

  const machine = machines[machineId];
  if (!machine || machine.status === 'new') {
    // If there is no instantiated machine to destroy, do nothing.
    callback();
    return;
  }

  const { container: containerId, host } = machine.docker;
  if (!containerId) {
    // This machine has no associated container, just recycle it as is.
    machine.status = 'new';
    db.save();
    callback();
    return;
  }

  log('destroy', containerId.slice(0, 16), 'started');
  docker.removeContainer({ host, container: containerId }, error => {
    if (error) {
      log('destroy', containerId.slice(0, 16), error);
      callback(error);
      return;
    }

    // Recycle the machine's name and ports.
    machine.status = 'new';
    machine.docker.container = '';
    db.save();
    callback();

    if (!machine.docker.image) {
      log('destroy', containerId.slice(0, 16), 'success');
      return;
    }

    // If the machine had an intermediary image, clean it up.
    docker.removeImage({ host, image: machine.docker.image }, error => {
      if (error) {
        log('destroy', containerId.slice(0, 16), error);
        return;
      }

      log('destroy', containerId.slice(0, 16), 'success');
      delete machine.docker.image;
      db.save();
    });
  });
};

// Install or overwrite a configuration file in all the user's containers.
exports.deployConfigurationInAllContainers = function (user, file) {
  let count = 0;
  const userMachines = Object.values(user.machines)
    .filter(m => m.status == 'started')
    .reduce((acc, val) => acc.concat(val), []);

  return Promise.all(userMachines.map(machine => {
    const { container: containerId } = machine.docker;
    if (containerId.length < 16 || !/^[0-9a-f]+$/.test(containerId)) {
      log('[fail] invalid container id:', containerId);
      // Continue deploying anyway.
      return;
    }

    return exports.deployConfiguration(user, machine, file).then(() => {
      count++;
    });
  })).then(() => count);
};

// Install or overwrite a configuration file in a given user container.
exports.deployConfiguration = function (user, machine, file) {
  const { host, container: containerId } = machine.docker;
  return new Promise((resolve, reject) => {
    docker.copyIntoContainer({
      host,
      container: containerId,
      path: '/home/user/',
      files: {
        [file]: user.configurations[file],
      }
    }, error => {
      if (error) {
        reject(error);
        return;
      }

      // FIXME: Remove this workaround when the following Docker bug is fixed:
      // https://github.com/docker/docker/issues/21651.
      const command = `sudo chown user:user /home/user/${file}`;
      docker.execInContainer({
        host,
        container: containerId,
        command
      }, error => {
        if (error) {
          reject(error);
          return;
        }
        resolve();
      });
    });
  });
};

// Reset a user machine's list of authorized SSH public keys.
function deploySSHAuthorizedKeys (user, machine, callback) {
  let authorizedKeys = [ user.keys.cloud9 ];
  if ('github' in user.keys) {
    authorizedKeys = authorizedKeys.concat(user.keys.github.authorizedKeys);
  }

  const { host, container: containerId } = machine.docker;
  const parameters = {
    host,
    container: containerId,
    path: '/home/user/.ssh/',
    files: {
      authorized_keys: authorizedKeys.join('\n')
    }
  };

  docker.copyIntoContainer(parameters, error => {
    if (error) {
      callback(error);
      return;
    }

    // FIXME: Remove this workaround when the following Docker bug is fixed:
    // https://github.com/docker/docker/issues/21651.
    const command = 'sudo chown user:user /home/user/.ssh/authorized_keys';
    docker.execInContainer({ host, container: containerId, command }, error => {
      callback(error);
    });
  });
}

// Get an available user machine for a project, or create a new one.
function getOrCreateNewMachine (user, projectId) {
  const project = getProject(projectId);
  if (!project) {
    log(new Error('Unknown project: ' + projectId +
      ' (while getting a machine for ' + user.email + ')'));
    return null;
  }

  let machines = user.machines[projectId];
  if (!machines) {
    machines = user.machines[projectId] = [];
  }

  let machine = machines[machines.length - 1];
  if (!machine || machine.status !== 'new') {
    machine = {
      id: machines.length,
      name: project.name + ' #' + machines.length,
      status: 'new',
      docker: {
        host: '',
        container: '',
        ports: {},
        logs: ''
      },
      data: {}
    };
    machines.push(machine);
  }

  // Force the new machine to live on the project's (new) host.
  machine.docker.host = project.docker.host;

  // Make sure all the ports required by the project are provisioned.
  for (const projectPort in project.docker.ports) {
    if (!machine.docker.ports[projectPort]) {
      const { proxy } = project.docker.ports[projectPort];
      machine.docker.ports[projectPort] = {
        port: getPort(),
        proxy: proxy
      };
    }
  }

  db.save();

  return machine;
}

// Get a unique available port starting from 42000.
function getPort () {
  const ports = db.get('ports');
  const port = ports.next || 42000;

  ports.next = port + 1;
  db.save();

  return port;
}

// Find an existing project, or optionally create a new one for that ID.
function getProject (projectId, create) {
  const projects = db.get('projects');
  let project = projects[projectId];

  if (!project && create) {
    project = {
      id: projectId,
      name: '',
      icon: '',
      docker: {
        host: '',
        image: '',
        path: '',
        ports: {
          '22': {
            proxy: 'none'
          },
          '8088': {
            proxy: 'https'
          },
          '8089': {
            proxy: 'https'
          }
        },
        build: '',
        update: '',
        logs: ''
      },
      data: {}
    };
    projects[projectId] = project;
    db.save();
  }

  return project || null;
}
<commit_msg>Fix deploying configuration files to all containers.<commit_after>// Copyright © 2015 Jan Keromnes. All rights reserved.
// The following code is covered by the AGPL-3.0 license.

const jsonpatch = require('fast-json-patch');

const db = require('./db');
const docker = require('./docker');
const log = require('./log');
const metrics = require('./metrics');
const streams = require('./streams');

// List available user machines for each project, create when necessary.
exports.getAvailableMachines = function (user) {
  const machines = {};
  for (const projectId in db.get('projects')) {
    machines[projectId] = getOrCreateNewMachine(user, projectId);
  }

  return machines;
};

// Get an existing user machine with the given project and machine ID.
exports.getMachineById = function (user, projectId, machineId) {
  const machines = user.machines[projectId];
  if (!machines) {
    return null;
  }

  return machines[machineId] || null;
};

// Get an existing user machine with the given hostname and container ID.
exports.getMachineByContainer = function (user, hostname, containerId) {
  if (containerId.length < 16) {
    log(new Error('Container ID too short: ' + containerId +
      ' (while getting a machine for ' + user.email + ')'));
    return null;
  }

  for (const projectId in user.machines) {
    for (const machine of user.machines[projectId]) {
      if (machine.docker.host === hostname &&
        machine.docker.container.startsWith(containerId)) {
        return machine;
      }
    }
  }

  return null;
};

// Change the parameters of an existing project, or create a new project.
exports.setProject = function (parameters) {
  // Find the project with this ID, create it if necessary.
  const project = getProject(parameters.id, true);

  // Extract parameters that look like JSON Patch operations (RFC 6902).
  const operations = [];
  for (const name in parameters) {
    if (name[0] !== '/') {
      continue;
    }

    operations.push({
      op: 'add',
      path: name,
      value: parameters[name]
    });
  }

  // Apply the requested changes to the project.
  jsonpatch.applyPatch(project, operations);
  db.save();
};

// Rebuild the base image of a project. (Slow)
exports.rebuild = function (projectId, callback) {
  const project = getProject(projectId);
  if (!project) {
    callback(new Error('Unknown project: ' + projectId));
    return;
  }

  const { host, image, build: dockerfile } = project.docker;
  const tag = image + ':base';
  const time = Date.now();

  docker.buildImage({ host, tag, dockerfile }, (error, stream) => {
    if (error) {
      log ('rebuild', image, error);
      callback(new Error('Unable to rebuild project: ' + projectId));
      return;
    }

    log('rebuild', image, 'started');

    streams.set(project.docker, 'logs', stream);

    stream.on('error', err => {
      log('rebuild', image, err);
      error = err;
    });

    stream.on('end', () => {
      if (error) {
        log('rebuild', image, error);
        callback(new Error('Problem while rebuilding project: ' + projectId));
        return;
      }

      log('rebuild', image, 'success');
      const now = Date.now();
      metrics.set(project, 'updated', now);
      metrics.push(project, 'build-time', [ now, now - time ]);
      callback();
    });
  });
};

// Update the base image of a project.
exports.update = function (projectId, callback) {
  const project = getProject(projectId);
  if (!project) {
    callback(new Error('Unknown project: ' + projectId));
    return;
  }

  const { host, image, update: dockerfile } = project.docker;
  const tag = image + ':latest';
  const time = Date.now();

  docker.buildImage({ host, tag, dockerfile }, (error, stream) => {
    if (error) {
      log('update', image, error);
      callback(new Error('Unable to update project: ' + projectId));
      return;
    }

    log('update', image, 'started');

    streams.set(project.docker, 'logs', stream);

    stream.on('error', err => {
      log('update', image, err);
      error = err;
    });

    stream.on('end', () => {
      if (error) {
        log('update', image, error);
        callback(new Error('Problem while updating project: ' + projectId));
        return;
      }

      log('update', image, 'success');
      const now = Date.now();
      metrics.set(project, 'updated', now);
      metrics.push(project, 'update-time', [ now, now - time ]);
      callback();
    });
  });
};

// Instantiate a user machine for a project. (Fast!)
exports.spawn = function (user, projectId, callback) {
  const project = getProject(projectId);
  if (!project) {
    callback(new Error('Unknown project: ' + projectId));
    return;
  }

  const machine = getOrCreateNewMachine(user, projectId);

  // Keep track of the last project update this machine will be based on.
  metrics.set(machine, 'updated', project.data.updated);

  // Specify which ports should be exposed publicly or kept private.
  const ports = {};
  for (const projectPort in machine.docker.ports) {
    const { port, proxy } = machine.docker.ports[projectPort];
    ports[projectPort] = {
      hostPort: port,
      publish: proxy === 'none'
    };
  }

  const { host, image } = project.docker;
  const time = Date.now();

  log('spawn', image, 'started');
  docker.runContainer({ host, image, ports }, (error, container, logs) => {
    if (error) {
      log('spawn', image, error);
      machine.status = 'start-failed';
      db.save();
      callback(new Error('Unable to start machine for project: ' + projectId));
      return;
    }

    log('spawn', image, 'success', container.id.slice(0, 16));
    machine.docker.container = container.id;
    machine.docker.logs = String(logs);
    machine.status = 'started';

    const now = Date.now();
    metrics.push(project, 'spawn-time', [ now, now - time ]);
    db.save();

    // Quickly authorize the user's public SSH keys to access this container.
    deploySSHAuthorizedKeys(user, machine, error => {
      log('spawn-sshkeys', container.id.slice(0, 16), error || 'success');
      db.save();
    });

    // Install all non-empty user configuration files into this container.
    Object.keys(user.configurations).forEach(file => {
      if (!user.configurations[file]) {
        return;
      }
      exports.deployConfiguration(user, machine, file).then(() => {
        log('spawn-config', file, container.id.slice(0, 16), 'success');
      }).catch(error => {
        log('spawn-config', file, container.id.slice(0, 16), error);
      });
    });

    callback();
  });
};

// Destroy a given user machine and recycle its ports.
exports.destroy = function (user, projectId, machineId, callback) {
  const machines = user.machines[projectId];
  if (!machines) {
    callback(new Error('No machines for project: ' + projectId));
    return;
  }

  const machine = machines[machineId];
  if (!machine || machine.status === 'new') {
    // If there is no instantiated machine to destroy, do nothing.
    callback();
    return;
  }

  const { container: containerId, host } = machine.docker;
  if (!containerId) {
    // This machine has no associated container, just recycle it as is.
    machine.status = 'new';
    db.save();
    callback();
    return;
  }

  log('destroy', containerId.slice(0, 16), 'started');
  docker.removeContainer({ host, container: containerId }, error => {
    if (error) {
      log('destroy', containerId.slice(0, 16), error);
      callback(error);
      return;
    }

    // Recycle the machine's name and ports.
    machine.status = 'new';
    machine.docker.container = '';
    db.save();
    callback();

    if (!machine.docker.image) {
      log('destroy', containerId.slice(0, 16), 'success');
      return;
    }

    // If the machine had an intermediary image, clean it up.
    docker.removeImage({ host, image: machine.docker.image }, error => {
      if (error) {
        log('destroy', containerId.slice(0, 16), error);
        return;
      }

      log('destroy', containerId.slice(0, 16), 'success');
      delete machine.docker.image;
      db.save();
    });
  });
};

// Install or overwrite a configuration file in all the user's containers.
exports.deployConfigurationInAllContainers = function (user, file) {
  let count = 0;
  const machines = Object.values(user.machines)
    .reduce((machines, projectMachines) => machines.concat(projectMachines), [])
    .filter(machine => machine.status === 'started');

  return Promise.all(machines.map(machine => {
    const { container: containerId } = machine.docker;
    return exports.deployConfiguration(user, machine, file).then(() => {
      log('deploy-config', file, containerId.slice(0, 16), 'success');
      count++;
    }).catch(error => {
      log('[fail] deploy-config', file, containerId.slice(0, 16), error);
      // Continue deploying to other containers anyway.
    });
  })).then(() => count);
};

// Install or overwrite a configuration file in a given user container.
exports.deployConfiguration = function (user, machine, file) {
  const { host, container: containerId } = machine.docker;
  if (containerId.length < 16 || !/^[0-9a-f]+$/.test(containerId)) {
    return Promise.reject(new Error('Invalid container ID: ' + containerId));
  }

  return new Promise((resolve, reject) => {
    docker.copyIntoContainer({
      host,
      container: containerId,
      path: '/home/user/',
      files: {
        [file]: user.configurations[file],
      }
    }, error => {
      if (error) {
        reject(error);
        return;
      }

      // FIXME: Remove this workaround when the following Docker bug is fixed:
      // https://github.com/docker/docker/issues/21651.
      const command = `sudo chown user:user /home/user/${file}`;
      docker.execInContainer({
        host,
        container: containerId,
        command
      }, error => {
        if (error) {
          reject(error);
          return;
        }
        resolve();
      });
    });
  });
};

// Reset a user machine's list of authorized SSH public keys.
function deploySSHAuthorizedKeys (user, machine, callback) {
  let authorizedKeys = [ user.keys.cloud9 ];
  if ('github' in user.keys) {
    authorizedKeys = authorizedKeys.concat(user.keys.github.authorizedKeys);
  }

  const { host, container: containerId } = machine.docker;
  const parameters = {
    host,
    container: containerId,
    path: '/home/user/.ssh/',
    files: {
      authorized_keys: authorizedKeys.join('\n')
    }
  };

  docker.copyIntoContainer(parameters, error => {
    if (error) {
      callback(error);
      return;
    }

    // FIXME: Remove this workaround when the following Docker bug is fixed:
    // https://github.com/docker/docker/issues/21651.
    const command = 'sudo chown user:user /home/user/.ssh/authorized_keys';
    docker.execInContainer({ host, container: containerId, command }, error => {
      callback(error);
    });
  });
}

// Get an available user machine for a project, or create a new one.
function getOrCreateNewMachine (user, projectId) {
  const project = getProject(projectId);
  if (!project) {
    log(new Error('Unknown project: ' + projectId +
      ' (while getting a machine for ' + user.email + ')'));
    return null;
  }

  let machines = user.machines[projectId];
  if (!machines) {
    machines = user.machines[projectId] = [];
  }

  let machine = machines[machines.length - 1];
  if (!machine || machine.status !== 'new') {
    machine = {
      id: machines.length,
      name: project.name + ' #' + machines.length,
      status: 'new',
      docker: {
        host: '',
        container: '',
        ports: {},
        logs: ''
      },
      data: {}
    };
    machines.push(machine);
  }

  // Force the new machine to live on the project's (new) host.
  machine.docker.host = project.docker.host;

  // Make sure all the ports required by the project are provisioned.
  for (const projectPort in project.docker.ports) {
    if (!machine.docker.ports[projectPort]) {
      const { proxy } = project.docker.ports[projectPort];
      machine.docker.ports[projectPort] = {
        port: getPort(),
        proxy: proxy
      };
    }
  }

  db.save();

  return machine;
}

// Get a unique available port starting from 42000.
function getPort () {
  const ports = db.get('ports');
  const port = ports.next || 42000;

  ports.next = port + 1;
  db.save();

  return port;
}

// Find an existing project, or optionally create a new one for that ID.
function getProject (projectId, create) {
  const projects = db.get('projects');
  let project = projects[projectId];

  if (!project && create) {
    project = {
      id: projectId,
      name: '',
      icon: '',
      docker: {
        host: '',
        image: '',
        path: '',
        ports: {
          '22': {
            proxy: 'none'
          },
          '8088': {
            proxy: 'https'
          },
          '8089': {
            proxy: 'https'
          }
        },
        build: '',
        update: '',
        logs: ''
      },
      data: {}
    };
    projects[projectId] = project;
    db.save();
  }

  return project || null;
}
<|endoftext|>


<commit_before>// Copyright © 2015 Jan Keromnes. All rights reserved.
// The following code is covered by the AGPL-3.0 license.

const jsonpatch = require('fast-json-patch');

const db = require('./db');
const docker = require('./docker');
const log = require('./log');
const metrics = require('./metrics');
const streams = require('./streams');

// Get an existing user machine with the given project and machine ID.
exports.getMachineById = function (user, projectId, machineId) {
  const machines = user.machines[projectId];
  if (!machines) {
    return null;
  }

  return machines[machineId] || null;
};

// Get an existing user machine with the given hostname and container ID.
exports.getMachineByContainer = function (user, hostname, containerId) {
  if (containerId.length < 16) {
    log(new Error('Container ID too short: ' + containerId +
      ' (while getting a machine for ' + user._primaryEmail + ')'));
    return null;
  }

  for (const projectId in user.machines) {
    for (const machine of user.machines[projectId]) {
      if (machine.docker.host === hostname &&
        machine.docker.container.startsWith(containerId)) {
        return machine;
      }
    }
  }

  return null;
};

// Change the parameters of an existing project, or create a new project.
exports.setProject = function (parameters) {
  // Find the project with this ID, create it if necessary.
  const project = getProject(parameters.id, true);

  // Extract parameters that look like JSON Patch operations (RFC 6902).
  const operations = [];
  for (const name in parameters) {
    if (name[0] !== '/') {
      continue;
    }

    operations.push({
      op: 'add',
      path: name,
      value: parameters[name]
    });
  }

  // Apply the requested changes to the project.
  jsonpatch.applyPatch(project, operations);
  db.save();
};

// Download the project's latest Docker image.
exports.pull = function (projectId, callback) {
  const project = getProject(projectId);
  if (!project) {
    callback(new Error('Unknown project: ' + projectId));
    return;
  }

  const { host, image } = project.docker;
  const time = Date.now();

  docker.pullImage({ host, image }, (error, stream) => {
    if (error) {
      log('pull', image, error);
      callback(new Error('Could not pull project'));
      return;
    }

    log('pull', image, 'started');
    streams.set(project.docker, 'logs', stream);

    stream.on('error', err => {
      log('pull', image, err);
      error = err;
    });

    stream.on('end', () => {
      if (error) {
        callback(new Error('Problem while pulling project'));
        return;
      }

      // Inspect the pulled image to check its creation time.
      docker.inspectImage({ host, image }, (error, data) => {
        if (error) {
          log('pull-inspect', image, error);
          callback(new Error('Problem while inspecting image'));
          return;
        }

        const imageCreated = new Date(data.Created).getTime();
        if (imageCreated <= project.data.updated) {
          // If the pulled image is as old as, or older than the Docker image
          // we currently use in production, stop here.
          log('pull-tag', image, 'success (old image not tagged)');
          callback(null, { image, created: imageCreated });
          return;
        }

        // The pulled image is more recent than the one we currently use in
        // production. Let's use the newer image, by tagging it appropriately.
        const { _productionImage: tag } = project.docker;
        docker.tagImage({ host, image, tag }, error => {
          if (error) {
            log('pull-tag', image, tag, error);
            callback(new Error('Problem while tagging project'));
            return;
          }

          log('pull-tag', image, tag, 'success');
          const now = Date.now();
          metrics.set(project, 'updated', imageCreated);
          metrics.push(project, 'pull-time', [ now, now - time ]);
          callback(null, { image, created: imageCreated });
        });
      });
    });
  });
};

// Build an incremental Docker image update for a project.
exports.update = function (projectId, callback) {
  const project = getProject(projectId);
  if (!project) {
    callback(new Error('Unknown project: ' + projectId));
    return;
  }

  const { host, update: dockerfile, _productionImage: image } = project.docker;
  const time = Date.now();

  docker.buildImage({ host, tag: image, dockerfile }, (error, stream) => {
    if (error) {
      log('update', image, error);
      callback(new Error('Unable to update project: ' + projectId));
      return;
    }

    log('update', image, 'started');
    streams.set(project.docker, 'logs', stream);

    stream.on('error', err => {
      log('update', image, err);
      error = err;
    });

    stream.on('end', () => {
      if (error) {
        log('update', image, error);
        callback(new Error('Problem while updating project: ' + projectId));
        return;
      }

      log('update', image, 'success');
      const now = Date.now();
      metrics.set(project, 'updated', now);
      metrics.push(project, 'update-time', [ now, now - time ]);
      callback();
    });
  });
};

// Instantiate a user machine for a project. (Fast!)
exports.spawn = function (user, projectId, callback) {
  const project = getProject(projectId);
  if (!project) {
    callback(new Error('Unknown project: ' + projectId));
    return;
  }

  const machine = getOrCreateNewMachine(user, projectId);

  // Keep track of the last project update this machine will be based on.
  metrics.set(machine, 'updated', project.data.updated);

  // Specify which ports should be exposed publicly or kept private.
  const ports = {};
  for (const projectPort in machine.docker.ports) {
    const { port, proxy } = machine.docker.ports[projectPort];
    ports[projectPort] = {
      hostPort: port,
      publish: proxy === 'none'
    };
  }

  const { host, _productionImage: image } = project.docker;
  const time = Date.now();

  log('spawn', image, 'started');
  docker.runContainer({ host, image, ports }, (error, container, logs) => {
    if (error) {
      log('spawn', image, error);
      callback(new Error('Unable to start machine for project: ' + projectId));
      return;
    }

    log('spawn', image, 'success', container.id.slice(0, 16));
    machine.docker.container = container.id;
    machine.status = 'started';

    const now = Date.now();
    metrics.push(project, 'spawn-time', [ now, now - time ]);
    db.save();

    // Quickly authorize the user's public SSH keys to access this container.
    deploySSHAuthorizedKeys(user, machine, error => {
      log('spawn-sshkeys', container.id.slice(0, 16), error || 'success');
      db.save();
    });

    // Install all non-empty user configuration files into this container.
    Object.keys(user.configurations).forEach(file => {
      if (!user.configurations[file]) {
        return;
      }
      exports.deployConfiguration(user, machine, file).then(() => {
        log('spawn-config', file, container.id.slice(0, 16), 'success');
      }).catch(error => {
        log('spawn-config', file, container.id.slice(0, 16), error);
      });
    });

    callback(null, machine);
  });
};

// Destroy a given user machine and recycle its ports.
exports.destroy = function (user, projectId, machineId, callback) {
  const machines = user.machines[projectId];
  if (!machines) {
    callback(new Error('No machines for project: ' + projectId));
    return;
  }

  const machine = machines[machineId];
  if (!machine || machine.status === 'new') {
    // If there is no instantiated machine to destroy, do nothing.
    callback();
    return;
  }

  const { container: containerId, host } = machine.docker;
  if (!containerId) {
    // This machine has no associated container, just recycle it as is.
    machine.status = 'new';
    db.save();
    callback();
    return;
  }

  log('destroy', containerId.slice(0, 16), 'started');
  docker.removeContainer({ host, container: containerId }, error => {
    if (error) {
      log('destroy', containerId.slice(0, 16), error);
      callback(error);
      return;
    }

    // Recycle the machine's name and ports.
    machine.status = 'new';
    machine.docker.container = '';
    db.save();

    log('destroy', containerId.slice(0, 16), 'success');
    callback();
  });
};

// Install or overwrite a configuration file in all the user's containers.
exports.deployConfigurationInAllContainers = function (user, file) {
  let count = 0;
  // eslint-disable-next-line node/no-unsupported-features
  const machines = Object.values(user.machines)
    .reduce((machines, projectMachines) => machines.concat(projectMachines), [])
    .filter(machine => machine.status === 'started');

  return Promise.all(machines.map(machine => {
    const { container: containerId } = machine.docker;
    return exports.deployConfiguration(user, machine, file).then(() => {
      log('deploy-config', file, containerId.slice(0, 16), 'success');
      count++;
    }).catch(error => {
      log('[fail] deploy-config', file, containerId.slice(0, 16), error);
      // Continue deploying to other containers anyway.
    });
  })).then(() => count);
};

// Install or overwrite a configuration file in a given user container.
exports.deployConfiguration = function (user, machine, file) {
  const { host, container: containerId } = machine.docker;
  if (containerId.length < 16 || !/^[0-9a-f]+$/.test(containerId)) {
    return Promise.reject(new Error('Invalid container ID: ' + containerId));
  }

  return new Promise((resolve, reject) => {
    docker.copyIntoContainer({
      host,
      container: containerId,
      path: '/home/user/',
      files: {
        [file]: user.configurations[file],
      }
    }, error => {
      if (error) {
        reject(error);
        return;
      }

      // FIXME: Remove this workaround when the following Docker bug is fixed:
      // https://github.com/docker/docker/issues/21651.
      const command = `sudo chown user:user /home/user/${file}`;
      docker.execInContainer({
        host,
        container: containerId,
        command
      }, error => {
        if (error) {
          reject(error);
          return;
        }
        resolve();
      });
    });
  });
};

// Reset a user machine's list of authorized SSH public keys.
function deploySSHAuthorizedKeys (user, machine, callback) {
  let authorizedKeys = [ user.keys.cloud9 ];
  if ('github' in user.keys) {
    authorizedKeys = authorizedKeys.concat(user.keys.github.authorizedKeys);
  }

  const { host, container: containerId } = machine.docker;
  const parameters = {
    host,
    container: containerId,
    path: '/home/user/.ssh/',
    files: {
      authorized_keys: authorizedKeys.join('\n')
    }
  };

  docker.copyIntoContainer(parameters, error => {
    if (error) {
      callback(error);
      return;
    }

    // FIXME: Remove this workaround when the following Docker bug is fixed:
    // https://github.com/docker/docker/issues/21651.
    const command = 'sudo chown user:user /home/user/.ssh/authorized_keys';
    docker.execInContainer({ host, container: containerId, command }, error => {
      callback(error);
    });
  });
}

// Get an available user machine for a project, or create a new one.
function getOrCreateNewMachine (user, projectId) {
  const project = getProject(projectId);
  if (!project) {
    log(new Error('Unknown project: ' + projectId +
      ' (while getting a machine for ' + user._primaryEmail + ')'));
    return null;
  }

  let machines = user.machines[projectId];
  if (!machines) {
    machines = user.machines[projectId] = [];
  }

  let machine = machines[machines.length - 1];
  if (!machine || machine.status !== 'new') {
    machine = {
      id: machines.length,
      properties: {
        name: '',
      },
      status: 'new',
      docker: {
        host: '',
        container: '',
        ports: {},
      },
      data: {}
    };
    machines.push(machine);
  }

  // Reset any previous name this machine was given.
  machine.properties.name = project.name + ' #' + machines.length;

  // Force the new machine to live on the project's (new) host.
  machine.docker.host = project.docker.host;

  // Make sure all the ports required by the project are provisioned.
  for (const projectPort in project.docker.ports) {
    if (!machine.docker.ports[projectPort]) {
      const { proxy } = project.docker.ports[projectPort];
      machine.docker.ports[projectPort] = {
        port: getPort(),
        proxy: proxy
      };
    }
  }

  db.save();

  return machine;
}

// Get a unique available port starting from 42000.
function getPort () {
  const ports = db.get('ports');
  const port = ports.next || 42000;

  ports.next = port + 1;
  db.save();

  return port;
}

// Find an existing project, or optionally create a new one for that ID.
function getProject (projectId, create) {
  const projects = db.get('projects');
  let project = projects[projectId];

  if (!project && create) {
    project = {
      id: projectId,
      name: '',
      description: '',
      icon: '',
      docker: {
        host: '',
        image: '',
        path: '',
        ports: {
          '22': {
            proxy: 'none'
          },
          '8088': {
            proxy: 'https'
          },
          '8089': {
            proxy: 'https'
          }
        },
        update: '',
        logs: ''
      },
      data: {}
    };
    projects[projectId] = project;
    db.save();
  }

  if (!project) {
    return null;
  }

  // Get a hidden internal Docker image tagged with ':janitor-production'.
  if (!project.docker.hasOwnProperty('_productionImage')) {
    Object.defineProperty(project.docker, '_productionImage', {
      get () {
        return this.image.split(':')[0] + ':janitor-production';
      }
    });
  }

  return project;
}
<commit_msg>Pre-existing projects have a `description` (#201)<commit_after>// Copyright © 2015 Jan Keromnes. All rights reserved.
// The following code is covered by the AGPL-3.0 license.

const jsonpatch = require('fast-json-patch');

const db = require('./db');
const docker = require('./docker');
const log = require('./log');
const metrics = require('./metrics');
const streams = require('./streams');

// Get an existing user machine with the given project and machine ID.
exports.getMachineById = function (user, projectId, machineId) {
  const machines = user.machines[projectId];
  if (!machines) {
    return null;
  }

  return machines[machineId] || null;
};

// Get an existing user machine with the given hostname and container ID.
exports.getMachineByContainer = function (user, hostname, containerId) {
  if (containerId.length < 16) {
    log(new Error('Container ID too short: ' + containerId +
      ' (while getting a machine for ' + user._primaryEmail + ')'));
    return null;
  }

  for (const projectId in user.machines) {
    for (const machine of user.machines[projectId]) {
      if (machine.docker.host === hostname &&
        machine.docker.container.startsWith(containerId)) {
        return machine;
      }
    }
  }

  return null;
};

// Change the parameters of an existing project, or create a new project.
exports.setProject = function (parameters) {
  // Find the project with this ID, create it if necessary.
  const project = getProject(parameters.id, true);

  // Extract parameters that look like JSON Patch operations (RFC 6902).
  const operations = [];
  for (const name in parameters) {
    if (name[0] !== '/') {
      continue;
    }

    operations.push({
      op: 'add',
      path: name,
      value: parameters[name]
    });
  }

  // Apply the requested changes to the project.
  jsonpatch.applyPatch(project, operations);
  db.save();
};

// Download the project's latest Docker image.
exports.pull = function (projectId, callback) {
  const project = getProject(projectId);
  if (!project) {
    callback(new Error('Unknown project: ' + projectId));
    return;
  }

  const { host, image } = project.docker;
  const time = Date.now();

  docker.pullImage({ host, image }, (error, stream) => {
    if (error) {
      log('pull', image, error);
      callback(new Error('Could not pull project'));
      return;
    }

    log('pull', image, 'started');
    streams.set(project.docker, 'logs', stream);

    stream.on('error', err => {
      log('pull', image, err);
      error = err;
    });

    stream.on('end', () => {
      if (error) {
        callback(new Error('Problem while pulling project'));
        return;
      }

      // Inspect the pulled image to check its creation time.
      docker.inspectImage({ host, image }, (error, data) => {
        if (error) {
          log('pull-inspect', image, error);
          callback(new Error('Problem while inspecting image'));
          return;
        }

        const imageCreated = new Date(data.Created).getTime();
        if (imageCreated <= project.data.updated) {
          // If the pulled image is as old as, or older than the Docker image
          // we currently use in production, stop here.
          log('pull-tag', image, 'success (old image not tagged)');
          callback(null, { image, created: imageCreated });
          return;
        }

        // The pulled image is more recent than the one we currently use in
        // production. Let's use the newer image, by tagging it appropriately.
        const { _productionImage: tag } = project.docker;
        docker.tagImage({ host, image, tag }, error => {
          if (error) {
            log('pull-tag', image, tag, error);
            callback(new Error('Problem while tagging project'));
            return;
          }

          log('pull-tag', image, tag, 'success');
          const now = Date.now();
          metrics.set(project, 'updated', imageCreated);
          metrics.push(project, 'pull-time', [ now, now - time ]);
          callback(null, { image, created: imageCreated });
        });
      });
    });
  });
};

// Build an incremental Docker image update for a project.
exports.update = function (projectId, callback) {
  const project = getProject(projectId);
  if (!project) {
    callback(new Error('Unknown project: ' + projectId));
    return;
  }

  const { host, update: dockerfile, _productionImage: image } = project.docker;
  const time = Date.now();

  docker.buildImage({ host, tag: image, dockerfile }, (error, stream) => {
    if (error) {
      log('update', image, error);
      callback(new Error('Unable to update project: ' + projectId));
      return;
    }

    log('update', image, 'started');
    streams.set(project.docker, 'logs', stream);

    stream.on('error', err => {
      log('update', image, err);
      error = err;
    });

    stream.on('end', () => {
      if (error) {
        log('update', image, error);
        callback(new Error('Problem while updating project: ' + projectId));
        return;
      }

      log('update', image, 'success');
      const now = Date.now();
      metrics.set(project, 'updated', now);
      metrics.push(project, 'update-time', [ now, now - time ]);
      callback();
    });
  });
};

// Instantiate a user machine for a project. (Fast!)
exports.spawn = function (user, projectId, callback) {
  const project = getProject(projectId);
  if (!project) {
    callback(new Error('Unknown project: ' + projectId));
    return;
  }

  const machine = getOrCreateNewMachine(user, projectId);

  // Keep track of the last project update this machine will be based on.
  metrics.set(machine, 'updated', project.data.updated);

  // Specify which ports should be exposed publicly or kept private.
  const ports = {};
  for (const projectPort in machine.docker.ports) {
    const { port, proxy } = machine.docker.ports[projectPort];
    ports[projectPort] = {
      hostPort: port,
      publish: proxy === 'none'
    };
  }

  const { host, _productionImage: image } = project.docker;
  const time = Date.now();

  log('spawn', image, 'started');
  docker.runContainer({ host, image, ports }, (error, container, logs) => {
    if (error) {
      log('spawn', image, error);
      callback(new Error('Unable to start machine for project: ' + projectId));
      return;
    }

    log('spawn', image, 'success', container.id.slice(0, 16));
    machine.docker.container = container.id;
    machine.status = 'started';

    const now = Date.now();
    metrics.push(project, 'spawn-time', [ now, now - time ]);
    db.save();

    // Quickly authorize the user's public SSH keys to access this container.
    deploySSHAuthorizedKeys(user, machine, error => {
      log('spawn-sshkeys', container.id.slice(0, 16), error || 'success');
      db.save();
    });

    // Install all non-empty user configuration files into this container.
    Object.keys(user.configurations).forEach(file => {
      if (!user.configurations[file]) {
        return;
      }
      exports.deployConfiguration(user, machine, file).then(() => {
        log('spawn-config', file, container.id.slice(0, 16), 'success');
      }).catch(error => {
        log('spawn-config', file, container.id.slice(0, 16), error);
      });
    });

    callback(null, machine);
  });
};

// Destroy a given user machine and recycle its ports.
exports.destroy = function (user, projectId, machineId, callback) {
  const machines = user.machines[projectId];
  if (!machines) {
    callback(new Error('No machines for project: ' + projectId));
    return;
  }

  const machine = machines[machineId];
  if (!machine || machine.status === 'new') {
    // If there is no instantiated machine to destroy, do nothing.
    callback();
    return;
  }

  const { container: containerId, host } = machine.docker;
  if (!containerId) {
    // This machine has no associated container, just recycle it as is.
    machine.status = 'new';
    db.save();
    callback();
    return;
  }

  log('destroy', containerId.slice(0, 16), 'started');
  docker.removeContainer({ host, container: containerId }, error => {
    if (error) {
      log('destroy', containerId.slice(0, 16), error);
      callback(error);
      return;
    }

    // Recycle the machine's name and ports.
    machine.status = 'new';
    machine.docker.container = '';
    db.save();

    log('destroy', containerId.slice(0, 16), 'success');
    callback();
  });
};

// Install or overwrite a configuration file in all the user's containers.
exports.deployConfigurationInAllContainers = function (user, file) {
  let count = 0;
  // eslint-disable-next-line node/no-unsupported-features
  const machines = Object.values(user.machines)
    .reduce((machines, projectMachines) => machines.concat(projectMachines), [])
    .filter(machine => machine.status === 'started');

  return Promise.all(machines.map(machine => {
    const { container: containerId } = machine.docker;
    return exports.deployConfiguration(user, machine, file).then(() => {
      log('deploy-config', file, containerId.slice(0, 16), 'success');
      count++;
    }).catch(error => {
      log('[fail] deploy-config', file, containerId.slice(0, 16), error);
      // Continue deploying to other containers anyway.
    });
  })).then(() => count);
};

// Install or overwrite a configuration file in a given user container.
exports.deployConfiguration = function (user, machine, file) {
  const { host, container: containerId } = machine.docker;
  if (containerId.length < 16 || !/^[0-9a-f]+$/.test(containerId)) {
    return Promise.reject(new Error('Invalid container ID: ' + containerId));
  }

  return new Promise((resolve, reject) => {
    docker.copyIntoContainer({
      host,
      container: containerId,
      path: '/home/user/',
      files: {
        [file]: user.configurations[file],
      }
    }, error => {
      if (error) {
        reject(error);
        return;
      }

      // FIXME: Remove this workaround when the following Docker bug is fixed:
      // https://github.com/docker/docker/issues/21651.
      const command = `sudo chown user:user /home/user/${file}`;
      docker.execInContainer({
        host,
        container: containerId,
        command
      }, error => {
        if (error) {
          reject(error);
          return;
        }
        resolve();
      });
    });
  });
};

// Reset a user machine's list of authorized SSH public keys.
function deploySSHAuthorizedKeys (user, machine, callback) {
  let authorizedKeys = [ user.keys.cloud9 ];
  if ('github' in user.keys) {
    authorizedKeys = authorizedKeys.concat(user.keys.github.authorizedKeys);
  }

  const { host, container: containerId } = machine.docker;
  const parameters = {
    host,
    container: containerId,
    path: '/home/user/.ssh/',
    files: {
      authorized_keys: authorizedKeys.join('\n')
    }
  };

  docker.copyIntoContainer(parameters, error => {
    if (error) {
      callback(error);
      return;
    }

    // FIXME: Remove this workaround when the following Docker bug is fixed:
    // https://github.com/docker/docker/issues/21651.
    const command = 'sudo chown user:user /home/user/.ssh/authorized_keys';
    docker.execInContainer({ host, container: containerId, command }, error => {
      callback(error);
    });
  });
}

// Get an available user machine for a project, or create a new one.
function getOrCreateNewMachine (user, projectId) {
  const project = getProject(projectId);
  if (!project) {
    log(new Error('Unknown project: ' + projectId +
      ' (while getting a machine for ' + user._primaryEmail + ')'));
    return null;
  }

  let machines = user.machines[projectId];
  if (!machines) {
    machines = user.machines[projectId] = [];
  }

  let machine = machines[machines.length - 1];
  if (!machine || machine.status !== 'new') {
    machine = {
      id: machines.length,
      properties: {
        name: '',
      },
      status: 'new',
      docker: {
        host: '',
        container: '',
        ports: {},
      },
      data: {}
    };
    machines.push(machine);
  }

  // Reset any previous name this machine was given.
  machine.properties.name = project.name + ' #' + machines.length;

  // Force the new machine to live on the project's (new) host.
  machine.docker.host = project.docker.host;

  // Make sure all the ports required by the project are provisioned.
  for (const projectPort in project.docker.ports) {
    if (!machine.docker.ports[projectPort]) {
      const { proxy } = project.docker.ports[projectPort];
      machine.docker.ports[projectPort] = {
        port: getPort(),
        proxy: proxy
      };
    }
  }

  db.save();

  return machine;
}

// Get a unique available port starting from 42000.
function getPort () {
  const ports = db.get('ports');
  const port = ports.next || 42000;

  ports.next = port + 1;
  db.save();

  return port;
}

// Find an existing project, or optionally create a new one for that ID.
function getProject (projectId, create) {
  const projects = db.get('projects');
  let project = projects[projectId];

  if (!project && create) {
    project = {
      id: projectId,
      name: '',
      description: '',
      icon: '',
      docker: {
        host: '',
        image: '',
        path: '',
        ports: {
          '22': {
            proxy: 'none'
          },
          '8088': {
            proxy: 'https'
          },
          '8089': {
            proxy: 'https'
          }
        },
        update: '',
        logs: ''
      },
      data: {}
    };
    projects[projectId] = project;
    db.save();
  }

  if (!project) {
    return null;
  }

  // Temporary migration code: Previous projects didn't have a description.
  if (!project.description) {
    project.description = '';
  }

  // Get a hidden internal Docker image tagged with ':janitor-production'.
  if (!project.docker.hasOwnProperty('_productionImage')) {
    Object.defineProperty(project.docker, '_productionImage', {
      get () {
        return this.image.split(':')[0] + ':janitor-production';
      }
    });
  }

  return project;
}
<|endoftext|>


<commit_before>import React from 'react';
import Button from 'react-bootstrap/Button';

import ViewActivitiesButton from './view-activities-button.js';

/**
 * Returns the date range of the trip associated with the Trip document data
 * `tripObj`.
 *
 * Note: tripObj will always contain valid start_date and end_date fields.
 *
 * @param {firebase.firestore.DocumentData} tripObj Object containing the fields
 *    and values for a Trip document.
 * @return Date range of the trip (if it exists).
 */
export function getDateRange(tripObj) {
  const startDate = tripObj.start_date.toDate();
  const endDate = tripObj.end_date.toDate();
  return `${startDate.getMonth() + 1}/${startDate.getDate()}/`  +
      `${startDate.getFullYear()} - ${endDate.getMonth() + 1}/` +
      `${endDate.getDate()}/${endDate.getFullYear()}`;
}

/**
 * Component corresponding to the container containing an individual trip.
 *
 * Trip object fields are cleaned and vetted with firestore security rules
 * when trips are added and/or editted. Thus, no error checking is done here
 * on the 'display' side.
 *
 * @param {Object} props These are the props for this component:
 * - tripObj: JS object holding a Trip document fields and corresponding values.
 * - tripId: Document ID for the current Trip document.
 */
const Trip = (props) => {
  return (

{props.tripObj.name}

{props.tripObj.description}

{getDateRange(props.tripObj)}

{props.tripObj.destination}

{props.tripObj.collaborators.join(', ')}

      {/* TODO(Issue 15): Add edit trip page. */}
      <Button type='button' onClick={null} variant='primary'>Edit</Button>
      <ViewActivitiesButton tripId={props.tripId} />

  );
};

export default Trip;
<commit_msg>Add note in descrption of getDateRange for 0 indexed js dates.<commit_after>import React from 'react';
import Button from 'react-bootstrap/Button';

import ViewActivitiesButton from './view-activities-button.js';

/**
 * Returns the date range of the trip associated with the Trip document data
 * `tripObj`.
 *
 * Notes:
 *  - tripObj will always contain valid start_date and end_date fields.
 *  - When the Firestore Timestamps contained in `tripObj` converted to js
 *    dates, the months are 0 indexed rather than 1 indexed so they must be
 *    incremented in order for the month to be correct.
 *
 * @param {firebase.firestore.DocumentData} tripObj Object containing the fields
 *    and values for a Trip document.
 * @return Date range of the trip (if it exists).
 */
export function getDateRange(tripObj) {
  const startDate = tripObj.start_date.toDate();
  const endDate = tripObj.end_date.toDate();
  return `${startDate.getMonth() + 1}/${startDate.getDate()}/`  +
      `${startDate.getFullYear()} - ${endDate.getMonth() + 1}/` +
      `${endDate.getDate()}/${endDate.getFullYear()}`;
}

/**
 * Component corresponding to the container containing an individual trip.
 *
 * Trip object fields are cleaned and vetted with firestore security rules
 * when trips are added and/or editted. Thus, no error checking is done here
 * on the 'display' side.
 *
 * @param {Object} props These are the props for this component:
 * - tripObj: JS object holding a Trip document fields and corresponding values.
 * - tripId: Document ID for the current Trip document.
 */
const Trip = (props) => {
  return (

{props.tripObj.name}

{props.tripObj.description}

{getDateRange(props.tripObj)}

{props.tripObj.destination}

{props.tripObj.collaborators.join(', ')}

      {/* TODO(Issue 15): Add edit trip page. */}
      <Button type='button' onClick={null} variant='primary'>Edit</Button>
      <ViewActivitiesButton tripId={props.tripId} />

  );
};

export default Trip;
<|endoftext|>


<commit_before>"use strict"; // run code in ES5 strict mode

// This file ends on .web to trigger webpack to prefer this file over index.js
// @see https://github.com/sokra/modules-webpack#options

exports.is = require("metaclass/lib/helpers/is.js");
exports.isEach = require("metaclass/lib/helpers/is.js");
exports.Class = function (Class) {
    return Class;
};
// Used for logging output. You can override this for other logging options.
exports.stdout = function (txt) {
    console.log(txt);
};<commit_msg>- Fixed error in comment<commit_after>"use strict"; // run code in ES5 strict mode

// This file ends on .web to trigger webpack to prefer this file over main.js
// @see https://github.com/sokra/modules-webpack#options

exports.is = require("metaclass/lib/helpers/is.js");
exports.isEach = require("metaclass/lib/helpers/is.js");
exports.Class = function (Class) {
    return Class;
};
// Used for logging output. You can override this for other logging options.
exports.stdout = function (txt) {
    console.log(txt);
};<|endoftext|>


<commit_before>/*jshint node: true */
'use strict';

/*
* Name :  TableFlow.js
* Module : UnitTest
* Location : /frontend/test/unit/Model
*
* History :
* Version       Date        Programmer                  Description
* =================================================================================================
* 0.2.1         2015-06-26  Maria Giovanna Chinellato	Fix initializeData
*
* 0.2.0         2015-05-22  Maria Giovanna Chinellato	Modified general structure, some fixes
*
* 0.1.1         2015-05-22  Maria Giovanna Chinellato   Fix code
*
* 0.1.0			2015-05-22	Maria Giovanna Chinellato	Add test of Model::TableFlow.js
*
* 0.0.1			2015-05-21	Maria Giovanna Chinellato	Initial code
* =================================================================================================
*
*/

describe('TableFlow', function(){

	var TableFlowFactory;

	beforeEach(angular.mock.module('norris-nrti'));

	beforeEach(inject(function(_TableFlowFactory_){
		TableFlowFactory = _TableFlowFactory_;
	}));

	describe('Default constructor', function(){
		var TableFlow;

		beforeEach(function(){
			TableFlow = TableFlowFactory.build();
		});

		afterEach(function(){
			TableFlow = null;
		});

		it('default constructor create the flow in the correct way', function(){
			expect(TableFlow.getMaxItem()).toEqual(null);
		});

		it('default constructor create the flow with the correct name', function(){
			expect(TableFlow.getName()).toEqual(null);
		});

	});

	describe('Constructor', function(){

		var json = {
			'dataFormat' : 'int',
			'name' : 'flusso1',
			'maxItems' : 20
		};
		var TableFlow;

		beforeEach(function(){
			TableFlow = TableFlowFactory.build(json);
		});

		afterEach(function(){
			TableFlow = null;
		});

		it('constructor create the flow with the correct name', function(){
			expect(TableFlow.getName()).toEqual('flusso1');
		});
		it('constructor create the flow with the correct max item displayed', function(){
			expect(TableFlow.getMaxItem()).toEqual(20);
		});

	});

	/*deeescribe('split', function(){
		var res;
		var json = {};
		var json1 = json;
		json = json1;
		json = {
			'dataFormat' : 'int',
			'name' : 'flusso1',
			'maxItem' : '15'
		};
		var f = {
			'dataFormat' : 'int',
			'name' : 'flusso1'
		};
		var t = {
			'maxItem' : '15'
		};

		beforeEach(function(){
			res = TableFlow.prototype.test('split(json)');
		});

		it('json splitted in the correct way', function(){
			expect(res.flowJson).toEqual(f);
			expect(res.tableFlowJson).toEqual(t);
		});

	});*/

	describe('#updateParameters', function(){
		var json = {
			'name' : 'flusso2',
			'maxItems' : 15
		};
		var TableFlow;

		beforeEach(function(){
			TableFlow = TableFlowFactory.build();
			TableFlow.updateParameters(json);
		});

		afterEach(function(){
			TableFlow = null;
		});

		it('flow updated with the correct max item displayed', function(){
			expect(TableFlow.getMaxItem()).toEqual(15);
		});

	});

	describe('#initializeData', function(){
		var data = {
			records: [
				{
					'NorrisRecordID' : 'record1',
					'value' : [ 'Oggi' , 'ho', 'mangiato']
				},
				{
					'NorrisRecordID' : 'record2',
					'value' : [ 'una', 'buonissima', 'pizza']
				},
				{
					'NorrisRecordID' : 'record3',
					'value' : [ 'con', 'la', 'ricotta']
				}
			]
		};
		var TableFlow;

		beforeEach(function(){
			TableFlow = TableFlowFactory.build();
			TableFlow.initializeData(data,'top');
		});

		afterEach(function(){
			TableFlow = null;
		});

		it('data inizialize in the correct way', function(){
			expect(TableFlow.getData().length).toEqual(3);
		});

	});

	describe('#initializeData', function(){

		var data = {
			'records' : [
				{
					'NorrisRecordID' : 'record1',
					'value' : [ 12.5464546515, 11.15468766]
				},
				{
					'NorrisRecordID' : 'record2',
					'value' : [ 12.5464546515, 11.15468766]
				}
			]
		};

		var TableFlow;

		beforeEach(function(){
			TableFlow = TableFlowFactory.build({ 'maxItem' : 3 });
			TableFlow.initializeData(data, 'top');
		});

		afterEach(function(){
			TableFlow = null;
		});

		it('data initialized in the correct way', function(){
			expect(TableFlow.getData().length).toEqual(2);
		});

	});

	describe('#initializeData', function(){

		var data = {
			'records' : [
				{
					'NorrisRecordID' : 'record1',
					'value' : [ 12.5464546515, 11.15468766]
				},
				{
					'NorrisRecordID' : 'record2',
					'value' : [ 12.5464546515, 11.15468766]
				},
				{
					'NorrisRecordID' : 'record3',
					'value' : [ 12.5464546515, 11.15468766]
				}
			]
		};

		var data2 = {
			'records' : [
				{
					'NorrisRecordID' : 'record4',
					'value' : [ 12.5464546515, 11.15468766]
				}
			]
		};

		var TableFlow;

		beforeEach(function(){
			TableFlow = TableFlowFactory.build({ 'maxItem' : 1 });
			TableFlow.initializeData(data, 'top');
			TableFlow.initializeData(data2, 'bottom');
		});

		afterEach(function(){
			TableFlow = null;
		});

		it('data initialized in the correct way', function(){
			expect(TableFlow.getData().length).toEqual(1);
		});

	});

	describe('#emptyData', function(){

		var data = {
			'records' : [
				{
					'NorrisRecordID' : 'record1',
					'value' : [ 12.5464546515, 11.15468766]
				},
				{
					'NorrisRecordID' : 'record2',
					'value' : [ 12.5464546515, 11.15468766]
				},
				{
					'NorrisRecordID' : 'record3',
					'value' : [ 12.5464546515, 11.15468766]
				}
			]
		};

		var TableFlow;

		beforeEach(function(){
			TableFlow = TableFlowFactory.build();
			TableFlow.initializeData(data,'bottom');
		});

		afterEach(function(){
			TableFlow = null;
		});

		it('data empty in the correct way', function(){
			expect(TableFlow.getData().length).toEqual(3);
			TableFlow.emptyData();
			expect(TableFlow.getData().length).toEqual(0);
		});

	});

	describe('#inPlaceUpdate', function(){

		var data = {
			records: [
				{
					'NorrisRecordID' : 'record1',
					'value' : [ 'Oggi' , 'ho', 'mangiato']
				}
			]
		};

		var update = {
			'NorrisRecordID' : 'record1',
			'value' : [ 'una', 'buonissima', 'pizza']
		};
		var TableFlow;

		beforeEach(function(){
			TableFlow = TableFlowFactory.build();
			TableFlow.initializeData(data,'bottom');
			TableFlow.inPlaceUpdate(update);
		});

		afterEach(function(){
			TableFlow = null;
		});

		it('data updated in the correct way', function(){
			expect(TableFlow.getData()[0].value[0]).toEqual('una');
			expect(TableFlow.getData()[0].value[1]).toEqual('buonissima');
			expect(TableFlow.getData()[0].value[2]).toEqual('pizza');
		});

	});

	describe('#streamUpdate', function(){
		var data = {
			records: [
				{
					'NorrisRecordID' : 'record1',
					'value' : [ 1, 1]
				}
			]
		};
		var TableFlow;

		beforeEach(function(){
			TableFlow = TableFlowFactory.build();
			TableFlow.streamUpdate(data, 'bottom');
		});

		afterEach(function(){
			TableFlow = null;
		});

		it('streamUpdate call initializedData in the correct way', function(){
			expect(TableFlow.getData().length).toEqual(1);
		});
	});

	describe('#deleteData', function(){

		var data = {
			records: [
				{
					'NorrisRecordID' : 'record1',
					'value' : [ 14.5364864646, 11.5646546516]
				}
			]
		};

		var delData = {
			'NorrisRecordID' : 'record1',
			'value' : [ 14.5364864646, 11.5646546516]
		};

		var TableFlow;

		beforeEach(function(){
			TableFlow = TableFlowFactory.build();
			TableFlow.initializeData(data,'top');
			TableFlow.deleteData(delData);
		});

		afterEach(function(){
			TableFlow = null;
		});

		it('data removed in the correct way', function(){
			expect(TableFlow.getData().length).toEqual(0);
		});
	});

});<commit_msg>fix table flow spec<commit_after>/*jshint node: true */
'use strict';

/*
* Name :  TableFlow.js
* Module : UnitTest
* Location : /frontend/test/unit/Model
*
* History :
* Version       Date        Programmer                  Description
* =================================================================================================
* 0.2.1         2015-06-26  Maria Giovanna Chinellato	Fix initializeData
*
* 0.2.0         2015-05-22  Maria Giovanna Chinellato	Modified general structure, some fixes
*
* 0.1.1         2015-05-22  Maria Giovanna Chinellato   Fix code
*
* 0.1.0			2015-05-22	Maria Giovanna Chinellato	Add test of Model::TableFlow.js
*
* 0.0.1			2015-05-21	Maria Giovanna Chinellato	Initial code
* =================================================================================================
*
*/

describe('TableFlow', function(){

	var TableFlowFactory;

	beforeEach(angular.mock.module('norris-nrti'));

	beforeEach(inject(function(_TableFlowFactory_){
		TableFlowFactory = _TableFlowFactory_;
	}));

	describe('Default constructor', function(){
		var TableFlow;

		beforeEach(function(){
			TableFlow = TableFlowFactory.build();
		});

		afterEach(function(){
			TableFlow = null;
		});

		it('default constructor create the flow in the correct way', function(){
			expect(TableFlow.getMaxItem()).toEqual(null);
		});

		it('default constructor create the flow with the correct name', function(){
			expect(TableFlow.getName()).toEqual(null);
		});

	});

	describe('Constructor', function(){

		var json = {
			'dataFormat' : 'int',
			'name' : 'flusso1',
			'maxItems' : 20
		};
		var TableFlow;

		beforeEach(function(){
			TableFlow = TableFlowFactory.build(json);
		});

		afterEach(function(){
			TableFlow = null;
		});

		it('constructor create the flow with the correct name', function(){
			expect(TableFlow.getName()).toEqual('flusso1');
		});
		it('constructor create the flow with the correct max item displayed', function(){
			expect(TableFlow.getMaxItem()).toEqual(20);
		});

	});

	/*deeescribe('split', function(){
		var res;
		var json = {};
		var json1 = json;
		json = json1;
		json = {
			'dataFormat' : 'int',
			'name' : 'flusso1',
			'maxItem' : '15'
		};
		var f = {
			'dataFormat' : 'int',
			'name' : 'flusso1'
		};
		var t = {
			'maxItem' : '15'
		};

		beforeEach(function(){
			res = TableFlow.prototype.test('split(json)');
		});

		it('json splitted in the correct way', function(){
			expect(res.flowJson).toEqual(f);
			expect(res.tableFlowJson).toEqual(t);
		});

	});*/

	describe('#updateParameters', function(){
		var json = {
			'name' : 'flusso2',
			'maxItems' : 15
		};
		var TableFlow;

		beforeEach(function(){
			TableFlow = TableFlowFactory.build();
			TableFlow.updateParameters(json);
		});

		afterEach(function(){
			TableFlow = null;
		});

		it('flow updated with the correct max item displayed', function(){
			expect(TableFlow.getMaxItem()).toEqual(15);
		});

	});

	describe('#initializeData', function(){
		var data = {
			records: [
				{
					'NorrisRecordID' : 'record1',
					'value' : [ 'Oggi' , 'ho', 'mangiato']
				},
				{
					'NorrisRecordID' : 'record2',
					'value' : [ 'una', 'buonissima', 'pizza']
				},
				{
					'NorrisRecordID' : 'record3',
					'value' : [ 'con', 'la', 'ricotta']
				}
			]
		};
		var TableFlow;

		beforeEach(function(){
			TableFlow = TableFlowFactory.build();
			TableFlow.initializeData(data,'top');
		});

		afterEach(function(){
			TableFlow = null;
		});

		it('data inizialize in the correct way', function(){
			expect(TableFlow.getData().length).toEqual(3);
		});

	});

	describe('#initializeData', function(){

		var data = {
			'records' : [
				{
					'NorrisRecordID' : 'record1',
					'value' : [ 12.5464546515, 11.15468766]
				},
				{
					'NorrisRecordID' : 'record2',
					'value' : [ 12.5464546515, 11.15468766]
				}
			]
		};

		var TableFlow;

		beforeEach(function(){
			TableFlow = TableFlowFactory.build({ 'maxItems' : 3 });
			TableFlow.initializeData(data, 'top');
		});

		afterEach(function(){
			TableFlow = null;
		});

		it('data initialized in the correct way', function(){
			expect(TableFlow.getData().length).toEqual(2);
		});

	});

	describe('#initializeData', function(){

		var data = {
			'records' : [
				{
					'NorrisRecordID' : 'record1',
					'value' : [ 12.5464546515, 11.15468766]
				},
				{
					'NorrisRecordID' : 'record2',
					'value' : [ 12.5464546515, 11.15468766]
				},
				{
					'NorrisRecordID' : 'record3',
					'value' : [ 12.5464546515, 11.15468766]
				}
			]
		};

		var data2 = {
			'records' : [
				{
					'NorrisRecordID' : 'record4',
					'value' : [ 12.5464546515, 11.15468766]
				}
			]
		};

		var TableFlow;

		beforeEach(function(){
			TableFlow = TableFlowFactory.build({ 'maxItems' : 1 });
			TableFlow.initializeData(data, 'top');
			TableFlow.initializeData(data2, 'bottom');
		});

		afterEach(function(){
			TableFlow = null;
		});

		it('data initialized in the correct way', function(){
			expect(TableFlow.getData().length).toEqual(1);
		});

	});

	describe('#emptyData', function(){

		var data = {
			'records' : [
				{
					'NorrisRecordID' : 'record1',
					'value' : [ 12.5464546515, 11.15468766]
				},
				{
					'NorrisRecordID' : 'record2',
					'value' : [ 12.5464546515, 11.15468766]
				},
				{
					'NorrisRecordID' : 'record3',
					'value' : [ 12.5464546515, 11.15468766]
				}
			]
		};

		var TableFlow;

		beforeEach(function(){
			TableFlow = TableFlowFactory.build();
			TableFlow.initializeData(data,'bottom');
		});

		afterEach(function(){
			TableFlow = null;
		});

		it('data empty in the correct way', function(){
			expect(TableFlow.getData().length).toEqual(3);
			TableFlow.emptyData();
			expect(TableFlow.getData().length).toEqual(0);
		});

	});

	describe('#inPlaceUpdate', function(){

		var data = {
			records: [
				{
					'NorrisRecordID' : 'record1',
					'value' : [ 'Oggi' , 'ho', 'mangiato']
				}
			]
		};

		var update = {
			'NorrisRecordID' : 'record1',
			'value' : [ 'una', 'buonissima', 'pizza']
		};
		var TableFlow;

		beforeEach(function(){
			TableFlow = TableFlowFactory.build();
			TableFlow.initializeData(data,'bottom');
			TableFlow.inPlaceUpdate(update);
		});

		afterEach(function(){
			TableFlow = null;
		});

		it('data updated in the correct way', function(){
			expect(TableFlow.getData()[0].value[0]).toEqual('una');
			expect(TableFlow.getData()[0].value[1]).toEqual('buonissima');
			expect(TableFlow.getData()[0].value[2]).toEqual('pizza');
		});

	});

	describe('#streamUpdate', function(){
		var data = {
			records: [
				{
					'NorrisRecordID' : 'record1',
					'value' : [ 1, 1]
				}
			]
		};
		var TableFlow;

		beforeEach(function(){
			TableFlow = TableFlowFactory.build();
			TableFlow.streamUpdate(data, 'bottom');
		});

		afterEach(function(){
			TableFlow = null;
		});

		it('streamUpdate call initializedData in the correct way', function(){
			expect(TableFlow.getData().length).toEqual(1);
		});
	});

	describe('#deleteData', function(){

		var data = {
			records: [
				{
					'NorrisRecordID' : 'record1',
					'value' : [ 14.5364864646, 11.5646546516]
				}
			]
		};

		var delData = {
			'NorrisRecordID' : 'record1',
			'value' : [ 14.5364864646, 11.5646546516]
		};

		var TableFlow;

		beforeEach(function(){
			TableFlow = TableFlowFactory.build();
			TableFlow.initializeData(data,'top');
			TableFlow.deleteData(delData);
		});

		afterEach(function(){
			TableFlow = null;
		});

		it('data removed in the correct way', function(){
			expect(TableFlow.getData().length).toEqual(0);
		});
	});

});<|endoftext|>