<commit_before>// Copyright 2017-2018, University of Colorado Boulder

/* eslint-env node */
'use strict';

const addToRosetta = require( './addToRosetta' );
const addTranslator = require( './addTranslator' );
const ChipperVersion = require( '../common/ChipperVersion' );
const constants = require( './constants' );
const createTranslationsXML = require( './createTranslationsXML' );
const devDeploy = require( './devDeploy' );
const execute = require( '../common/execute' );
const fs = require( 'graceful-fs' ); //eslint-disable-line
const getLocales = require( './getLocales' );
const notifyServer = require( './notifyServer' );
const pullMaster = require( './pullMaster' );
const rsync = require( 'rsync' );
const winston = require( 'winston' );
const writeFile = require( './writeFile' );
const writePhetHtaccess = require( './writePhetHtaccess' );
const writePhetioHtaccess = require( './writePhetioHtaccess' );

const buildDir = './js/build-server/tmp';

/**
 * checkout master everywhere and abort build with err
 * @param {String|Error} err - error logged and sent via email
 */
const abortBuild = async err => {
  winston.log( 'error', 'BUILD ABORTED! ' + err );
  winston.log( 'info', 'build aborted: checking out master for every repo in case build shas are still checked out' );
  await execute( 'grunt', [ 'checkout-master-all' ], constants.PERENNIAL );
  return Promise.reject( 'Build aborted,' + err );
};

/**
 * Clean up after deploy. Checkout master for every repo and remove tmp dir.
 */
const afterDeploy = async buildDir => {
  try {
    await execute( 'grunt', [ 'checkout-master-all' ], constants.PERENNIAL );
    await execute( 'rm', [ '-rf', buildDir ], '.' );
  }
  catch( err ) {
    return abortBuild( err );
  }
};

/**
 * taskQueue ensures that only one build/deploy process will be happening at the same time.  The main build/deploy logic is here.
 *
 * @param {Object}
 * @property {JSON} repos
 * @property {String} api
 * @property {String} locales - comma separated list of locale codes
 * @property {String} simName - lower case simulation name used for creating files/directories
 * @property {String} version - sim version identifier string
 * @property {String} servers - deployment targets, subset of [ 'dev', 'production' ]
 * @property {String} brands - deployment brands
 * @property {String} email - used for sending notifications about success/failure
 * @property {String} translatorId - rosetta user id for adding translators to the website
 * @property {String} res - express response object
 * @property {winston} winston - logger
 */
async function taskWorker( { api, repos, locales, simName, version, email, brands, servers, userId } ) {
  try {
    //-------------------------------------------------------------------------------------
    // Parse and validate parameters
    //-------------------------------------------------------------------------------------
    if ( userId ) {
      winston.log( 'info', 'setting userId = ' + userId );
    }

    const simNameRegex = /^[a-z-]+$/;

    winston.debug( JSON.stringify( repos ) );

    // make sure the repos passed in validates
    for ( let key in repos ) {
      if ( repos.hasOwnProperty( key ) ) {
        winston.log( 'info', 'Validating repo: ' + key );

        // make sure all keys in repos object are valid sim names
        if ( !simNameRegex.test( key ) ) {
          return abortBuild( 'invalid simName in repos: ' + simName );
        }

        const value = repos[ key ];
        if ( key === 'comment' ) {
          if ( typeof value !== 'string' ) {
            return abortBuild( 'invalid comment in repos: should be a string' );
          }
        }
        else if ( value instanceof Object && value.hasOwnProperty( 'sha' ) ) {
          if ( !/^[a-f0-9]{40}$/.test( value.sha ) ) {
            return abortBuild( 'invalid sha in repos. key: ' + key + ' value: ' + value + ' sha: ' + value.sha );
          }
        }
        else {
          return abortBuild( 'invalid item in repos. key: ' + key + ' value: ' + value );
        }
      }
    }

    // validate simName
    if ( !simNameRegex.test( simName ) ) {
      return abortBuild( 'invalid simName ' + simName );
    }

    // Infer brand from version string and keep unstripped version for phet-io
    const originalVersion = version;
    if ( api === '1.0' ) {
      // validate version and strip suffixes since just the numbers are used in the directory name on dev and production servers
      const versionMatch = version.match( /^(\d+\.\d+\.\d+)(?:-.*)?$/ );
      if ( versionMatch && versionMatch.length === 2 ) {

        if ( servers.includes( 'dev' ) ) {
          // if deploying an rc version use the -rc.[number] suffix
          version = versionMatch[ 0 ];
        }
        else {
          // otherwise strip any suffix
          version = versionMatch[ 1 ];
        }
        winston.log( 'info', 'detecting version number: ' + version );
      }
      else {
        return abortBuild( 'invalid version number: ' + version );
      }
    }

    const simDir = '../' + simName;
    winston.log( 'info', 'building sim ' + simName );

    // Create the temporary build dir, removing the existing dir if it exists.
    await new Promise( ( resolve, reject ) => {
      fs.mkdir( buildDir, async err => {
        // If there is an error, try to remove the directory and contents and try again
        if ( err ) {
          try {
            await execute( 'rm', [ '-rf', buildDir ], '.' );
          }
          catch( e ) {
            reject( e );
          }
          fs.mkdir( buildDir, err => {
            if ( err ) {
              winston.error( 'Error creating new build dir: ' );
              winston.error( err );
              reject( err );
            }
            else {
              winston.info( 'successfully created build dir' );
              resolve();
            }
          } );

        }
        else {
          winston.info( 'successfully created build dir' );
          resolve();
        }
      } );
    } );

    await writeFile( buildDir + '/dependencies.json', JSON.stringify( repos ) );
    winston.log( 'info', 'wrote file ' + buildDir + '/dependencies.json' );

    await execute( 'git', [ 'pull' ], constants.PERENNIAL );
    await execute( 'npm', [ 'prune' ], constants.PERENNIAL );
    await execute( 'npm', [ 'update' ], constants.PERENNIAL );
    await execute( './perennial/bin/clone-missing-repos.sh', [], '..' );
    await pullMaster( repos );
    await execute( 'grunt', [ 'checkout-shas', '--buildServer=true', '--repo=' + simName ], constants.PERENNIAL );
    await execute( 'git', [ 'checkout', repos[ simName ].sha ], simDir );
    await execute( 'npm', [ 'prune' ], '../chipper' );
    await execute( 'npm', [ 'update' ], '../chipper' );
    await execute( 'npm', [ 'prune' ], simDir );
    await execute( 'npm', [ 'update' ], simDir );

    if ( api === '1.0' ) {
      locales = await getLocales( locales, simName );
    }

    const brandLocales = ( brands.indexOf( constants.PHET_BRAND ) >= 0 ) ? locales : 'en';
    winston.log( 'info', 'building for brands: ' + brands + ' version: ' + version );

    const chipperVersion = ChipperVersion.getFromRepository();
    winston.debug( 'Chipper version detected: ' + chipperVersion.toString() );

    if ( chipperVersion.major === 2 && chipperVersion.minor === 0 ) {
      await execute( 'grunt', [ '--allHTML', '--debugHTML', '--brands=' + brands.join( ',' ), '--locales=' + brandLocales ], simDir );
    }
    else if ( chipperVersion.major === 0 && chipperVersion.minor === 0 ) {
      const args = [ 'build-for-server', '--brand=' + brands[ 0 ], '--locales=' + brandLocales ];
      if ( brands[ 0 ] === constants.PHET_BRAND ) {
        args.push( '--allHTML' );
      }
      await execute( 'grunt', args, simDir );
    }
    else {
      return abortBuild( 'Unsupported chipper version' );
    }

    winston.debug( 'deploying to servers: ' + JSON.stringify( servers ) );

    if ( servers.indexOf( constants.DEV_SERVER ) >= 0 ) {
      winston.info( 'deploying to dev' );
      if ( brands.indexOf( constants.PHET_IO_BRAND ) >= 0 ) {
        const htaccessLocation = ( chipperVersion.major === 2 && chipperVersion.minor === 0 ) ?
                                 simDir + '/build/phet-io/wrappers/.htaccess' :
                                 simDir + '/build/wrappers/.htaccess';
        await writePhetioHtaccess( htaccessLocation, '/htdocs/physics/phet-io/config/.htpasswd' );
      }
      await devDeploy( simDir, simName, version, chipperVersion, brands );
    }

    if ( servers.indexOf( constants.PRODUCTION_SERVER ) >= 0 ) {
      winston.info( 'deploying to production' );
      let targetDir;
      // Loop over all brands
      for ( let i in brands ) {
        if ( brands.hasOwnProperty( i ) ) {
          const brand = brands[ i ];
          winston.info( 'deploying brand: ' + brand );

          // Pre-copy steps
          if ( brand === constants.PHET_BRAND ) {
            targetDir = constants.HTML_SIMS_DIRECTORY + simName + '/' + version + '/';

            if ( chipperVersion.major === 2 && chipperVersion.minor === 0 ) {
              // Remove _phet from all filenames in the phet directory
              const files = fs.readdirSync( simDir + '/build/phet' );
              for ( let i in files ) {
                if ( files.hasOwnProperty( i ) ) {
                  const filename = files[ i ];
                  if ( filename.indexOf( '_phet' ) >= 0 ) {
                    const newFilename = filename.replace( '_phet', '' );
                    await execute( 'mv', [ filename, newFilename ], simDir + '/build/phet' );
                  }
                }
              }
            }
          }
          else if ( brand === constants.PHET_IO_BRAND ) {
            targetDir = constants.PHETIO_SIMS_DIRECTORY + simName + '/' + originalVersion + '/';
          }

          // Copy steps
          await new Promise( ( resolve, reject ) => {
            fs.mkdir( targetDir, err => {
              if ( err ) {
                winston.info( 'Error on mkdir, probably due to the fact that the target already exists' );
              }
              resolve();
            } );
          } );
          let sourceDir = simDir + '/build';
          if ( chipperVersion.major === 2 && chipperVersion.minor === 0 ) {
            sourceDir += '/' + brand;
          }
          await new Promise( ( resolve, reject ) => {
            winston.debug( 'Copying recursive ' + sourceDir + ' to ' + targetDir );
            new rsync()
              .flags( 'razp' )
              .source( sourceDir + '/' )
              .destination( targetDir )
              .execute( ( err, code, cmd ) => {
                if ( err ) {
                  winston.debug( code );
                  winston.debug( cmd );
                  reject( err );
                }
                else { resolve(); }
              } );
          } );

          // Post-copy steps
          if ( brand === constants.PHET_BRAND ) {
            await writePhetHtaccess( simName, version );
            let simTitle = await createTranslationsXML( simName, version );
            await notifyServer( simName, email );
            await addToRosetta( simTitle, simName, email );

            // if this build request comes from rosetta it will have a userId field and only one locale
            const localesArray = typeof( locales ) === 'string' ? locales.split( ',' ) : locales;
            if ( userId && localesArray.length === 1 && localesArray[ 0 ] !== '*' ) {
              await addTranslator( localesArray[ 0 ], simName, userId );
            }
          }
          else if ( brand === constants.PHET_IO_BRAND ) {
            await writePhetioHtaccess( constants.PHETIO_SIMS_DIRECTORY + simName + '/' + originalVersion + '/wrappers/.htaccess', '/etc/httpd/conf/phet-io_pw', simName, originalVersion );
          }
        }
      }
    }
  }
  catch( err ) {
    return abortBuild( err );
  }
  return afterDeploy();
}

module.exports = ( task, taskCallback ) => {
  taskWorker( task )
    .then( () => {
        taskCallback();
      }
    ).catch( ( reason ) => {
    taskCallback( reason );
  } );
};<commit_msg>added logging to rsync<commit_after>// Copyright 2017-2018, University of Colorado Boulder

/* eslint-env node */
'use strict';

const addToRosetta = require( './addToRosetta' );
const addTranslator = require( './addTranslator' );
const ChipperVersion = require( '../common/ChipperVersion' );
const constants = require( './constants' );
const createTranslationsXML = require( './createTranslationsXML' );
const devDeploy = require( './devDeploy' );
const execute = require( '../common/execute' );
const fs = require( 'graceful-fs' ); //eslint-disable-line
const getLocales = require( './getLocales' );
const notifyServer = require( './notifyServer' );
const pullMaster = require( './pullMaster' );
const rsync = require( 'rsync' );
const winston = require( 'winston' );
const writeFile = require( './writeFile' );
const writePhetHtaccess = require( './writePhetHtaccess' );
const writePhetioHtaccess = require( './writePhetioHtaccess' );

const buildDir = './js/build-server/tmp';

/**
 * checkout master everywhere and abort build with err
 * @param {String|Error} err - error logged and sent via email
 */
const abortBuild = async err => {
  winston.log( 'error', 'BUILD ABORTED! ' + err );
  winston.log( 'info', 'build aborted: checking out master for every repo in case build shas are still checked out' );
  await execute( 'grunt', [ 'checkout-master-all' ], constants.PERENNIAL );
  return Promise.reject( 'Build aborted,' + err );
};

/**
 * Clean up after deploy. Checkout master for every repo and remove tmp dir.
 */
const afterDeploy = async buildDir => {
  try {
    await execute( 'grunt', [ 'checkout-master-all' ], constants.PERENNIAL );
    await execute( 'rm', [ '-rf', buildDir ], '.' );
  }
  catch( err ) {
    return abortBuild( err );
  }
};

/**
 * taskQueue ensures that only one build/deploy process will be happening at the same time.  The main build/deploy logic is here.
 *
 * @param {Object}
 * @property {JSON} repos
 * @property {String} api
 * @property {String} locales - comma separated list of locale codes
 * @property {String} simName - lower case simulation name used for creating files/directories
 * @property {String} version - sim version identifier string
 * @property {String} servers - deployment targets, subset of [ 'dev', 'production' ]
 * @property {String} brands - deployment brands
 * @property {String} email - used for sending notifications about success/failure
 * @property {String} translatorId - rosetta user id for adding translators to the website
 * @property {String} res - express response object
 * @property {winston} winston - logger
 */
async function taskWorker( { api, repos, locales, simName, version, email, brands, servers, userId } ) {
  try {
    //-------------------------------------------------------------------------------------
    // Parse and validate parameters
    //-------------------------------------------------------------------------------------
    if ( userId ) {
      winston.log( 'info', 'setting userId = ' + userId );
    }

    const simNameRegex = /^[a-z-]+$/;

    winston.debug( JSON.stringify( repos ) );

    // make sure the repos passed in validates
    for ( let key in repos ) {
      if ( repos.hasOwnProperty( key ) ) {
        winston.log( 'info', 'Validating repo: ' + key );

        // make sure all keys in repos object are valid sim names
        if ( !simNameRegex.test( key ) ) {
          return abortBuild( 'invalid simName in repos: ' + simName );
        }

        const value = repos[ key ];
        if ( key === 'comment' ) {
          if ( typeof value !== 'string' ) {
            return abortBuild( 'invalid comment in repos: should be a string' );
          }
        }
        else if ( value instanceof Object && value.hasOwnProperty( 'sha' ) ) {
          if ( !/^[a-f0-9]{40}$/.test( value.sha ) ) {
            return abortBuild( 'invalid sha in repos. key: ' + key + ' value: ' + value + ' sha: ' + value.sha );
          }
        }
        else {
          return abortBuild( 'invalid item in repos. key: ' + key + ' value: ' + value );
        }
      }
    }

    // validate simName
    if ( !simNameRegex.test( simName ) ) {
      return abortBuild( 'invalid simName ' + simName );
    }

    // Infer brand from version string and keep unstripped version for phet-io
    const originalVersion = version;
    if ( api === '1.0' ) {
      // validate version and strip suffixes since just the numbers are used in the directory name on dev and production servers
      const versionMatch = version.match( /^(\d+\.\d+\.\d+)(?:-.*)?$/ );
      if ( versionMatch && versionMatch.length === 2 ) {

        if ( servers.includes( 'dev' ) ) {
          // if deploying an rc version use the -rc.[number] suffix
          version = versionMatch[ 0 ];
        }
        else {
          // otherwise strip any suffix
          version = versionMatch[ 1 ];
        }
        winston.log( 'info', 'detecting version number: ' + version );
      }
      else {
        return abortBuild( 'invalid version number: ' + version );
      }
    }

    const simDir = '../' + simName;
    winston.log( 'info', 'building sim ' + simName );

    // Create the temporary build dir, removing the existing dir if it exists.
    await new Promise( ( resolve, reject ) => {
      fs.mkdir( buildDir, async err => {
        // If there is an error, try to remove the directory and contents and try again
        if ( err ) {
          try {
            await execute( 'rm', [ '-rf', buildDir ], '.' );
          }
          catch( e ) {
            reject( e );
          }
          fs.mkdir( buildDir, err => {
            if ( err ) {
              winston.error( 'Error creating new build dir: ' );
              winston.error( err );
              reject( err );
            }
            else {
              winston.info( 'successfully created build dir' );
              resolve();
            }
          } );

        }
        else {
          winston.info( 'successfully created build dir' );
          resolve();
        }
      } );
    } );

    await writeFile( buildDir + '/dependencies.json', JSON.stringify( repos ) );
    winston.log( 'info', 'wrote file ' + buildDir + '/dependencies.json' );

    await execute( 'git', [ 'pull' ], constants.PERENNIAL );
    await execute( 'npm', [ 'prune' ], constants.PERENNIAL );
    await execute( 'npm', [ 'update' ], constants.PERENNIAL );
    await execute( './perennial/bin/clone-missing-repos.sh', [], '..' );
    await pullMaster( repos );
    await execute( 'grunt', [ 'checkout-shas', '--buildServer=true', '--repo=' + simName ], constants.PERENNIAL );
    await execute( 'git', [ 'checkout', repos[ simName ].sha ], simDir );
    await execute( 'npm', [ 'prune' ], '../chipper' );
    await execute( 'npm', [ 'update' ], '../chipper' );
    await execute( 'npm', [ 'prune' ], simDir );
    await execute( 'npm', [ 'update' ], simDir );

    if ( api === '1.0' ) {
      locales = await getLocales( locales, simName );
    }

    const brandLocales = ( brands.indexOf( constants.PHET_BRAND ) >= 0 ) ? locales : 'en';
    winston.log( 'info', 'building for brands: ' + brands + ' version: ' + version );

    const chipperVersion = ChipperVersion.getFromRepository();
    winston.debug( 'Chipper version detected: ' + chipperVersion.toString() );

    if ( chipperVersion.major === 2 && chipperVersion.minor === 0 ) {
      await execute( 'grunt', [ '--allHTML', '--debugHTML', '--brands=' + brands.join( ',' ), '--locales=' + brandLocales ], simDir );
    }
    else if ( chipperVersion.major === 0 && chipperVersion.minor === 0 ) {
      const args = [ 'build-for-server', '--brand=' + brands[ 0 ], '--locales=' + brandLocales ];
      if ( brands[ 0 ] === constants.PHET_BRAND ) {
        args.push( '--allHTML' );
      }
      await execute( 'grunt', args, simDir );
    }
    else {
      return abortBuild( 'Unsupported chipper version' );
    }

    winston.debug( 'deploying to servers: ' + JSON.stringify( servers ) );

    if ( servers.indexOf( constants.DEV_SERVER ) >= 0 ) {
      winston.info( 'deploying to dev' );
      if ( brands.indexOf( constants.PHET_IO_BRAND ) >= 0 ) {
        const htaccessLocation = ( chipperVersion.major === 2 && chipperVersion.minor === 0 ) ?
                                 simDir + '/build/phet-io/wrappers/.htaccess' :
                                 simDir + '/build/wrappers/.htaccess';
        await writePhetioHtaccess( htaccessLocation, '/htdocs/physics/phet-io/config/.htpasswd' );
      }
      await devDeploy( simDir, simName, version, chipperVersion, brands );
    }

    if ( servers.indexOf( constants.PRODUCTION_SERVER ) >= 0 ) {
      winston.info( 'deploying to production' );
      let targetDir;
      // Loop over all brands
      for ( let i in brands ) {
        if ( brands.hasOwnProperty( i ) ) {
          const brand = brands[ i ];
          winston.info( 'deploying brand: ' + brand );

          // Pre-copy steps
          if ( brand === constants.PHET_BRAND ) {
            targetDir = constants.HTML_SIMS_DIRECTORY + simName + '/' + version + '/';

            if ( chipperVersion.major === 2 && chipperVersion.minor === 0 ) {
              // Remove _phet from all filenames in the phet directory
              const files = fs.readdirSync( simDir + '/build/phet' );
              for ( let i in files ) {
                if ( files.hasOwnProperty( i ) ) {
                  const filename = files[ i ];
                  if ( filename.indexOf( '_phet' ) >= 0 ) {
                    const newFilename = filename.replace( '_phet', '' );
                    await execute( 'mv', [ filename, newFilename ], simDir + '/build/phet' );
                  }
                }
              }
            }
          }
          else if ( brand === constants.PHET_IO_BRAND ) {
            targetDir = constants.PHETIO_SIMS_DIRECTORY + simName + '/' + originalVersion + '/';
          }

          // Copy steps
          await new Promise( ( resolve, reject ) => {
            fs.mkdir( targetDir, err => {
              if ( err ) {
                winston.info( 'Error on mkdir, probably due to the fact that the target already exists' );
              }
              resolve();
            } );
          } );
          let sourceDir = simDir + '/build';
          if ( chipperVersion.major === 2 && chipperVersion.minor === 0 ) {
            sourceDir += '/' + brand;
          }
          await new Promise( ( resolve, reject ) => {
            winston.debug( 'Copying recursive ' + sourceDir + ' to ' + targetDir );
            new rsync()
              .flags( 'razp' )
              .source( sourceDir + '/' )
              .destination( targetDir )
              .output( stdout => { winston.debug( JSON.stringify( stdout ) ); },
                stderr => { winston.error( JSON.stringify( stderr ) ); } )
              .execute( ( err, code, cmd ) => {
                if ( err ) {
                  winston.debug( code );
                  winston.debug( cmd );
                  reject( err );
                }
                else { resolve(); }
              } );
          } );

          // Post-copy steps
          if ( brand === constants.PHET_BRAND ) {
            await writePhetHtaccess( simName, version );
            let simTitle = await createTranslationsXML( simName, version );
            await notifyServer( simName, email );
            await addToRosetta( simTitle, simName, email );

            // if this build request comes from rosetta it will have a userId field and only one locale
            const localesArray = typeof( locales ) === 'string' ? locales.split( ',' ) : locales;
            if ( userId && localesArray.length === 1 && localesArray[ 0 ] !== '*' ) {
              await addTranslator( localesArray[ 0 ], simName, userId );
            }
          }
          else if ( brand === constants.PHET_IO_BRAND ) {
            await writePhetioHtaccess( constants.PHETIO_SIMS_DIRECTORY + simName + '/' + originalVersion + '/wrappers/.htaccess', '/etc/httpd/conf/phet-io_pw', simName, originalVersion );
          }
        }
      }
    }
  }
  catch( err ) {
    return abortBuild( err );
  }
  return afterDeploy();
}

module.exports = ( task, taskCallback ) => {
  taskWorker( task )
    .then( () => {
        taskCallback();
      }
    ).catch( ( reason ) => {
    taskCallback( reason );
  } );
};<|endoftext|>


<commit_before>'use strict';

const AWS = require('aws-sdk');

const S3 = new AWS.S3();
const DynamoDB = new AWS.DynamoDB.DocumentClient();

// make sure you add an environment name to lambda
const TABLE_NAME = process.env.TABLE_NAME;
const THROTTLING_ERRORS = [
    'ProvisionedThroughputExceededException',
    'ThrottlingException',
];

exports.handler = (event, context, callback) => {

    // create an array of promises to upload
    // a bunch of JSON records in S3 to DynamoDB
    const promises = event.Records.map((record) => {
        const params = {
            Bucket: record.s3.bucket.name,
            Key: record.s3.object.key,
        };
        // this is the meat and potato of the fuction:
        // read objects in S3, format the request, write it to DynamoDB
        return S3.getObject(params).promise()
            .then(data => buildRequests(data.Body))
            .then(items => processRequests(items));
    });
    // takes an array of promises and returns a Promise
    // that resolves when all the 'promises' have resolved
    Promise.all(promises)
        .then(() => callback(null, `Processed ${event.Records.length} file(s)`))
        .catch(err => callback(err));
};

// Take the json input and transform it (by adding StatusTime)
// to appropriate format for DynamoDB
function buildRequests(buf) {
    // create a new array of type "item" by parsing the input file
    const requestItems = buf.toString().trim().split('\n').map((json) => {
        const item = JSON.parse(json);
        item.StatusTime = (new Date(item.StatusTime)).getTime();

        // DynamoDB expects a JSON map with PutRequest
        //  - Perform a PutItem operation on the specified item.
        //   The item to be put is identified by an Item subelement:
        //   - Item - A map of attributes and their values.
        return {
            PutRequest: {
                Item: item,
            },
        };
    });
    // returns a Promise object that is resolved with the given value (requestItems)
    return Promise.resolve(requestItems);
}

// Take the correctly formatted items in the request,
// process them in batches of 25 at the time
function processRequests(requestItems) {
    const batches = [];

    while (requestItems.length > 0) {
        // process 25 records at the time
        console.log('processed records: ' + requestItems.length );
        batches.push(writeRecords(requestItems.splice(0, 25)));
    }
    // makes sure all are processed
    return Promise.all(batches);
}

// Write to DynamoDb the requestItems (in JSON)
// handle Throttling erros
function writeRecords(requestItems) {
    const params = {
        RequestItems: {
            [TABLE_NAME]: requestItems,
        },
    };

    return new Promise((resolve, reject) => {

        // DynamoDB is busy so we need to slow down and retry
        function retry(retryRequestItems) {
            const delay = ((Math.random() * (3 - 1)) + 1) * 1000;

            console.log(`Retrying ${retryRequestItems.length} reqs in ${Math.round(delay)}ms`);
            setTimeout(() => {
                writeRecords(retryRequestItems).then(resolve).catch(reject);
            }, delay);
        }

        // The logic for DynamoDB.batchWrite() is funky, make sure
        // you read the documentation:
        // http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_BatchWriteItem.html
        // because you may get errors for unprocessed data and for throttling
        DynamoDB.batchWrite(params, (err, data) => {
            if (data && data.UnprocessedItems[TABLE_NAME]) {
                retry(data.UnprocessedItems[TABLE_NAME]);
            } else if (err && THROTTLING_ERRORS.includes(err.name)) {
                retry(requestItems);
            } else if (err) {
                reject(err);
            } else {
                // all went well
                resolve();
            }
        });
    });
}
<commit_msg>changed one log output message<commit_after>'use strict';

const AWS = require('aws-sdk');

const S3 = new AWS.S3();
const DynamoDB = new AWS.DynamoDB.DocumentClient();

// make sure you add an environment name to lambda
const TABLE_NAME = process.env.TABLE_NAME;
const THROTTLING_ERRORS = [
    'ProvisionedThroughputExceededException',
    'ThrottlingException',
];

exports.handler = (event, context, callback) => {

    // create an array of promises to upload
    // a bunch of JSON records in S3 to DynamoDB
    const promises = event.Records.map((record) => {
        const params = {
            Bucket: record.s3.bucket.name,
            Key: record.s3.object.key,
        };
        // this is the meat and potato of the fuction:
        // read objects in S3, format the request, write it to DynamoDB
        return S3.getObject(params).promise()
            .then(data => buildRequests(data.Body))
            .then(items => processRequests(items));
    });
    // takes an array of promises and returns a Promise
    // that resolves when all the 'promises' have resolved
    Promise.all(promises)
        .then(() => callback(null, `Processed ${event.Records.length} file(s)`))
        .catch(err => callback(err));
};

// Take the json input and transform it (by adding StatusTime)
// to appropriate format for DynamoDB
function buildRequests(buf) {
    // create a new array of type "item" by parsing the input file
    const requestItems = buf.toString().trim().split('\n').map((json) => {

        // you should check you are parsing a correct json record
        // and handle any errors
        const item = JSON.parse(json);
        item.StatusTime = (new Date(item.StatusTime)).getTime();

        // DynamoDB expects a JSON map with PutRequest
        //  - Perform a PutItem operation on the specified item.
        //   The item to be put is identified by an Item subelement:
        //   - Item - A map of attributes and their values.
        return {
            PutRequest: {
                Item: item,
            },
        };
    });
    // returns a Promise object that is resolved with the given value (requestItems)
    return Promise.resolve(requestItems);
}

// Take the correctly formatted items in the request,
// process them in batches of 25 at the time
function processRequests(requestItems) {
    const batches = [];

    while (requestItems.length > 0) {
        // process 25 records at the time
        console.log('remaining records to be processed: ' + requestItems.length );
        batches.push(writeRecords(requestItems.splice(0, 25)));
    }
    // makes sure all are processed
    return Promise.all(batches);
}

// Write to DynamoDb the requestItems (in JSON)
// handle Throttling erros
function writeRecords(requestItems) {
    const params = {
        RequestItems: {
            [TABLE_NAME]: requestItems,
        },
    };

    return new Promise((resolve, reject) => {

        // DynamoDB is busy so we need to slow down and retry
        function retry(retryRequestItems) {
            const delay = ((Math.random() * (3 - 1)) + 1) * 1000;

            console.log(`Retrying ${retryRequestItems.length} reqs in ${Math.round(delay)}ms`);
            setTimeout(() => {
                writeRecords(retryRequestItems).then(resolve).catch(reject);
            }, delay);
        }

        // The logic for DynamoDB.batchWrite() is funky, make sure
        // you read the documentation:
        // http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_BatchWriteItem.html
        // because you may get errors for unprocessed data and for throttling
        DynamoDB.batchWrite(params, (err, data) => {
            if (data && data.UnprocessedItems[TABLE_NAME]) {
                retry(data.UnprocessedItems[TABLE_NAME]);
            } else if (err && THROTTLING_ERRORS.includes(err.name)) {
                retry(requestItems);
            } else if (err) {
                reject(err);
            } else {
                // all went well
                resolve();
            }
        });
    });
}
<|endoftext|>


<commit_before>/* @flow */
'use strict';

import type {RenderTarget, RenderOptions} from '../Renderer';
import type Color from '../Color';
import type {Path} from '../drawing/Path';
import type Size from '../drawing/Size';

import type {Font} from '../parsing/font';
import type {TextDecoration} from '../parsing/textDecoration';
import type {TextShadow} from '../parsing/textShadow';
import type {Matrix} from '../parsing/transform';

import type {Bounds} from '../Bounds';
import type {ImageElement} from '../ResourceLoader';
import type {LinearGradient, RadialGradient} from '../Gradient';
import type {TextBounds} from '../TextBounds';

import {PATH} from '../drawing/Path';
import {TEXT_DECORATION_LINE} from '../parsing/textDecoration';

const addColorStops = (
    gradient: LinearGradient | RadialGradient,
    canvasGradient: CanvasGradient
): void => {
    const maxStop = Math.max.apply(null, gradient.colorStops.map(colorStop => colorStop.stop));
    const f = 1 / Math.max(1, maxStop);
    gradient.colorStops.forEach(colorStop => {
        canvasGradient.addColorStop(f * colorStop.stop, colorStop.color.toString());
    });
};

export default class CanvasRenderer implements RenderTarget<HTMLCanvasElement> {
    canvas: HTMLCanvasElement;
    ctx: CanvasRenderingContext2D;
    options: RenderOptions;

    constructor(canvas: ?HTMLCanvasElement) {
        this.canvas = canvas ? canvas : document.createElement('canvas');
    }

    render(options: RenderOptions) {
        this.ctx = this.canvas.getContext('2d');
        this.options = options;
        this.canvas.width = Math.floor(options.width * options.scale);
        this.canvas.height = Math.floor(options.height * options.scale);
        this.canvas.style.width = `${options.width}px`;
        this.canvas.style.height = `${options.height}px`;

        this.ctx.scale(this.options.scale, this.options.scale);
        this.ctx.translate(-options.x, -options.y);
        this.ctx.textBaseline = 'bottom';
        options.logger.log(
            `Canvas renderer initialized (${options.width}x${options.height} at ${options.x},${options.y}) with scale ${this
                .options.scale}`
        );
    }

    clip(clipPaths: Array<Path>, callback: () => void) {
        if (clipPaths.length) {
            this.ctx.save();
            clipPaths.forEach(path => {
                this.path(path);
                this.ctx.clip();
            });
        }

        callback();

        if (clipPaths.length) {
            this.ctx.restore();
        }
    }

    drawImage(image: ImageElement, source: Bounds, destination: Bounds) {
        this.ctx.drawImage(
            image,
            source.left,
            source.top,
            source.width,
            source.height,
            destination.left,
            destination.top,
            destination.width,
            destination.height
        );
    }

    drawShape(path: Path, color: Color) {
        this.path(path);
        this.ctx.fillStyle = color.toString();
        this.ctx.fill();
    }

    fill(color: Color) {
        this.ctx.fillStyle = color.toString();
        this.ctx.fill();
    }

    getTarget(): Promise<HTMLCanvasElement> {
        return Promise.resolve(this.canvas);
    }

    path(path: Path) {
        this.ctx.beginPath();
        if (Array.isArray(path)) {
            path.forEach((point, index) => {
                const start = point.type === PATH.VECTOR ? point : point.start;
                if (index === 0) {
                    this.ctx.moveTo(start.x, start.y);
                } else {
                    this.ctx.lineTo(start.x, start.y);
                }

                if (point.type === PATH.BEZIER_CURVE) {
                    this.ctx.bezierCurveTo(
                        point.startControl.x,
                        point.startControl.y,
                        point.endControl.x,
                        point.endControl.y,
                        point.end.x,
                        point.end.y
                    );
                }
            });
        } else {
            this.ctx.arc(
                path.x + path.radius,
                path.y + path.radius,
                path.radius,
                0,
                Math.PI * 2,
                true
            );
        }

        this.ctx.closePath();
    }

    rectangle(x: number, y: number, width: number, height: number, color: Color) {
        this.ctx.fillStyle = color.toString();
        this.ctx.fillRect(x, y, width, height);
    }

    renderLinearGradient(bounds: Bounds, gradient: LinearGradient) {
        const linearGradient = this.ctx.createLinearGradient(
            bounds.left + gradient.direction.x1,
            bounds.top + gradient.direction.y1,
            bounds.left + gradient.direction.x0,
            bounds.top + gradient.direction.y0
        );

        addColorStops(gradient, linearGradient);
        this.ctx.fillStyle = linearGradient;
        this.ctx.fillRect(bounds.left, bounds.top, bounds.width, bounds.height);
    }

    renderRadialGradient(bounds: Bounds, gradient: RadialGradient) {
        const x = bounds.left + gradient.center.x;
        const y = bounds.top + gradient.center.y;

        const radialGradient = this.ctx.createRadialGradient(x, y, 0, x, y, gradient.radius.x);
        if (!radialGradient) {
            return;
        }

        addColorStops(gradient, radialGradient);
        this.ctx.fillStyle = radialGradient;

        if (gradient.radius.x !== gradient.radius.y) {
            // transforms for elliptical radial gradient
            const midX = bounds.left + 0.5 * bounds.width;
            const midY = bounds.top + 0.5 * bounds.height;
            const f = gradient.radius.y / gradient.radius.x;
            const invF = 1 / f;

            this.transform(midX, midY, [1, 0, 0, f, 0, 0], () =>
                this.ctx.fillRect(
                    bounds.left,
                    invF * (bounds.top - midY) + midY,
                    bounds.width,
                    bounds.height * invF
                )
            );
        } else {
            this.ctx.fillRect(bounds.left, bounds.top, bounds.width, bounds.height);
        }
    }

    renderRepeat(
        path: Path,
        image: ImageElement,
        imageSize: Size,
        offsetX: number,
        offsetY: number
    ) {
        this.path(path);
        this.ctx.fillStyle = this.ctx.createPattern(this.resizeImage(image, imageSize), 'repeat');
        this.ctx.translate(offsetX, offsetY);
        this.ctx.fill();
        this.ctx.translate(-offsetX, -offsetY);
    }

    renderTextNode(
        textBounds: Array<TextBounds>,
        color: Color,
        font: Font,
        textDecoration: TextDecoration | null,
        textShadows: Array<TextShadow> | null
    ) {
        this.ctx.font = [
            font.fontStyle,
            font.fontVariant,
            font.fontWeight,
            font.fontSize,
            font.fontFamily
        ].join(' ');

        textBounds.forEach(text => {
            this.ctx.fillStyle = color.toString();
            if (textShadows && text.text.trim().length) {
                textShadows.slice(0).reverse().forEach(textShadow => {
                    this.ctx.shadowColor = textShadow.color.toString();
                    this.ctx.shadowOffsetX = textShadow.offsetX * this.options.scale;
                    this.ctx.shadowOffsetY = textShadow.offsetY * this.options.scale;
                    this.ctx.shadowBlur = textShadow.blur;

                    this.ctx.fillText(
                        text.text,
                        text.bounds.left,
                        text.bounds.top + text.bounds.height
                    );
                });
            } else {
                this.ctx.fillText(
                    text.text,
                    text.bounds.left,
                    text.bounds.top + text.bounds.height
                );
            }

            if (textDecoration !== null) {
                const textDecorationColor = textDecoration.textDecorationColor || color;
                textDecoration.textDecorationLine.forEach(textDecorationLine => {
                    switch (textDecorationLine) {
                        case TEXT_DECORATION_LINE.UNDERLINE:
                            // Draws a line at the baseline of the font
                            // TODO As some browsers display the line as more than 1px if the font-size is big,
                            // need to take that into account both in position and size
                            const {baseline} = this.options.fontMetrics.getMetrics(font);
                            this.rectangle(
                                text.bounds.left,
                                Math.round(text.bounds.top + baseline),
                                text.bounds.width,
                                1,
                                textDecorationColor
                            );
                            break;
                        case TEXT_DECORATION_LINE.OVERLINE:
                            this.rectangle(
                                text.bounds.left,
                                Math.round(text.bounds.top),
                                text.bounds.width,
                                1,
                                textDecorationColor
                            );
                            break;
                        case TEXT_DECORATION_LINE.LINE_THROUGH:
                            // TODO try and find exact position for line-through
                            const {middle} = this.options.fontMetrics.getMetrics(font);
                            this.rectangle(
                                text.bounds.left,
                                Math.ceil(text.bounds.top + middle),
                                text.bounds.width,
                                1,
                                textDecorationColor
                            );
                            break;
                    }
                });
            }
        });
    }

    resizeImage(image: ImageElement, size: Size): ImageElement {
        if (image.width === size.width && image.height === size.height) {
            return image;
        }

        const canvas = this.canvas.ownerDocument.createElement('canvas');
        canvas.width = size.width;
        canvas.height = size.height;
        const ctx = canvas.getContext('2d');
        ctx.drawImage(image, 0, 0, image.width, image.height, 0, 0, size.width, size.height);
        return canvas;
    }

    setOpacity(opacity: number) {
        this.ctx.globalAlpha = opacity;
    }

    transform(offsetX: number, offsetY: number, matrix: Matrix, callback: () => void) {
        this.ctx.save();
        this.ctx.translate(offsetX, offsetY);
        this.ctx.transform(matrix[0], matrix[1], matrix[2], matrix[3], matrix[4], matrix[5]);
        this.ctx.translate(-offsetX, -offsetY);

        callback();

        this.ctx.restore();
    }
}
<commit_msg>Reset canvas transform on finish (Fix #1494)<commit_after>/* @flow */
'use strict';

import type {RenderTarget, RenderOptions} from '../Renderer';
import type Color from '../Color';
import type {Path} from '../drawing/Path';
import type Size from '../drawing/Size';

import type {Font} from '../parsing/font';
import type {TextDecoration} from '../parsing/textDecoration';
import type {TextShadow} from '../parsing/textShadow';
import type {Matrix} from '../parsing/transform';

import type {Bounds} from '../Bounds';
import type {ImageElement} from '../ResourceLoader';
import type {LinearGradient, RadialGradient} from '../Gradient';
import type {TextBounds} from '../TextBounds';

import {PATH} from '../drawing/Path';
import {TEXT_DECORATION_LINE} from '../parsing/textDecoration';

const addColorStops = (
    gradient: LinearGradient | RadialGradient,
    canvasGradient: CanvasGradient
): void => {
    const maxStop = Math.max.apply(null, gradient.colorStops.map(colorStop => colorStop.stop));
    const f = 1 / Math.max(1, maxStop);
    gradient.colorStops.forEach(colorStop => {
        canvasGradient.addColorStop(f * colorStop.stop, colorStop.color.toString());
    });
};

export default class CanvasRenderer implements RenderTarget<HTMLCanvasElement> {
    canvas: HTMLCanvasElement;
    ctx: CanvasRenderingContext2D;
    options: RenderOptions;

    constructor(canvas: ?HTMLCanvasElement) {
        this.canvas = canvas ? canvas : document.createElement('canvas');
    }

    render(options: RenderOptions) {
        this.ctx = this.canvas.getContext('2d');
        this.options = options;
        this.canvas.width = Math.floor(options.width * options.scale);
        this.canvas.height = Math.floor(options.height * options.scale);
        this.canvas.style.width = `${options.width}px`;
        this.canvas.style.height = `${options.height}px`;

        this.ctx.scale(this.options.scale, this.options.scale);
        this.ctx.translate(-options.x, -options.y);
        this.ctx.textBaseline = 'bottom';
        options.logger.log(
            `Canvas renderer initialized (${options.width}x${options.height} at ${options.x},${options.y}) with scale ${this
                .options.scale}`
        );
    }

    clip(clipPaths: Array<Path>, callback: () => void) {
        if (clipPaths.length) {
            this.ctx.save();
            clipPaths.forEach(path => {
                this.path(path);
                this.ctx.clip();
            });
        }

        callback();

        if (clipPaths.length) {
            this.ctx.restore();
        }
    }

    drawImage(image: ImageElement, source: Bounds, destination: Bounds) {
        this.ctx.drawImage(
            image,
            source.left,
            source.top,
            source.width,
            source.height,
            destination.left,
            destination.top,
            destination.width,
            destination.height
        );
    }

    drawShape(path: Path, color: Color) {
        this.path(path);
        this.ctx.fillStyle = color.toString();
        this.ctx.fill();
    }

    fill(color: Color) {
        this.ctx.fillStyle = color.toString();
        this.ctx.fill();
    }

    getTarget(): Promise<HTMLCanvasElement> {
        this.canvas.getContext('2d').setTransform(1, 0, 0, 1, 0, 0);
        return Promise.resolve(this.canvas);
    }

    path(path: Path) {
        this.ctx.beginPath();
        if (Array.isArray(path)) {
            path.forEach((point, index) => {
                const start = point.type === PATH.VECTOR ? point : point.start;
                if (index === 0) {
                    this.ctx.moveTo(start.x, start.y);
                } else {
                    this.ctx.lineTo(start.x, start.y);
                }

                if (point.type === PATH.BEZIER_CURVE) {
                    this.ctx.bezierCurveTo(
                        point.startControl.x,
                        point.startControl.y,
                        point.endControl.x,
                        point.endControl.y,
                        point.end.x,
                        point.end.y
                    );
                }
            });
        } else {
            this.ctx.arc(
                path.x + path.radius,
                path.y + path.radius,
                path.radius,
                0,
                Math.PI * 2,
                true
            );
        }

        this.ctx.closePath();
    }

    rectangle(x: number, y: number, width: number, height: number, color: Color) {
        this.ctx.fillStyle = color.toString();
        this.ctx.fillRect(x, y, width, height);
    }

    renderLinearGradient(bounds: Bounds, gradient: LinearGradient) {
        const linearGradient = this.ctx.createLinearGradient(
            bounds.left + gradient.direction.x1,
            bounds.top + gradient.direction.y1,
            bounds.left + gradient.direction.x0,
            bounds.top + gradient.direction.y0
        );

        addColorStops(gradient, linearGradient);
        this.ctx.fillStyle = linearGradient;
        this.ctx.fillRect(bounds.left, bounds.top, bounds.width, bounds.height);
    }

    renderRadialGradient(bounds: Bounds, gradient: RadialGradient) {
        const x = bounds.left + gradient.center.x;
        const y = bounds.top + gradient.center.y;

        const radialGradient = this.ctx.createRadialGradient(x, y, 0, x, y, gradient.radius.x);
        if (!radialGradient) {
            return;
        }

        addColorStops(gradient, radialGradient);
        this.ctx.fillStyle = radialGradient;

        if (gradient.radius.x !== gradient.radius.y) {
            // transforms for elliptical radial gradient
            const midX = bounds.left + 0.5 * bounds.width;
            const midY = bounds.top + 0.5 * bounds.height;
            const f = gradient.radius.y / gradient.radius.x;
            const invF = 1 / f;

            this.transform(midX, midY, [1, 0, 0, f, 0, 0], () =>
                this.ctx.fillRect(
                    bounds.left,
                    invF * (bounds.top - midY) + midY,
                    bounds.width,
                    bounds.height * invF
                )
            );
        } else {
            this.ctx.fillRect(bounds.left, bounds.top, bounds.width, bounds.height);
        }
    }

    renderRepeat(
        path: Path,
        image: ImageElement,
        imageSize: Size,
        offsetX: number,
        offsetY: number
    ) {
        this.path(path);
        this.ctx.fillStyle = this.ctx.createPattern(this.resizeImage(image, imageSize), 'repeat');
        this.ctx.translate(offsetX, offsetY);
        this.ctx.fill();
        this.ctx.translate(-offsetX, -offsetY);
    }

    renderTextNode(
        textBounds: Array<TextBounds>,
        color: Color,
        font: Font,
        textDecoration: TextDecoration | null,
        textShadows: Array<TextShadow> | null
    ) {
        this.ctx.font = [
            font.fontStyle,
            font.fontVariant,
            font.fontWeight,
            font.fontSize,
            font.fontFamily
        ].join(' ');

        textBounds.forEach(text => {
            this.ctx.fillStyle = color.toString();
            if (textShadows && text.text.trim().length) {
                textShadows.slice(0).reverse().forEach(textShadow => {
                    this.ctx.shadowColor = textShadow.color.toString();
                    this.ctx.shadowOffsetX = textShadow.offsetX * this.options.scale;
                    this.ctx.shadowOffsetY = textShadow.offsetY * this.options.scale;
                    this.ctx.shadowBlur = textShadow.blur;

                    this.ctx.fillText(
                        text.text,
                        text.bounds.left,
                        text.bounds.top + text.bounds.height
                    );
                });
            } else {
                this.ctx.fillText(
                    text.text,
                    text.bounds.left,
                    text.bounds.top + text.bounds.height
                );
            }

            if (textDecoration !== null) {
                const textDecorationColor = textDecoration.textDecorationColor || color;
                textDecoration.textDecorationLine.forEach(textDecorationLine => {
                    switch (textDecorationLine) {
                        case TEXT_DECORATION_LINE.UNDERLINE:
                            // Draws a line at the baseline of the font
                            // TODO As some browsers display the line as more than 1px if the font-size is big,
                            // need to take that into account both in position and size
                            const {baseline} = this.options.fontMetrics.getMetrics(font);
                            this.rectangle(
                                text.bounds.left,
                                Math.round(text.bounds.top + baseline),
                                text.bounds.width,
                                1,
                                textDecorationColor
                            );
                            break;
                        case TEXT_DECORATION_LINE.OVERLINE:
                            this.rectangle(
                                text.bounds.left,
                                Math.round(text.bounds.top),
                                text.bounds.width,
                                1,
                                textDecorationColor
                            );
                            break;
                        case TEXT_DECORATION_LINE.LINE_THROUGH:
                            // TODO try and find exact position for line-through
                            const {middle} = this.options.fontMetrics.getMetrics(font);
                            this.rectangle(
                                text.bounds.left,
                                Math.ceil(text.bounds.top + middle),
                                text.bounds.width,
                                1,
                                textDecorationColor
                            );
                            break;
                    }
                });
            }
        });
    }

    resizeImage(image: ImageElement, size: Size): ImageElement {
        if (image.width === size.width && image.height === size.height) {
            return image;
        }

        const canvas = this.canvas.ownerDocument.createElement('canvas');
        canvas.width = size.width;
        canvas.height = size.height;
        const ctx = canvas.getContext('2d');
        ctx.drawImage(image, 0, 0, image.width, image.height, 0, 0, size.width, size.height);
        return canvas;
    }

    setOpacity(opacity: number) {
        this.ctx.globalAlpha = opacity;
    }

    transform(offsetX: number, offsetY: number, matrix: Matrix, callback: () => void) {
        this.ctx.save();
        this.ctx.translate(offsetX, offsetY);
        this.ctx.transform(matrix[0], matrix[1], matrix[2], matrix[3], matrix[4], matrix[5]);
        this.ctx.translate(-offsetX, -offsetY);

        callback();

        this.ctx.restore();
    }
}
<|endoftext|>


<commit_before>'use strict';

import _ from 'lodash';
import path from 'path';
import fs from 'fs';

import config from '../../config/environment';

import Auth from '../../auth/auth.service';
import resizeAndMove from '../../components/resize-image';
import {actions as Actions} from '../../components/actions';

function handleError(req, res) {
  return function(statusCode, err) {
    statusCode = statusCode || 500;

    if (err) {
      req.log.error(err);
      res.status(statusCode).send(err);
    } else {
      res.sendStatus(statusCode);
    }
  };
}

function handleStatusError(req) {
  return new Promise(function(resolve, reject) {
    if (Auth.meetsRequirements(req.user.role, 'adminMdph')) {
      return resolve();
    }

    if (req.request.status === 'en_cours' || req.request.status === 'en_attente_usager') {
      return resolve();
    }

    reject(403);
  });
}

function respondWithResult(res, statusCode) {
  statusCode = statusCode || 200;
  return function(entity) {
    if (entity) {
      res.status(statusCode).json(entity);
    } else {
      res.sendStatus(statusCode);
    }

    return null;
  };
}

function removeFileFromFS(path) {
  if (path) {
    fs.exists(path, function(exists) {
      if (exists) {
        fs.unlink(path, () => {
          return Promise.resolve();
        });
      }
    });
  }

  return Promise.resolve();
}

function handleDeleteFile(req) {
  const file = req.request.documents.id(req.params.fileId);

  if (!file) {
    throw(304);
  }

  return removeFileFromFS(file.path)
    .then(() => {
      return file.remove({}, () => {
        req.request.saveActionLog(Actions.DOCUMENT_REMOVED, req.user, req.log, {document: file});
        return req.request.save();
      });
    });
}

function processDocument(file, fileData, done) {
  if (typeof file === 'undefined') {
    return done({status: 304});
  }

  var supportedFileTypes = ['application/pdf', 'image/jpeg', 'image/png'];
  if (supportedFileTypes.indexOf(file.mimetype) < 0) {
    return done({status: 403});
  }

  resizeAndMove(file, function() {
    var document = _.extend(file, {
      type: fileData.type,
      partenaire: fileData.partenaire
    });

    return done(null, document);
  });
}

export function saveFile(req, res) {
  processDocument(req.file, req.body, function(err, document) {
    if (err) {
      return res.sendStatus(err.status);
    }

    var request = req.request;
    request.documents.push(document);

    request.save(function(err, saved) {
      if (err) { return handleError(req, res, err); }

      request.saveActionLog(Actions.DOCUMENT_ADDED, req.user, req.log, {document: document});

      var savedDocument = _.find(saved.documents, {filename: document.filename});
      return res
        .status(201)
        .json(savedDocument);
    });
  });
}

export function downloadFile(req, res) {
  var filePath = path.join(config.uploadDir, req.params.fileName);

  var stat = fs.statSync(filePath);

  res.writeHead(200, {
    'Content-Length': stat.size
  });

  var readStream = fs.createReadStream(filePath);
  readStream.pipe(res);
}

export function deleteFile(req, res) {
  handleStatusError(req, res)
    .then(handleDeleteFile(req))
    .then(respondWithResult(res, 204))
    .catch(handleError(req, res));
}

export function updateFile(req, res) {
  var request = req.request;
  var file = request.documents.id(req.params.fileId);
  var isInvalid = req.body.isInvalid;
  var invalidReason = req.body.invalidReason;

  if (!file) {
    return res.sendStatus(404);
  }

  if (typeof isInvalid === 'undefined') {
    return res.sendStatus(400);
  }

  file.set('isInvalid', isInvalid);
  file.set('invalidReason', invalidReason);

  request.save(function(err) {
    if (err) return handleError(err);

    var action = isInvalid ? Actions.DOCUMENT_REFUSED : Actions.DOCUMENT_VALIDATED;
    var reason = isInvalid ? invalidReason : '';

    request.saveActionLog(action, req.user, req.log, {document: file, reason : reason});
    return res.json(file);
  });
}
<commit_msg>refactor style ES2015<commit_after>'use strict';

import _ from 'lodash';
import path from 'path';
import fs from 'fs';

import config from '../../config/environment';

import Auth from '../../auth/auth.service';
import resizeAndMove from '../../components/resize-image';
import {actions as Actions} from '../../components/actions';

function handleError(req, res) {
  return function(statusCode, err) {
    statusCode = statusCode || 500;

    if (err) {
      req.log.error(err);
      res.status(statusCode).send(err);
    } else {
      res.sendStatus(statusCode);
    }
  };
}

function handleStatusError(req) {
  return new Promise(function(resolve, reject) {
    if (Auth.meetsRequirements(req.user.role, 'adminMdph')) {
      return resolve();
    }

    if (req.request.status === 'en_cours' || req.request.status === 'en_attente_usager') {
      return resolve();
    }

    reject(403);
  });
}

function respondWithResult(res, statusCode) {
  statusCode = statusCode || 200;
  return function(entity) {
    if (entity) {
      res.status(statusCode).json(entity);
    } else {
      res.sendStatus(statusCode);
    }

    return null;
  };
}

function removeFileFromFS(path) {
  if (path) {
    fs.exists(path, function(exists) {
      if (exists) {
        fs.unlink(path, () => {
          return Promise.resolve();
        });
      }
    });
  }

  return Promise.resolve();
}

function handleDeleteFile(req) {
  const file = req.request.documents.id(req.params.fileId);

  if (!file) {
    throw(304);
  }

  return removeFileFromFS(file.path)
    .then(() => {
      return file.remove({}, () => {
        req.request.saveActionLog(Actions.DOCUMENT_REMOVED, req.user, req.log, {document: file});
        return req.request.save();
      });
    });
}

function processDocument(file, fileData, done) {
  if (typeof file === 'undefined') {
    return done({status: 304});
  }

  var supportedFileTypes = ['application/pdf', 'image/jpeg', 'image/png'];
  if (supportedFileTypes.indexOf(file.mimetype) < 0) {
    return done({status: 403});
  }

  resizeAndMove(file, function() {
    var document = _.extend(file, {
      type: fileData.type,
      partenaire: fileData.partenaire
    });

    return done(null, document);
  });
}

export function saveFile(req, res) {
  processDocument(req.file, req.body, function(err, document) {
    if (err) {
      return res.sendStatus(err.status);
    }

    var request = req.request;
    request.documents.push(document);

    request.save(function(err, saved) {
      if (err) { return handleError(req, res, err); }

      request.saveActionLog(Actions.DOCUMENT_ADDED, req.user, req.log, {document: document});

      var savedDocument = _.find(saved.documents, {filename: document.filename});
      return res
        .status(201)
        .json(savedDocument);
    });
  });
}

export function downloadFile(req, res) {
  var filePath = path.join(config.uploadDir, req.params.fileName);

  var stat = fs.statSync(filePath);

  res.writeHead(200, {
    'Content-Length': stat.size
  });

  var readStream = fs.createReadStream(filePath);
  readStream.pipe(res);
}

export function deleteFile(req, res) {
  handleStatusError(req, res)
    .then(handleDeleteFile(req))
    .then(respondWithResult(res, 204))
    .catch(handleError(req, res));
}

export function updateFile(req, res) {
  var request = req.request;
  var file = request.documents.id(req.params.fileId);
  var isInvalid = req.body.isInvalid;
  var invalidReason = req.body.invalidReason;

  if (!file) {
    return res.sendStatus(404);
  }

  if (typeof isInvalid === 'undefined') {
    return res.sendStatus(400);
  }

  file.set('isInvalid', isInvalid);
  file.set('invalidReason', invalidReason);

  request.save(function(err) {
    if (err) return handleError(err);

    var action = isInvalid ? Actions.DOCUMENT_REFUSED : Actions.DOCUMENT_VALIDATED;
    var reason = isInvalid ? invalidReason : '';

    request.saveActionLog(action, req.user, req.log, {document: file, reason});
    return res.json(file);
  });
}
<|endoftext|>


<commit_before>var fs = require('fs');
var Discord = require('discord.js');
var youtubeStream = require('youtube-audio-stream');
var Helpers = require('./helpers');

var bot = new Discord.Client();
var token;
var messageChannel;
var currentVoiceChannel = null;
var songQueue = [];
var dispatcher = null;
var repeat = false;

/**
 * Plays the given song to the voice connection and registers end song event.
 *
 * @param {VoiceConnection} connection The bot's current voice connection
 * @param songRequest Information about the song request
 * @param songRequest.name The username of the person that created this request
 * @param songRequest.song The URL of the YouTube video to play
 */
function playSong(connection, songRequest) {
    dispatcher = connection.playStream(youtubeStream(songRequest.url));
    messageChannel.sendMessage('Now playing `' + songRequest.title + '`, requested by `'
        + songRequest.requester + '`.');

    dispatcher.on('end', function() {
        if (repeat) {
            playSong(connection, songQueue[0]);
        } else {
            // remove song that just finished from the queue
            songQueue.shift();
            // disconnect if queue is empty, otherwise play the next song
            if (songQueue.length) {
                playSong(connection, songQueue[0]);
            } else {
                connection.disconnect();
                currentVoiceChannel = null;
                dispatcher = null;
            }
        }
    });
}

try {
    token = fs.readFileSync('token', 'utf8');
} catch (err) {
    console.log('Could not open token file');
    process.exit();
}

bot.on('ready', function() {
    console.log('Bot ready');
});

bot.on('message', function(msg) {
    let prefix = '.';
    // if not a bot command or message was sent from bot then do nothing
    if (!(msg.content.startsWith(prefix)) || msg.author.bot) {
        return;
    }
    messageChannel = msg.channel;
    if (msg.content.startsWith(prefix + 'play')) {
        // print message if author is not in a voice channel
        var msgSender = msg.author;
        // get all voice channels and check if the sender is one of them
        // only get channels from the server that the message was sent from
        var allChannels = msg.guild.channels.array();
        // filter for voice channels
        var voiceChannels = [];
        for (let i = 0; i < allChannels.length; i++) {
            if (allChannels[i].type === 'voice') {
                voiceChannels.push(allChannels[i]);
            }
        }
        // check for the voice channel the sender is in, return if not in one
        var channelToStream = null;
        for (let i = 0; i < voiceChannels.length; i++) {
            let channelMembers = voiceChannels[i].members.array();
            let channelFound = false;
            for (let j = 0; j < channelMembers.length; j++) {
                if (channelMembers[j].user.id === msgSender.id) {
                    channelToStream = voiceChannels[i];
                    channelFound = true;
                    break;
                }
            }
            if (channelFound) {
                break;
            }
        }
        if (channelToStream === null) {
            messageChannel.sendMessage('You must be in a voice channel to play a song.');
            return;
        } else if (currentVoiceChannel !== null && channelToStream.id !== currentVoiceChannel.id) {
            messageChannel.sendMessage('The bot is currently playing music in `'
                + currentVoiceChannel.name + '`. Please join that voice channel before requesting '
                + 'songs.');
            return;
        }

        // if the bot is already inside a voice channel, then it's playing music
        var previouslyActive = currentVoiceChannel === null ? false : true;

        var songUrl = msg.content.split(' ')[1];
        var songRequest = {requester: msg.author.username, url: songUrl};
        Helpers.getSongName(songUrl).then(function(title) {
            // only process the song if the title can be obtained
            songRequest.title = title;
            songQueue.push(songRequest);
            if (!previouslyActive) {
                // not playing anything, so join a channel and play
                channelToStream.join().then(function(connection) {
                    currentVoiceChannel = channelToStream;
                    playSong(connection, songRequest);
                });
            } else {
                Helpers.getSongName(songUrl).then(function(title) {
                    messageChannel.sendMessage('`' + title + '` added to song queue.');
                });
            }
        }, function(error) {
            console.log(error);
            messageChannel.sendMessage('There was an error processing your song request. Please try again.');
        });
    } else if (msg.content === prefix + 'queue') {
        if (!songQueue.length) {
            messageChannel.sendMessage('No songs in queue.');
        } else {
            var queueMessage = '';
            // wrap url in angled brackets to prevent embed
            queueMessage += 'Currently playing `' + songQueue[0].title + '`, requested by `'
                + songQueue[0].requester + '`.\n<' + songQueue[0].url + '>\n';
            if (songQueue.length > 1) {
                queueMessage += 'Up next:\n';
                for (let i = 1; i < songQueue.length; i++) {
                    queueMessage += i + ') `' + songQueue[i].title + '`, requested by `'
                        + songQueue[i].requester + '`.\n';
                }
            }
            // cut out the last \n
            messageChannel.sendMessage(queueMessage.slice(0, -1));
        }
    } else if (dispatcher === null && ['.pause', '.resume', '.skip', '.repeat'].indexOf(msg.content) !== -1) {
        // these commands are invalid if no song is playing
        messageChannel.sendMessage('No song is currently being played.');
    } else if (msg.content === prefix + 'pause') {
        if (dispatcher.paused) {
            messageChannel.sendMessage('Song is already paused. Type `.resume` to resume playback.');
        } else {
            dispatcher.pause();
            messageChannel.sendMessage('Playback paused.');
        }
    } else if (msg.content === prefix + 'resume') {
        if (!dispatcher.paused) {
            messageChannel.sendMessage('Song is already playing. Type `.pause` to pause playback.');
        } else {
            dispatcher.resume();
            messageChannel.sendMessage('Playback resumed.');
        }
    } else if (msg.content === prefix + 'skip') {
        dispatcher.end();
    } else if (msg.content === prefix + 'repeat') {
        repeat = true;
        messageChannel.sendMessage('`' + songQueue[0].title + '` is now playing on repeat. Type '
            + '`.stoprepeat` to continue playing song queue.')
    } else if (msg.content === prefix + 'stoprepeat') {
        repeat = false;
        messageChannel.sendMessage('Repeat mode disabled.');
    }
});

bot.login(token);<commit_msg>more descriptive bot messages<commit_after>var fs = require('fs');
var Discord = require('discord.js');
var youtubeStream = require('youtube-audio-stream');
var Helpers = require('./helpers');

var bot = new Discord.Client();
var token;
var messageChannel;
var currentVoiceChannel = null;
var songQueue = [];
var dispatcher = null;
var repeat = false;

/**
 * Plays the given song to the voice connection and registers end song event.
 *
 * @param {VoiceConnection} connection The bot's current voice connection
 * @param songRequest Information about the song request
 * @param songRequest.name The username of the person that created this request
 * @param songRequest.song The URL of the YouTube video to play
 */
function playSong(connection, songRequest) {
    dispatcher = connection.playStream(youtubeStream(songRequest.url));
    messageChannel.sendMessage('Now playing `' + songRequest.title + '`, requested by `'
        + songRequest.requester + '`.');

    dispatcher.on('end', function() {
        if (repeat) {
            playSong(connection, songQueue[0]);
        } else {
            // remove song that just finished from the queue
            songQueue.shift();
            // disconnect if queue is empty, otherwise play the next song
            if (songQueue.length) {
                playSong(connection, songQueue[0]);
            } else {
                connection.disconnect();
                currentVoiceChannel = null;
                dispatcher = null;
            }
        }
    });
}

try {
    token = fs.readFileSync('token', 'utf8');
} catch (err) {
    console.log('Could not open token file');
    process.exit();
}

bot.on('ready', function() {
    console.log('Bot ready');
});

bot.on('message', function(msg) {
    let prefix = '.';
    // if not a bot command or message was sent from bot then do nothing
    if (!(msg.content.startsWith(prefix)) || msg.author.bot) {
        return;
    }
    messageChannel = msg.channel;
    if (msg.content.startsWith(prefix + 'play')) {
        // print message if author is not in a voice channel
        var msgSender = msg.author;
        // get all voice channels and check if the sender is one of them
        // only get channels from the server that the message was sent from
        var allChannels = msg.guild.channels.array();
        // filter for voice channels
        var voiceChannels = [];
        for (let i = 0; i < allChannels.length; i++) {
            if (allChannels[i].type === 'voice') {
                voiceChannels.push(allChannels[i]);
            }
        }
        // check for the voice channel the sender is in, return if not in one
        var channelToStream = null;
        for (let i = 0; i < voiceChannels.length; i++) {
            let channelMembers = voiceChannels[i].members.array();
            let channelFound = false;
            for (let j = 0; j < channelMembers.length; j++) {
                if (channelMembers[j].user.id === msgSender.id) {
                    channelToStream = voiceChannels[i];
                    channelFound = true;
                    break;
                }
            }
            if (channelFound) {
                break;
            }
        }
        if (channelToStream === null) {
            messageChannel.sendMessage('You must be in a voice channel to play a song.');
            return;
        } else if (currentVoiceChannel !== null && channelToStream.id !== currentVoiceChannel.id) {
            messageChannel.sendMessage('The bot is currently playing music in `'
                + currentVoiceChannel.name + '`. Please join that voice channel before requesting '
                + 'songs.');
            return;
        }

        // if the bot is already inside a voice channel, then it's playing music
        var previouslyActive = currentVoiceChannel === null ? false : true;

        var songUrl = msg.content.split(' ')[1];
        var songRequest = {requester: msg.author.username, url: songUrl};
        Helpers.getSongName(songUrl).then(function(title) {
            // only process the song if the title can be obtained
            songRequest.title = title;
            songQueue.push(songRequest);
            if (!previouslyActive) {
                // not playing anything, so join a channel and play
                channelToStream.join().then(function(connection) {
                    currentVoiceChannel = channelToStream;
                    playSong(connection, songRequest);
                });
            } else {
                Helpers.getSongName(songUrl).then(function(title) {
                    messageChannel.sendMessage('`' + title + '` added to song queue.');
                });
            }
        }, function(error) {
            console.log(error);
            messageChannel.sendMessage('There was an error processing your song request. Please try again.');
        });
    } else if (msg.content === prefix + 'queue') {
        if (!songQueue.length) {
            messageChannel.sendMessage('No songs in queue.');
        } else {
            var queueMessage = '';
            // wrap url in angled brackets to prevent embed
            queueMessage += 'Currently playing `' + songQueue[0].title + '`, requested by `'
                + songQueue[0].requester + '`.\n<' + songQueue[0].url + '>\n';
            if (songQueue.length > 1) {
                queueMessage += 'Up next:\n';
                for (let i = 1; i < songQueue.length; i++) {
                    queueMessage += i + ') `' + songQueue[i].title + '`, requested by `'
                        + songQueue[i].requester + '`.\n';
                }
            }
            // cut out the last \n
            messageChannel.sendMessage(queueMessage.slice(0, -1));
        }
    } else if (dispatcher === null && ['.pause', '.resume', '.skip', '.repeat'].indexOf(msg.content) !== -1) {
        // these commands are invalid if no song is playing
        messageChannel.sendMessage('No song is currently being played.');
    } else if (msg.content === prefix + 'pause') {
        var resumeMsg = 'Type `.resume` to resume playback.';
        if (dispatcher.paused) {
            messageChannel.sendMessage('Song is already paused. ' + resumeMsg);
        } else {
            dispatcher.pause();
            messageChannel.sendMessage('Playback paused. ' + resumeMsg);
        }
    } else if (msg.content === prefix + 'resume') {
        var pauseMsg = 'Type `.pause` to pause playback.';
        if (!dispatcher.paused) {
            messageChannel.sendMessage('Song is already playing. ' + pauseMsg);
        } else {
            dispatcher.resume();
            messageChannel.sendMessage('Playback resumed. ' + pauseMsg);
        }
    } else if (msg.content === prefix + 'skip') {
        dispatcher.end();
    } else if (msg.content === prefix + 'repeat') {
        repeat = true;
        messageChannel.sendMessage('`' + songQueue[0].title + '` is now playing on repeat. Type '
            + '`.stoprepeat` to continue playing song queue.')
    } else if (msg.content === prefix + 'stoprepeat') {
        repeat = false;
        messageChannel.sendMessage('Repeat mode disabled. Type `.repeat` to play the current song '
            + 'on repeat.');
    }
});

bot.login(token);<|endoftext|>


<commit_before>(function(window,document,undefined) {
'use strict';

//Object that will be built up
var codeReview = {};
//Expose API
window.codeReview = codeReview;

var g = {}; //Globals
g.debug = true; //Debug mode
g.vendors = {
  gist: /https?:\/\/gist.github.com/  //Github gists
};
g.vendorDefault = 'readability';
g.baseUrl = g.debug ? '//localhost:8080' : '//review.steamedpears.com';
g.bookmarkletUri =  '/bookmarklet';
g.vendorUri = g.debug ? '/bookmarklet/vendors/' : '/bookmarklet/vendors/';
g.newCodeApi = '/do/newcode';

function dbg() { if (g.debug) console.log.apply(console,arguments); }
codeReview.dbg = dbg;

var assetLoader = (function() {
  var assets = {};

  /*
   * Create a tag that will be appended to head. If you input only a URL, the
   * file's extension will be matched to the appropriate tag. If no match is
   * found, the tag used will be the default type.
   *
   * Returns an HTML element
   *
   * Default type: javascript
   *
   * Usage:
   *  var tag = createTag('http://example.com/style.css');
   *  var tag = createTag('http://example.com/someJSfile', 'js');
   *
   */
  var createTag = function createTag(url, t) {
    var ext_re = /\.[0-9a-zA-Z]+$/; // matches a URL's extension
    var tag;
    var type;

    var matches = url.match(ext_re);
    type = t || matches && matches[0].substr(1) || 'default';

    switch (type) {
      case 'css':
        tag = document.createElement('link');
        tag.type = 'text/css';
        tag.rel = 'stylesheet';
        tag.href = url;
        break;

      case 'js': // FALLTHROUGH
      case 'javascript': // FALLTHROUGH
      default:
        tag = document.createElement('script');
        tag.src = url;
        break;
    }
    return tag;
  };

  //API
  return {
    /* Loads assets
     * USAGE:
     * Just load asset
     *  assetLoader.load('//example.ca/st.css');
     *
     * Call callback when loaded
     *  assetLoader.load('//example.ca/script.js',callback)
     */
    load: function (url, callback) {

      if (assets[url]) return;

      var tag = createTag(url);
      dbg('assetLoader.load: ', tag);
      assets[url] = tag;
      if (callback !== undefined) this.runOnLoad(tag, callback);
      document.head.appendChild(tag);
    },
    /* Invoke callback when asset is loaded
    * USAGE:
    * runOnLoad(someTag, function() { //do stuff });
    */
    runOnLoad: function (asset, callback) {
      if (asset.addEventListener) { //W3C
        asset.addEventListener('load',callback);
      } else if (asset.readyState) { //IE
        asset.onreadystatechange = (function() {

          if (asset.readyState === 'loaded' ||
            asset.readyState === 'complete') {

            asset.onreadystatechange = null;
            return callback();
          }
        });
      } else {
        throw 'runOnLoad: Can not attach event.';
      }
    },
    deleteAssets: function () {
      var a;
      for (var asset in assets) {
        if (assets.hasOwnProperty(asset)) {
          a = assets[asset];
          a.parentNode.removeChild(a);
          delete assets[asset];
        }
      }
    }
  };
})();

var helpers = {
  //Figure out if this url uses a known vendor
  vendorGetFromUrl: function(url) {
    var matches;
    var regex;

    for (var vendor in g.vendors) {
      if (g.vendors.hasOwnProperty(vendor)) {
        regex = g.vendors[vendor];
        matches = regex.test(url);
        if (matches) return vendor;
      }
    }
    return null;
  },
  //Load a given vendor
  loadVendor: function (vendor, callback) {
    dbg('helpers.loadVendor: ' + vendor);
    assetLoader.load(g.baseUrl + g.vendorUri + vendor + '.js', callback);
  },
  //Returns an XML HTTP request object
  request: function (method, url, type) {
    var xhr = new XMLHttpRequest();
    xhr.open(method || 'POST', url, true);
    xhr.setRequestHeader('Content-type', type || 'application/json');
    return xhr;
  }
};

//Sketch of what's to come
helpers.loadVendor('readability', function () {
  var content = codeReview.vendor.getContent();

  var req = helpers.request(null,g.baseUrl + g.newCodeApi);

  var obj = JSON.stringify({
    language_id: 1,
    text: content.text
  });
  req.onreadystatechange = function() {
    if (req.readyState === 4) {
      console.log(req.responseText);
    }
  };
  req.send(obj);

});

codeReview.exit = function () {
  dbg('Exiting...');
  assetLoader.deleteAssets();
};

})(window,document);
/* vim: set softtabstop=2 shiftwidth=2 tabstop=8 expandtab textwidth=80: */
<commit_msg>created comment sections<commit_after>(function(window,document,undefined) {
'use strict';

//Object that will be built up
var codeReview = {};
//Expose API
window.codeReview = codeReview;

var g = {}; //Globals
g.debug = true; //Debug mode
g.vendors = {
  gist: /https?:\/\/gist.github.com/  //Github gists
};
g.vendorDefault = 'readability';
g.baseUrl = g.debug ? '//localhost:8080' : '//review.steamedpears.com';
g.bookmarkletUri =  '/bookmarklet';
g.vendorUri = g.debug ? '/bookmarklet/vendors/' : '/bookmarklet/vendors/';
g.newCodeApi = '/do/newcode';

function dbg() { if (g.debug) console.log.apply(console,arguments); }
codeReview.dbg = dbg;

/*******************************************************************************
* Asset loader                                                                 *
*******************************************************************************/
var assetLoader = (function() {
  var assets = {};

  /*
   * Create a tag that will be appended to head. If you input only a URL, the
   * file's extension will be matched to the appropriate tag. If no match is
   * found, the tag used will be the default type.
   *
   * Returns an HTML element
   *
   * Default type: javascript
   *
   * Usage:
   *  var tag = createTag('http://example.com/style.css');
   *  var tag = createTag('http://example.com/someJSfile', 'js');
   *
   */
  var createTag = function createTag(url, t) {
    var ext_re = /\.[0-9a-zA-Z]+$/; // matches a URL's extension
    var tag;
    var type;

    var matches = url.match(ext_re);
    type = t || matches && matches[0].substr(1) || 'default';

    switch (type) {
      case 'css':
        tag = document.createElement('link');
        tag.type = 'text/css';
        tag.rel = 'stylesheet';
        tag.href = url;
        break;

      case 'js': // FALLTHROUGH
      case 'javascript': // FALLTHROUGH
      default:
        tag = document.createElement('script');
        tag.src = url;
        break;
    }
    return tag;
  };

  //API
  return {
    /* Loads assets
     * USAGE:
     * Just load asset
     *  assetLoader.load('//example.ca/st.css');
     *
     * Call callback when loaded
     *  assetLoader.load('//example.ca/script.js',callback)
     */
    load: function (url, callback) {

      if (assets[url]) return;

      var tag = createTag(url);
      dbg('assetLoader.load: ', tag);
      assets[url] = tag;
      if (callback !== undefined) this.runOnLoad(tag, callback);
      document.head.appendChild(tag);
    },
    /* Invoke callback when asset is loaded
    * USAGE:
    * runOnLoad(someTag, function() { //do stuff });
    */
    runOnLoad: function (asset, callback) {
      if (asset.addEventListener) { //W3C
        asset.addEventListener('load',callback);
      } else if (asset.readyState) { //IE
        asset.onreadystatechange = (function() {

          if (asset.readyState === 'loaded' ||
            asset.readyState === 'complete') {

            asset.onreadystatechange = null;
            return callback();
          }
        });
      } else {
        throw 'runOnLoad: Can not attach event.';
      }
    },
    deleteAssets: function () {
      var a;
      for (var asset in assets) {
        if (assets.hasOwnProperty(asset)) {
          a = assets[asset];
          a.parentNode.removeChild(a);
          delete assets[asset];
        }
      }
    }
  };
})();

/*******************************************************************************
* Helpers                                                                      *
*******************************************************************************/
var helpers = {
  //Figure out if this url uses a known vendor
  vendorGetFromUrl: function(url) {
    var matches;
    var regex;

    for (var vendor in g.vendors) {
      if (g.vendors.hasOwnProperty(vendor)) {
        regex = g.vendors[vendor];
        matches = regex.test(url);
        if (matches) return vendor;
      }
    }
    return null;
  },
  //Load a given vendor
  loadVendor: function (vendor, callback) {
    dbg('helpers.loadVendor: ' + vendor);
    assetLoader.load(g.baseUrl + g.vendorUri + vendor + '.js', callback);
  },
  //Returns an XML HTTP request object
  request: function (method, url, type) {
    var xhr = new XMLHttpRequest();
    xhr.open(method || 'POST', url, true);
    xhr.setRequestHeader('Content-type', type || 'application/json');
    return xhr;
  }
};

/*******************************************************************************
* Global API                                                                   *
*******************************************************************************/

codeReview.exit = function () {
  dbg('Exiting...');
  assetLoader.deleteAssets();
};

/*******************************************************************************
* Main code                                                                    *
*******************************************************************************/

//Sketch of what's to come
helpers.loadVendor('readability', function () {
  var content = codeReview.vendor.getContent();

  var req = helpers.request(null,g.baseUrl + g.newCodeApi);

  var obj = JSON.stringify({
    language_id: 1,
    text: content.text
  });
  req.onreadystatechange = function() {
    if (req.readyState === 4) {
      console.log(req.responseText);
    }
  };
  req.send(obj);

});

})(window,document);
/* vim: set softtabstop=2 shiftwidth=2 tabstop=8 expandtab textwidth=80: */
<|endoftext|>