<commit_before>'use strict';
var BPromise = require('bluebird');
var PouchDB = require('pouchdb');
var util = require('./../util');
var seed = require('pouchdb-seed-design');
var request = require('superagent');

module.exports = function (config, userDB, couchAuthDB) {

  var cloudant = config.getItem('dbServer.cloudant');

  var adapter;

  if(cloudant) {
    adapter = require('./cloudant');
  } else {
    var CouchAdapter = require('./couchdb');
    adapter = new CouchAdapter(couchAuthDB);
  }

  this.storeKey = function (username, key, password, expires, roles) {
    return adapter.storeKey(username, key, password, expires, roles);
  };

  this.removeKeys = function(keys) {
    return adapter.removeKeys(keys);
  };

  this.authorizeKeys = function (user_id, db, keys, permissions, roles) {
    return adapter.authorizeKeys(user_id, db, keys, permissions, roles);
  };

  this.deauthorizeKeys = function (db, keys) {
    return adapter.deauthorizeKeys(db, keys);
  };

  this.authorizeUserSessions = function(user_id, personalDBs, sessionKeys, roles) {
    var self = this;
    var promises = [];
    sessionKeys = util.toArray(sessionKeys);
    Object.keys(personalDBs).forEach(function(personalDB) {
      var permissions = personalDBs[personalDB].permissions;
      if(!permissions) {
        permissions = config.getItem('userDBs.model.' + personalDB.name + '.permissions') || config.getItem('userDBs.model._default.permissions') || [];
      }
      var db = new PouchDB(util.getDBURL(config.getItem('dbServer')) + '/' + personalDB);
      promises.push(self.authorizeKeys(user_id, db, sessionKeys, permissions, roles));
    });
    return BPromise.all(promises);
  };

  this.addUserDB = function (userDoc, dbName, designDocs, type, permissions, adminRoles, memberRoles) {
    var self = this;
    var promises = [];
    adminRoles = adminRoles || [];
    memberRoles = memberRoles || [];
    // Create and the database and seed it if a designDoc is specified
    var prefix = config.getItem('userDBs.privatePrefix') ? config.getItem('userDBs.privatePrefix') + '_' : '';
    var finalDBName, newDB;
    // Make sure we have a legal database name
    var username = userDoc._id;
    username = getLegalDBName(username);
    if(type === 'shared') {
      finalDBName = dbName;
    } else {
      finalDBName = prefix + dbName + '$' + username;
    }
    return self.createDB(finalDBName)
      .then(function() {
        newDB = new PouchDB(util.getDBURL(config.getItem('dbServer')) + '/' + finalDBName);
        return adapter.initSecurity(newDB, adminRoles, memberRoles);
      })
      .then(function() {
        // Seed the design docs
        if (designDocs && designDocs instanceof Array) {
          designDocs.forEach(function(ddName) {
            var dDoc = self.getDesignDoc(ddName);
            if(dDoc) {
              promises.push(seed(newDB, dDoc));
            } else {
              console.warn('Failed to locate design doc: ' + ddName);
            }
          });
        }
        // Authorize the user's existing DB keys to access the new database
        var keysToAuthorize = [];
        if (userDoc.session) {
          for (var key in userDoc.session) {
            if(userDoc.session.hasOwnProperty(key) && userDoc.session[key].expires > Date.now()) {
              keysToAuthorize.push(key);
            }
          }
        }
        if (keysToAuthorize.length > 0) {
          promises.push(self.authorizeKeys(userDoc._id, newDB, keysToAuthorize, permissions, userDoc.roles));
        }
        return BPromise.all(promises);
      })
      .then(function() {
        return BPromise.resolve(finalDBName);
      });
  };

  this.removeExpiredKeys = function () {
    var self = this;
    var keysByUser = {};
    var userDocs = {};
    var expiredKeys = [];
    // query a list of expired keys by user
    return userDB.query('auth/expiredKeys', {endkey: Date.now(), include_docs: true})
      .then(function(results) {
        // group by user
        results.rows.forEach(function(row) {
          keysByUser[row.value.user] = row.value.key;
          expiredKeys.push(row.value.key);
          // Add the user doc if it doesn't already exist
          if(typeof userDocs[row.value.user] === 'undefined') {
            userDocs[row.value.user] = row.doc;
          }
          // remove each key from user.session
          if(userDocs[row.value.user].session) {
            Object.keys(userDocs[row.value.user].session).forEach(function(session) {
              if(row.value.key === session) {
                delete userDocs[row.value.user].session[session];
              }
            });
          }
        });
        return self.removeKeys(expiredKeys);
      })
      .then(function() {
        // - deauthorize keys for each personal database of each user
        var deauthorize = [];
        Object.keys(keysByUser).forEach(function(user) {
          deauthorize.push(self.deauthorizeUser(userDocs[user], keysByUser[user]));
        });
        return BPromise.all(deauthorize);
      })
      .then(function() {
        var userUpdates = [];
        Object.keys(userDocs).forEach(function(user) {
          userUpdates.push(userDocs[user]);
        });
        // Bulk save user doc updates
        return userDB.bulkDocs(userUpdates);
      })
      .then(function() {
        return BPromise.resolve(expiredKeys);
      });
  };

  this.deauthorizeUser = function(userDoc, keys) {
    var self = this;
    var promises = [];
    // If keys is not specified we will deauthorize all of the users sessions
    if(!keys) {
      keys = util.getSessions(userDoc);
    }
    keys = util.toArray(keys);
    if(userDoc.personalDBs && typeof userDoc.personalDBs === 'object') {
      Object.keys(userDoc.personalDBs).forEach(function(personalDB) {
        var db = new PouchDB(util.getDBURL(config.getItem('dbServer')) + '/' + personalDB);
        promises.push(self.deauthorizeKeys(db, keys));
      });
      return BPromise.all(promises);
    } else {
      return BPromise.resolve(false);
    }
  };

  this.getDesignDoc = function(docName) {
    if(!docName) {
      return null;
    }
    var designDoc;
    var designDocDir = config.getItem('userDBs.designDocDir');
    if(!designDocDir) {
      designDocDir = __dirname;
    }
    try {
      designDoc = require(designDocDir + '/' + docName);
    }
    catch(err) {
      console.warn('Design doc: ' + designDocDir + '/' + docName + ' not found.');
      designDoc = null;
    }
    return designDoc;
  };

  this.getDBConfig = function(dbName, type) {
    var dbConfig = {
      name: dbName
    };
    dbConfig.adminRoles = config.getItem('userDBs.defaultSecurityRoles.admins') || [];
    dbConfig.memberRoles = config.getItem('userDBs.defaultSecurityRoles.members') || [];
    var dbConfigRef = 'userDBs.model.' + dbName;
    if(config.getItem(dbConfigRef)) {
      dbConfig.permissions = config.getItem(dbConfigRef + '.permissions') || [];
      dbConfig.designDocs = config.getItem(dbConfigRef + '.designDocs') || [];
      dbConfig.type = type || config.getItem(dbConfigRef + '.type') || 'private';
      var dbAdminRoles = config.getItem(dbConfigRef + '.adminRoles');
      var dbMemberRoles = config.getItem(dbConfigRef + '.memberRoles');
      if(dbAdminRoles && dbAdminRoles instanceof Array) {
        dbAdminRoles.forEach(function(role) {
          if(role && dbConfig.adminRoles.indexOf(role) === -1) {
            dbConfig.adminRoles.push(role);
          }
        });
      }
      if(dbMemberRoles && dbMemberRoles instanceof Array) {
        dbMemberRoles.forEach(function(role) {
          if(role && dbConfig.memberRoles.indexOf(role) === -1) {
            dbConfig.memberRoles.push(role);
          }
        });
      }
    } else if(config.getItem('userDBs.model._default')) {
      dbConfig.permissions = config.getItem('userDBs.model._default.permissions') || [];
      // Only add the default design doc to a private database
      if(!type || type === 'private') {
        dbConfig.designDocs = config.getItem('userDBs.model._default.designDocs') || [];
      } else {
        dbConfig.designDocs = [];
      }
      dbConfig.type = type || 'private';
    } else {
      dbConfig.type = type || 'private';
    }
    return dbConfig;
  };

  this.createDB = function (dbName) {
    var finalUrl = util.getDBURL(config.getItem('dbServer')) + '/' + dbName;
    return BPromise.fromNode(function(callback) {
      request.put(finalUrl)
        .send({})
        .end(callback);
    })
      .then(function(res) {
        return BPromise.resolve(JSON.parse(res.text));
      }, function(err) {
        if(err.status === 412) {
          return BPromise.resolve(false);
        } else {
          return BPromise.reject(err.text);
        }
      });
  };

  this.removeDB = function(dbName) {
    var db = new PouchDB(util.getDBURL(config.getItem('dbServer')) + '/' + dbName);
    return db.destroy();
  };

  return this;
};

// Escapes any characters that are illegal in a CouchDB database name using percent codes inside parenthesis
// Example: 'My.name@example.com' => 'my(2e)name(40)example(2e)com'
function getLegalDBName(input) {
  input = input.toLowerCase();
  var output = encodeURIComponent(input);
  output = output.replace(/\./g, '%2E');
  output = output.replace(/!/g, '%21');
  output = output.replace(/~/g, '%7E');
  output = output.replace(/\*/g, '%2A');
  output = output.replace(/'/g, '%27');
  output = output.replace(/\(/g, '%28');
  output = output.replace(/\)/g, '%29');
  output = output.replace(/\-/g, '%2D');
  output = output.toLowerCase();
  output = output.replace(/(%..)/g, function(esc) {
    esc = esc.substr(1);
    return '(' + esc + ')';
  });
  return output;
}<commit_msg>Fixed permissions retrieval from config userDBs.model<commit_after>'use strict';
var BPromise = require('bluebird');
var PouchDB = require('pouchdb');
var util = require('./../util');
var seed = require('pouchdb-seed-design');
var request = require('superagent');

module.exports = function (config, userDB, couchAuthDB) {

  var cloudant = config.getItem('dbServer.cloudant');

  var adapter;

  if(cloudant) {
    adapter = require('./cloudant');
  } else {
    var CouchAdapter = require('./couchdb');
    adapter = new CouchAdapter(couchAuthDB);
  }

  this.storeKey = function (username, key, password, expires, roles) {
    return adapter.storeKey(username, key, password, expires, roles);
  };

  this.removeKeys = function(keys) {
    return adapter.removeKeys(keys);
  };

  this.authorizeKeys = function (user_id, db, keys, permissions, roles) {
    return adapter.authorizeKeys(user_id, db, keys, permissions, roles);
  };

  this.deauthorizeKeys = function (db, keys) {
    return adapter.deauthorizeKeys(db, keys);
  };

  this.authorizeUserSessions = function(user_id, personalDBs, sessionKeys, roles) {
    var self = this;
    var promises = [];
    sessionKeys = util.toArray(sessionKeys);
    Object.keys(personalDBs).forEach(function(personalDB) {
      var permissions = personalDBs[personalDB].permissions;
      if(!permissions) {
        permissions = config.getItem('userDBs.model.' + personalDBs[personalDB].name + '.permissions') || config.getItem('userDBs.model._default.permissions') || [];
      }
      var db = new PouchDB(util.getDBURL(config.getItem('dbServer')) + '/' + personalDB);
      promises.push(self.authorizeKeys(user_id, db, sessionKeys, permissions, roles));
    });
    return BPromise.all(promises);
  };

  this.addUserDB = function (userDoc, dbName, designDocs, type, permissions, adminRoles, memberRoles) {
    var self = this;
    var promises = [];
    adminRoles = adminRoles || [];
    memberRoles = memberRoles || [];
    // Create and the database and seed it if a designDoc is specified
    var prefix = config.getItem('userDBs.privatePrefix') ? config.getItem('userDBs.privatePrefix') + '_' : '';
    var finalDBName, newDB;
    // Make sure we have a legal database name
    var username = userDoc._id;
    username = getLegalDBName(username);
    if(type === 'shared') {
      finalDBName = dbName;
    } else {
      finalDBName = prefix + dbName + '$' + username;
    }
    return self.createDB(finalDBName)
      .then(function() {
        newDB = new PouchDB(util.getDBURL(config.getItem('dbServer')) + '/' + finalDBName);
        return adapter.initSecurity(newDB, adminRoles, memberRoles);
      })
      .then(function() {
        // Seed the design docs
        if (designDocs && designDocs instanceof Array) {
          designDocs.forEach(function(ddName) {
            var dDoc = self.getDesignDoc(ddName);
            if(dDoc) {
              promises.push(seed(newDB, dDoc));
            } else {
              console.warn('Failed to locate design doc: ' + ddName);
            }
          });
        }
        // Authorize the user's existing DB keys to access the new database
        var keysToAuthorize = [];
        if (userDoc.session) {
          for (var key in userDoc.session) {
            if(userDoc.session.hasOwnProperty(key) && userDoc.session[key].expires > Date.now()) {
              keysToAuthorize.push(key);
            }
          }
        }
        if (keysToAuthorize.length > 0) {
          promises.push(self.authorizeKeys(userDoc._id, newDB, keysToAuthorize, permissions, userDoc.roles));
        }
        return BPromise.all(promises);
      })
      .then(function() {
        return BPromise.resolve(finalDBName);
      });
  };

  this.removeExpiredKeys = function () {
    var self = this;
    var keysByUser = {};
    var userDocs = {};
    var expiredKeys = [];
    // query a list of expired keys by user
    return userDB.query('auth/expiredKeys', {endkey: Date.now(), include_docs: true})
      .then(function(results) {
        // group by user
        results.rows.forEach(function(row) {
          keysByUser[row.value.user] = row.value.key;
          expiredKeys.push(row.value.key);
          // Add the user doc if it doesn't already exist
          if(typeof userDocs[row.value.user] === 'undefined') {
            userDocs[row.value.user] = row.doc;
          }
          // remove each key from user.session
          if(userDocs[row.value.user].session) {
            Object.keys(userDocs[row.value.user].session).forEach(function(session) {
              if(row.value.key === session) {
                delete userDocs[row.value.user].session[session];
              }
            });
          }
        });
        return self.removeKeys(expiredKeys);
      })
      .then(function() {
        // - deauthorize keys for each personal database of each user
        var deauthorize = [];
        Object.keys(keysByUser).forEach(function(user) {
          deauthorize.push(self.deauthorizeUser(userDocs[user], keysByUser[user]));
        });
        return BPromise.all(deauthorize);
      })
      .then(function() {
        var userUpdates = [];
        Object.keys(userDocs).forEach(function(user) {
          userUpdates.push(userDocs[user]);
        });
        // Bulk save user doc updates
        return userDB.bulkDocs(userUpdates);
      })
      .then(function() {
        return BPromise.resolve(expiredKeys);
      });
  };

  this.deauthorizeUser = function(userDoc, keys) {
    var self = this;
    var promises = [];
    // If keys is not specified we will deauthorize all of the users sessions
    if(!keys) {
      keys = util.getSessions(userDoc);
    }
    keys = util.toArray(keys);
    if(userDoc.personalDBs && typeof userDoc.personalDBs === 'object') {
      Object.keys(userDoc.personalDBs).forEach(function(personalDB) {
        var db = new PouchDB(util.getDBURL(config.getItem('dbServer')) + '/' + personalDB);
        promises.push(self.deauthorizeKeys(db, keys));
      });
      return BPromise.all(promises);
    } else {
      return BPromise.resolve(false);
    }
  };

  this.getDesignDoc = function(docName) {
    if(!docName) {
      return null;
    }
    var designDoc;
    var designDocDir = config.getItem('userDBs.designDocDir');
    if(!designDocDir) {
      designDocDir = __dirname;
    }
    try {
      designDoc = require(designDocDir + '/' + docName);
    }
    catch(err) {
      console.warn('Design doc: ' + designDocDir + '/' + docName + ' not found.');
      designDoc = null;
    }
    return designDoc;
  };

  this.getDBConfig = function(dbName, type) {
    var dbConfig = {
      name: dbName
    };
    dbConfig.adminRoles = config.getItem('userDBs.defaultSecurityRoles.admins') || [];
    dbConfig.memberRoles = config.getItem('userDBs.defaultSecurityRoles.members') || [];
    var dbConfigRef = 'userDBs.model.' + dbName;
    if(config.getItem(dbConfigRef)) {
      dbConfig.permissions = config.getItem(dbConfigRef + '.permissions') || [];
      dbConfig.designDocs = config.getItem(dbConfigRef + '.designDocs') || [];
      dbConfig.type = type || config.getItem(dbConfigRef + '.type') || 'private';
      var dbAdminRoles = config.getItem(dbConfigRef + '.adminRoles');
      var dbMemberRoles = config.getItem(dbConfigRef + '.memberRoles');
      if(dbAdminRoles && dbAdminRoles instanceof Array) {
        dbAdminRoles.forEach(function(role) {
          if(role && dbConfig.adminRoles.indexOf(role) === -1) {
            dbConfig.adminRoles.push(role);
          }
        });
      }
      if(dbMemberRoles && dbMemberRoles instanceof Array) {
        dbMemberRoles.forEach(function(role) {
          if(role && dbConfig.memberRoles.indexOf(role) === -1) {
            dbConfig.memberRoles.push(role);
          }
        });
      }
    } else if(config.getItem('userDBs.model._default')) {
      dbConfig.permissions = config.getItem('userDBs.model._default.permissions') || [];
      // Only add the default design doc to a private database
      if(!type || type === 'private') {
        dbConfig.designDocs = config.getItem('userDBs.model._default.designDocs') || [];
      } else {
        dbConfig.designDocs = [];
      }
      dbConfig.type = type || 'private';
    } else {
      dbConfig.type = type || 'private';
    }
    return dbConfig;
  };

  this.createDB = function (dbName) {
    var finalUrl = util.getDBURL(config.getItem('dbServer')) + '/' + dbName;
    return BPromise.fromNode(function(callback) {
      request.put(finalUrl)
        .send({})
        .end(callback);
    })
      .then(function(res) {
        return BPromise.resolve(JSON.parse(res.text));
      }, function(err) {
        if(err.status === 412) {
          return BPromise.resolve(false);
        } else {
          return BPromise.reject(err.text);
        }
      });
  };

  this.removeDB = function(dbName) {
    var db = new PouchDB(util.getDBURL(config.getItem('dbServer')) + '/' + dbName);
    return db.destroy();
  };

  return this;
};

// Escapes any characters that are illegal in a CouchDB database name using percent codes inside parenthesis
// Example: 'My.name@example.com' => 'my(2e)name(40)example(2e)com'
function getLegalDBName(input) {
  input = input.toLowerCase();
  var output = encodeURIComponent(input);
  output = output.replace(/\./g, '%2E');
  output = output.replace(/!/g, '%21');
  output = output.replace(/~/g, '%7E');
  output = output.replace(/\*/g, '%2A');
  output = output.replace(/'/g, '%27');
  output = output.replace(/\(/g, '%28');
  output = output.replace(/\)/g, '%29');
  output = output.replace(/\-/g, '%2D');
  output = output.toLowerCase();
  output = output.replace(/(%..)/g, function(esc) {
    esc = esc.substr(1);
    return '(' + esc + ')';
  });
  return output;
}
<|endoftext|>


<commit_before>import Ember from 'ember';

/**
  * @overview OAuth2 library for Emberjs that stores tokens in the browsers localStorage
  * @license   Licensed under MIT license
  *            See https://raw.github.com/amkirwan/ember-oauth2/master/LICENSE
  * @version   0.4.0
  */
export default Ember.Object.extend(Ember.Evented, {
  /**
   * Initializes the Ember.OAuth2 object when using Ember.OAuth2.create({providerId: 'providerId'}). The following options are available for the configuring a provider: clientId, authBaseUri, redirectUri, scope, statePrefix and tokenPrefix. The clientId, authBaseUri and redirectUri are required. The statePrefix has a default value of 'state' and the tokenPrefix has a default value of 'token'.
   *
   * @method init
   */
  init: function() {
    this._super();
    /**
     * The configuration object for the given provider id.
     *  @property {Object} providerConfig
     *  @property {Object} providerConfig.providerId **Required**
     *  @property {String} providerConfig.providerId.clientId **Required**
     *  @property {String} providerConfig.providerId.authBaseUri **Required**
     *  @property {String} providerConfig.providerId.redirectUri **Required**
     *  @property {String} providerConfig.providerId.scope **Optional**
     *  @property {String} providerConfig.providerId.statePrefix **Default:** "state", **Optional**
     *  @property {String} providerConfig.providerId.tokenPrefix **Default:** "prefix", **Optional**
     *  @example
     *    App.oauth = Ember.OAuth2.create({providerId: 'google'});
     */

    // if Ember.OAuth2.config has keys use it instead of window.ENV
    if (Ember.OAuth2 && Ember.OAuth2.config && Object.keys(Ember.OAuth2.config).length) {
      Ember.Logger.warn("Ember.OAuth2.config is deprecated and will be removed in version 0.5.0. Set the config using window.ENV['ember-oauth2']");
      this.providerConfig = Ember.OAuth2.config[this.get('providerId')];
    } else {
      this.providerConfig = window.ENV['ember-oauth2'][this.get('providerId')];
    }
    /**
     * The prefix name for the state key stored in the localStorage.
     *
     * @property statePrefix
     * @type String
     * @default "state"
     */
    this.set('statePrefix', 'state');
    /**
     * The prefix name for the token key stored in the localStorage.
     *
     * @property tokenPrefix
     * @type String
     * @default "token"
     */
    this.set('tokenPrefix', 'token');

    /**
     * @property {String} clientId
     */
    /**
     * @property {String} authBaseUri
     */
    /**
     * @property {String} redirectUri
     */
    /**
     * @property {String} scope
     */
    // sets the properties from the providerConfig and overrides any default settings.
    this.setProperties(this.providerConfig);

    // Bind deprecated event handlers
    /**
     * @event redirect
     * @param {function} The function that handles the redirect.
     */
    this.on('redirect', this.handleRedirect);

    /**
     * @event success
     * @param {function} The function that handles the success callback.
     */
    this.on('success', this._onSuccess);

    /**
     * @event error
     * @param {function} The function that handles the error callback.
     */
    this.on('error', this._onError);
  },

  /**
   * @method version
   * @return {String} The current version number of Ember.OAuth2
   */
  version: function() {
    return VERSION;
  },

  /**
   * @method uuid
   * @return {String} A pseudo random uuid
   */
  uuid: function() {
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
          var r = Math.random()*16|0, v = c === 'x' ? r : (r&0x3|0x8);
              return v.toString(16);
    });
  },

  /**
   * @method now
   * @return {Number} The current time in seconds rounded
   */
  now: function() {
    return Math.round(new Date().getTime()/1000.0);
  },

  /**
   * Creates and returns the request object.
   *
   * @method requestObj
   * @return {Object} request object
   */
  requestObj: function() {
    var request = { 'response_type': 'token' };
    request.providerId = this.get('providerId');
    request.state = this.get('state');
    request.client_id = this.get('clientId');
    request.state = this.get('state');
    if (this.get('scope')) request.scope = this.get('scope');
    return request;
  },

  /**
   * @method authUri
   * @return {String} Authorization uri for generating an OAuth2 token
   */
  authUri: function() {
    if (this.get('state') === null) this.set('state', this.uuid());
    var uri = this.get('authBaseUri');
    uri += '?response_type=token' +
        '&redirect_uri=' + encodeURIComponent(this.get('redirectUri')) +
        '&client_id=' + encodeURIComponent(this.get('clientId')) +
        '&state=' + encodeURIComponent(this.get('state'));
    if (this.get('scope')) uri += '&scope=' + encodeURIComponent(this.get('.scope')).replace('%20', '+');
    return uri;
  },

  /**
   * Open authorize window if the configuration object is valid.
   *
   * @method authorize
   * @return {Promise}
   */
  authorize: function() {
    if (!this.get('providerId')) throw new Error('No provider id given.');
    if (!this.get('clientId')) throw new Error('No client id given.');
    if (!this.get('authBaseUri')) throw new Error('No auth base uri given.');
    if (!this.get('redirectUri')) throw new Error('No redirect uri given.');
    var authorizeUri = this.authUri();
    this.clearStates();
    this.saveState(this.get('state'), this.requestObj());
    return this.openWindow(authorizeUri);
  },

  /**
   * Isolated function responsible for opening windows, to make it
   * easier to override this part in some environments (e.g. Phonegap)

    @param {String} url
    @return {Object} reference to the opened window
   */
  openWindow: function(url) {
    var dialog = window.open(url, 'Authorize', 'height=600, width=450');
    if (window.focus) dialog.focus();
    var self = this;
    return new Ember.RSVP.Promise(function(resolve, reject) {
      self.one('success', function() { resolve(dialog); });
      self.one('error', function(error) {
        new Error('Dialog failed with ' + error);
        reject(dialog);
      });
    });
  },

  /**
    @method authSuccess
    @param {Object} The params returned from the OAuth2 callback
    @return {String} the access_token from the params
  */
  authSuccess: function(params) {
    return params.access_token;
  },

  /**
   * Token properties
   * providerId
   * expiresIn
   * scope
   * token

    @method generateToken
    @return {Object} The access_token object with info about the token
   */
  generateToken: function(params) {
    var token = {};
    token.provider_id = this.get('providerId');
    token.expires_in = this.expiresIn(params.expires_in);
    token.scope = this.get('scope');
    token.access_token = params.access_token;
    return token;
  },

  /**
    @method expiresIn
    @return {Number} When the token expires in seconds.
  */
  expiresIn: function(expires) {
    return this.now() + parseInt(expires, 10);
  },

  /**
   * call on redirect from OAuth2 provider response
    @method onRedirect
    @deprecated Use `.trigger('redirect')` instead.
   */
  onRedirect: function(hash, callback) {
    Ember.Logger.warn("Ember.OAuth2.onRedirect is deprecated and will be removed in future version 0.5.0. Please use .trigger('redirect') instead.");
    this.trigger('redirect', hash, callback);
  },

  /*
   * proxy functions for old event handlers
   *
   * Check if the token returned is valid and if so trigger `success` event else trigger `error`
   *
   * @method handleRedirect
   */
  handleRedirect: function(hash, callback) {
    var params = this.parseCallback(hash);
    if (this.authSuccess(params)) {
      var stateObj = this.getState(params.state);
      this.checkState(stateObj);
      this.saveToken(this.generateToken(params));
      this.trigger('success', stateObj);
    } else {
      this.trigger('error', params);
    }
    if (callback && typeof(callback) === "function") {
      callback();
    }
  },

  /**
   * This method will call the old onSuccess callback when using the old API: Ember.OAuth2.reopen({ onSuccess: function() { return 'hello, onSuccess' } });
   *
   * The old onSuccess method will only be called when onSuccess is defined as a function on the Ember.OAuth2 instance
   *
   * @method _onSuccess
   * @param {Object} stateObj
   * @private
   */
  _onSuccess: function(stateObj) {
    if (typeof(this.onSuccess) !== 'function')
      return;

    Ember.Logger.warn("Ember.OAuth2.onSuccess is deprecated and will be removed in version 0.5.0. Bind your callbacks using .on('success', fn) instead.");
    this.onSuccess(stateObj);
  },

  /**
   * This method will call the old onError callback when using the old API: Ember.OAuth2.reopen({ onError: function() { return 'hello, onError' } });
   *
   * The old onSuccess method will only be called when onError is defined as a function on the Ember.OAuth2 instance
   *
   * @method _onError
   * @param {Object} err object
   * @private
   */
  _onError: function(err) {
    if (typeof(this.onError) !== 'function')
      return;

    Ember.Logger.warn("Ember.OAuth2.onError is deprecated and will be removed in version 0.5.0. Bind your callbacks using .on('error', fn) instead.");
    this.onError(err);
  },

  /**
   * Checks if the State returned from the server matches the state that was generated in the original request and saved in the browsers localStorage.
   *
   * @method checkState
   * @param {Object} stateObj The object returned from localStorage
   */
  checkState: function(stateObj) {
    if (!stateObj) throw new Error("Could not find state.");
    if (stateObj.state !== this.get('state')) throw new Error("State returned from the server did not match the local saved state.");
  },

  /**
   * Parse the callback function from the OAuth2 provider
   *
   * callback should have the following params if authentication is successful
   * state
   * access_token
   * token_type
   * expires_in
   *
   * @method parseCalback
   * @param {String} locationHash
   * @return {Object} The params returned from the OAuth2 provider
   */
  parseCallback: function(locationHash) {
    var oauthParams = {};
    var queryString = locationHash.substring(1);
    var regex = /([^#?&=]+)=([^&]*)/g;
    var match;
    while ((match = regex.exec(queryString)) !== null) {
      oauthParams[decodeURIComponent(match[1])] = decodeURIComponent(match[2]);
    }
    return oauthParams;
  },

  /**
   * @method saveState
   * @param {String} state The state uuid
   * @param {Object} requestObj Properties of the request state to save in localStorage
   */
  saveState: function(state, requestObj) {
    window.localStorage.setItem(this.get('statePrefix') + '-' + state, JSON.stringify(requestObj));
  },

  /**
   * Return the saved state and remove it from the localStoage.
   *
   * @method getState
   * @param {String} state The state uuid to retreive from localStorage
   * @return {Object} Properties of the request state
   */
  getState: function(state) {
    var obj = JSON.parse(window.localStorage.getItem(this.get('statePrefix') + '-'  + state));
    window.localStorage.removeItem(this.get('statePrefix') + '-' + state);

    return obj;
  },

  /**
    * Remove any states from localStorage if they exist
    * @method clearStates
    * @return {Array} Keys used to remove states from localStorage
    */
  clearStates: function() {
    var regex = new RegExp( '^' + this.get('statePrefix') + '-.*', 'g');

    var toRemove = [];
    for(var i = 0, l = window.localStorage.length; i < l; i++) {
      var name = window.localStorage.key(i);
      if (name.match(regex)) {
        toRemove.push(name);
      }
    }

    for(var i = 0, l = toRemove.length; i < l; i++) {
      var name = toRemove[i];
      window.localStorage.removeItem(name);
    }
    return toRemove;
  },

  /**
   * saveToken stores the token by the tokenPrefix and the providerId
   * access_token
   * expires : time that the token expires
   * providerId: the providerId
   * scopes: array of scopes
   *
   * @method saveToken
   * @param {Object} token Saves the params in the response from the OAuth2 server to localStorage with the key 'tokenPrefix-providerId
   */
  saveToken: function(token) {
    window.localStorage.setItem(this.get('tokenPrefix') + '-' + this.get('providerId'), JSON.stringify(token));
  },

  /**
   * @method getToken
   * @return {Object} The params from the OAuth2 response from localStorage with the key 'tokenPrefix-providerId'.
   */
  getToken: function() {
    var token = JSON.parse(window.localStorage.getItem( this.get('tokenPrefix') + '-' + this.get('providerId')));
    if (!token) return null;
    if (!token.access_token) return null;
    return token;
  },

  /**
   * @method getAccessToken
   * @return {Object} The access_token param from the OAuth2 response from localStorage with the key 'tokenPrefix-providerId'.
   */
  getAccessToken: function() {
    var token = this.getToken();
    if (!token) return null;
    return token.access_token;
  },

  /**
   * @method accessTokenIsExpired
   * @return {Boolean} Check if the access_token is expired.
   */
  accessTokenIsExpired: function() {
    var token = this.getToken();
    if (!token) return true;
    if (this.now() >= token.expires_in) {
      return true;
    } else {
      return false;
    }
  },

  /**
   * Sets the access token expires_in time to 0 and saves the token to localStorage
   * @method expireAccessToken
   */
  expireAccessToken: function() {
    var token = this.getToken();
    if (!token) return null;
    token.expires_in = 0;
    this.saveToken(token);
  }
});

/**
 * @property {String} VERSION
 * @final
*/
var VERSION = "0.4.0";

/**
 * @method version
 * @static
 */
if (Ember.OAuth2) {
 Ember.OAuth2.version = VERSION;
}
<commit_msg>fix variable scope in for loop<commit_after>import Ember from 'ember';

/**
  * @overview OAuth2 library for Emberjs that stores tokens in the browsers localStorage
  * @license   Licensed under MIT license
  *            See https://raw.github.com/amkirwan/ember-oauth2/master/LICENSE
  * @version   0.4.0
  */
export default Ember.Object.extend(Ember.Evented, {
  /**
   * Initializes the Ember.OAuth2 object when using Ember.OAuth2.create({providerId: 'providerId'}). The following options are available for the configuring a provider: clientId, authBaseUri, redirectUri, scope, statePrefix and tokenPrefix. The clientId, authBaseUri and redirectUri are required. The statePrefix has a default value of 'state' and the tokenPrefix has a default value of 'token'.
   *
   * @method init
   */
  init: function() {
    this._super();
    /**
     * The configuration object for the given provider id.
     *  @property {Object} providerConfig
     *  @property {Object} providerConfig.providerId **Required**
     *  @property {String} providerConfig.providerId.clientId **Required**
     *  @property {String} providerConfig.providerId.authBaseUri **Required**
     *  @property {String} providerConfig.providerId.redirectUri **Required**
     *  @property {String} providerConfig.providerId.scope **Optional**
     *  @property {String} providerConfig.providerId.statePrefix **Default:** "state", **Optional**
     *  @property {String} providerConfig.providerId.tokenPrefix **Default:** "prefix", **Optional**
     *  @example
     *    App.oauth = Ember.OAuth2.create({providerId: 'google'});
     */

    // if Ember.OAuth2.config has keys use it instead of window.ENV
    if (Ember.OAuth2 && Ember.OAuth2.config && Object.keys(Ember.OAuth2.config).length) {
      Ember.Logger.warn("Ember.OAuth2.config is deprecated and will be removed in version 0.5.0. Set the config using window.ENV['ember-oauth2']");
      this.providerConfig = Ember.OAuth2.config[this.get('providerId')];
    } else {
      this.providerConfig = window.ENV['ember-oauth2'][this.get('providerId')];
    }
    /**
     * The prefix name for the state key stored in the localStorage.
     *
     * @property statePrefix
     * @type String
     * @default "state"
     */
    this.set('statePrefix', 'state');
    /**
     * The prefix name for the token key stored in the localStorage.
     *
     * @property tokenPrefix
     * @type String
     * @default "token"
     */
    this.set('tokenPrefix', 'token');

    /**
     * @property {String} clientId
     */
    /**
     * @property {String} authBaseUri
     */
    /**
     * @property {String} redirectUri
     */
    /**
     * @property {String} scope
     */
    // sets the properties from the providerConfig and overrides any default settings.
    this.setProperties(this.providerConfig);

    // Bind deprecated event handlers
    /**
     * @event redirect
     * @param {function} The function that handles the redirect.
     */
    this.on('redirect', this.handleRedirect);

    /**
     * @event success
     * @param {function} The function that handles the success callback.
     */
    this.on('success', this._onSuccess);

    /**
     * @event error
     * @param {function} The function that handles the error callback.
     */
    this.on('error', this._onError);
  },

  /**
   * @method version
   * @return {String} The current version number of Ember.OAuth2
   */
  version: function() {
    return VERSION;
  },

  /**
   * @method uuid
   * @return {String} A pseudo random uuid
   */
  uuid: function() {
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
          var r = Math.random()*16|0, v = c === 'x' ? r : (r&0x3|0x8);
              return v.toString(16);
    });
  },

  /**
   * @method now
   * @return {Number} The current time in seconds rounded
   */
  now: function() {
    return Math.round(new Date().getTime()/1000.0);
  },

  /**
   * Creates and returns the request object.
   *
   * @method requestObj
   * @return {Object} request object
   */
  requestObj: function() {
    var request = { 'response_type': 'token' };
    request.providerId = this.get('providerId');
    request.state = this.get('state');
    request.client_id = this.get('clientId');
    request.state = this.get('state');
    if (this.get('scope')) request.scope = this.get('scope');
    return request;
  },

  /**
   * @method authUri
   * @return {String} Authorization uri for generating an OAuth2 token
   */
  authUri: function() {
    if (this.get('state') === null) this.set('state', this.uuid());
    var uri = this.get('authBaseUri');
    uri += '?response_type=token' +
        '&redirect_uri=' + encodeURIComponent(this.get('redirectUri')) +
        '&client_id=' + encodeURIComponent(this.get('clientId')) +
        '&state=' + encodeURIComponent(this.get('state'));
    if (this.get('scope')) uri += '&scope=' + encodeURIComponent(this.get('.scope')).replace('%20', '+');
    return uri;
  },

  /**
   * Open authorize window if the configuration object is valid.
   *
   * @method authorize
   * @return {Promise}
   */
  authorize: function() {
    if (!this.get('providerId')) throw new Error('No provider id given.');
    if (!this.get('clientId')) throw new Error('No client id given.');
    if (!this.get('authBaseUri')) throw new Error('No auth base uri given.');
    if (!this.get('redirectUri')) throw new Error('No redirect uri given.');
    var authorizeUri = this.authUri();
    this.clearStates();
    this.saveState(this.get('state'), this.requestObj());
    return this.openWindow(authorizeUri);
  },

  /**
   * Isolated function responsible for opening windows, to make it
   * easier to override this part in some environments (e.g. Phonegap)

    @param {String} url
    @return {Object} reference to the opened window
   */
  openWindow: function(url) {
    var dialog = window.open(url, 'Authorize', 'height=600, width=450');
    if (window.focus) dialog.focus();
    var self = this;
    return new Ember.RSVP.Promise(function(resolve, reject) {
      self.one('success', function() { resolve(dialog); });
      self.one('error', function(error) {
        new Error('Dialog failed with ' + error);
        reject(dialog);
      });
    });
  },

  /**
    @method authSuccess
    @param {Object} The params returned from the OAuth2 callback
    @return {String} the access_token from the params
  */
  authSuccess: function(params) {
    return params.access_token;
  },

  /**
   * Token properties
   * providerId
   * expiresIn
   * scope
   * token

    @method generateToken
    @return {Object} The access_token object with info about the token
   */
  generateToken: function(params) {
    var token = {};
    token.provider_id = this.get('providerId');
    token.expires_in = this.expiresIn(params.expires_in);
    token.scope = this.get('scope');
    token.access_token = params.access_token;
    return token;
  },

  /**
    @method expiresIn
    @return {Number} When the token expires in seconds.
  */
  expiresIn: function(expires) {
    return this.now() + parseInt(expires, 10);
  },

  /**
   * call on redirect from OAuth2 provider response
    @method onRedirect
    @deprecated Use `.trigger('redirect')` instead.
   */
  onRedirect: function(hash, callback) {
    Ember.Logger.warn("Ember.OAuth2.onRedirect is deprecated and will be removed in future version 0.5.0. Please use .trigger('redirect') instead.");
    this.trigger('redirect', hash, callback);
  },

  /*
   * proxy functions for old event handlers
   *
   * Check if the token returned is valid and if so trigger `success` event else trigger `error`
   *
   * @method handleRedirect
   */
  handleRedirect: function(hash, callback) {
    var params = this.parseCallback(hash);
    if (this.authSuccess(params)) {
      var stateObj = this.getState(params.state);
      this.checkState(stateObj);
      this.saveToken(this.generateToken(params));
      this.trigger('success', stateObj);
    } else {
      this.trigger('error', params);
    }
    if (callback && typeof(callback) === "function") {
      callback();
    }
  },

  /**
   * This method will call the old onSuccess callback when using the old API: Ember.OAuth2.reopen({ onSuccess: function() { return 'hello, onSuccess' } });
   *
   * The old onSuccess method will only be called when onSuccess is defined as a function on the Ember.OAuth2 instance
   *
   * @method _onSuccess
   * @param {Object} stateObj
   * @private
   */
  _onSuccess: function(stateObj) {
    if (typeof(this.onSuccess) !== 'function')
      return;

    Ember.Logger.warn("Ember.OAuth2.onSuccess is deprecated and will be removed in version 0.5.0. Bind your callbacks using .on('success', fn) instead.");
    this.onSuccess(stateObj);
  },

  /**
   * This method will call the old onError callback when using the old API: Ember.OAuth2.reopen({ onError: function() { return 'hello, onError' } });
   *
   * The old onSuccess method will only be called when onError is defined as a function on the Ember.OAuth2 instance
   *
   * @method _onError
   * @param {Object} err object
   * @private
   */
  _onError: function(err) {
    if (typeof(this.onError) !== 'function')
      return;

    Ember.Logger.warn("Ember.OAuth2.onError is deprecated and will be removed in version 0.5.0. Bind your callbacks using .on('error', fn) instead.");
    this.onError(err);
  },

  /**
   * Checks if the State returned from the server matches the state that was generated in the original request and saved in the browsers localStorage.
   *
   * @method checkState
   * @param {Object} stateObj The object returned from localStorage
   */
  checkState: function(stateObj) {
    if (!stateObj) throw new Error("Could not find state.");
    if (stateObj.state !== this.get('state')) throw new Error("State returned from the server did not match the local saved state.");
  },

  /**
   * Parse the callback function from the OAuth2 provider
   *
   * callback should have the following params if authentication is successful
   * state
   * access_token
   * token_type
   * expires_in
   *
   * @method parseCalback
   * @param {String} locationHash
   * @return {Object} The params returned from the OAuth2 provider
   */
  parseCallback: function(locationHash) {
    var oauthParams = {};
    var queryString = locationHash.substring(1);
    var regex = /([^#?&=]+)=([^&]*)/g;
    var match;
    while ((match = regex.exec(queryString)) !== null) {
      oauthParams[decodeURIComponent(match[1])] = decodeURIComponent(match[2]);
    }
    return oauthParams;
  },

  /**
   * @method saveState
   * @param {String} state The state uuid
   * @param {Object} requestObj Properties of the request state to save in localStorage
   */
  saveState: function(state, requestObj) {
    window.localStorage.setItem(this.get('statePrefix') + '-' + state, JSON.stringify(requestObj));
  },

  /**
   * Return the saved state and remove it from the localStoage.
   *
   * @method getState
   * @param {String} state The state uuid to retreive from localStorage
   * @return {Object} Properties of the request state
   */
  getState: function(state) {
    var obj = JSON.parse(window.localStorage.getItem(this.get('statePrefix') + '-'  + state));
    window.localStorage.removeItem(this.get('statePrefix') + '-' + state);

    return obj;
  },

  /**
    * Remove any states from localStorage if they exist
    * @method clearStates
    * @return {Array} Keys used to remove states from localStorage
    */
  clearStates: function() {
    var regex = new RegExp( '^' + this.get('statePrefix') + '-.*', 'g');

    var name;
    var toRemove = [];
    for(var i = 0, l = window.localStorage.length; i < l; i++) {
      name = window.localStorage.key(i);
      if (name.match(regex)) {
        toRemove.push(name);
      }
    }

    for(var j = 0, len = toRemove.length; j < len; j++) {
      name = toRemove[j];
      window.localStorage.removeItem(name);
    }
    return toRemove;
  },

  /**
   * saveToken stores the token by the tokenPrefix and the providerId
   * access_token
   * expires : time that the token expires
   * providerId: the providerId
   * scopes: array of scopes
   *
   * @method saveToken
   * @param {Object} token Saves the params in the response from the OAuth2 server to localStorage with the key 'tokenPrefix-providerId
   */
  saveToken: function(token) {
    window.localStorage.setItem(this.get('tokenPrefix') + '-' + this.get('providerId'), JSON.stringify(token));
  },

  /**
   * @method getToken
   * @return {Object} The params from the OAuth2 response from localStorage with the key 'tokenPrefix-providerId'.
   */
  getToken: function() {
    var token = JSON.parse(window.localStorage.getItem( this.get('tokenPrefix') + '-' + this.get('providerId')));
    if (!token) return null;
    if (!token.access_token) return null;
    return token;
  },

  /**
   * @method getAccessToken
   * @return {Object} The access_token param from the OAuth2 response from localStorage with the key 'tokenPrefix-providerId'.
   */
  getAccessToken: function() {
    var token = this.getToken();
    if (!token) return null;
    return token.access_token;
  },

  /**
   * @method accessTokenIsExpired
   * @return {Boolean} Check if the access_token is expired.
   */
  accessTokenIsExpired: function() {
    var token = this.getToken();
    if (!token) return true;
    if (this.now() >= token.expires_in) {
      return true;
    } else {
      return false;
    }
  },

  /**
   * Sets the access token expires_in time to 0 and saves the token to localStorage
   * @method expireAccessToken
   */
  expireAccessToken: function() {
    var token = this.getToken();
    if (!token) return null;
    token.expires_in = 0;
    this.saveToken(token);
  }
});

/**
 * @property {String} VERSION
 * @final
*/
var VERSION = "0.4.0";

/**
 * @method version
 * @static
 */
if (Ember.OAuth2) {
 Ember.OAuth2.version = VERSION;
}
<|endoftext|>


<commit_before>// Copyright, 2013-2014, by Tomas Korcak. <korczis@gmail.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

(function () {
    'use strict';

    var define = require('amdefine')(module);

    /**
     * Array of modules this one depends on.
     * @type {Array}
     */
    var deps = [
        'cheerio',
        'deferred',
        'events',
        'fs',
        'minimist',
        'path',
        'util',
        '../helpers',
        '../request'
    ];

    define(deps, function(cheerio, deferred, Events, fs, minimist, path, util, Helpers, Request) {

        /**
         * Engine Class
         * @class
         */
        function Engine(opts) {
            this.opts = opts || Engine.defaultOptions;

            this.queue = {
                requested: [],
                processing: [],
                done: [],
                failed: []
            };

            this.processors = {};
        }

        util.inherits(Engine, Events.EventEmitter);

        Engine.defaultOptions = {
            // TODO: Add more default options here
        };

        /**
         * Enqueue new URL and specify processor to parse page (and optionally the data passed)
         *
         * Procedure is following:
         *
         * * Checks if wasAlreadyEnqueued() and returns if co
         * * Generates boxing structure wih following info
         *   * guid - generated using Helpers.guid()
         *   * url - url to be processed
         *   * processor - name of previously registered processor to parse results
         *
         * @param url Url to be enqueued
         * @param processor Name of processor to be used for processing result page
         * @param data Optional data to pass to processor
         * @returns {boolean} True if newly queued, false if already queued (or processing, done, failed ...)
         */
        Engine.prototype.enqueueUrl = function(url, processor, data) {
            if(this.wasAlreadyEnqueued(url, processor)) {
                return false;
            }

            var item = {
                guid: Helpers.guid(),
                url: url,
                processor: processor
            };

            if(data) {
                item.data = data;
            }

            this.queue.requested.push(item);
            return true;
        };

        /**
         * Process finished request
         *
         * It means following:
         *
         * * Remove item from this.queue.processing
         * * Add item to this.queue.done
         *
         * @param item Item to be moved from this.queue.processing to this.queue.done
         * @returns {boolean} True if the move happened
         */
        Engine.prototype.finishRequested = function(item) {
            for(var i = 0; i < this.queue.processing.length; i++) {
                var tmp = this.queue.processing[i];
                if(tmp.guid !== item.guid) {
                    continue;
                }

                this.queue.processing.splice(i, 1);
                this.queue.done.push(item);
                return true;
            }

            return false;
        };

        /**
         * Returns true if all request were processed
         *
         * It means following:
         *
         * * this.queue.requested is empty
         * * this.queue.processing is empty
         *
         * TODO: Decide how to handle failed items
         *
         * @returns {boolean} True if everything was processed
         */
        Engine.prototype.isDone = function() {
            return this.queue.requested.length === 0 &&
                this.queue.processing.length === 0;
        };

        /**
         * Loads and register processor from path (dir)
         *
         * The name of processor is generated in following way:
         *
         * * The extension is stripped
         * * path.sep is converted to dot ('.')
         *
         * Example filename to processor name transformation:
         *
         * 'google/listing.js' -> 'google.listing'
         *
         * @param pathDir Dir with processors (each in own folder) to be loaded
         * @returns {*} Promise
         */
        Engine.prototype.loadProcessors = function(pathDir) {
            var d = deferred();

            var self = this;
            Helpers.walk(pathDir, function(err, results) {
                if (err) {
                    d.reject(err);
                }

                var processors = [];

                for(var i = 0; i < results.length; i++) {
                    var fullPath = results[i];
                    var name = path.relative(pathDir, fullPath)
                        .replace(path.extname(fullPath), '')
                        .replace(path.sep, '.');

                    self.registerProcessor(name, require(fullPath));

                    processors.push({
                        name: name,
                        path: fullPath,
                        processor: require(fullPath)
                    });
                }

                d.resolve(processors);
            });

            return d.promise();
        };

        /**
         * Main CLI function
         *
         * Workflow is following:
         *
         * * Parse CLI arguments
         * * Process requested URLS
         *
         * @returns {*} Promise which is resolved when all work is done
         */
        Engine.prototype.main = function() {
            var argv = minimist(process.argv.slice(2));

            if(argv.l) {
                var loadPaths = null;
                if(Object.prototype.toString.call(argv.l) !== '[object Array]') {
                    loadPaths = [argv.l];
                } else {
                    loadPaths = argv.l;
                }

                console.log(loadPaths);
                for(var i = 0; i < loadPaths.length; i++) {
                    this.loadProcessors(path.resolve(loadPaths[i]));
                }
            }

            for(var j = 0; j < argv._.length; j++) {
                var url = argv._[j];

                this.enqueueUrl(url, argv.p, null);
            }

            // Now just launch the engine and wait for results
            return this.run();
        };

        Engine.prototype.registerProcessor = function(name, processor) {
            if(!name) {
                throw new TypeError('name must be specified');
            }

            this.processors[name] = processor;
            return processor;
        };

        /**
         * Process this.queue.requested urls
         *
         * It means following:
         *
         * * Iterate over all this.queue.requested.items
         * * Remove from this.queue.requested
         * * processRequested() each of them - will add item to this.queue.processing
         *
         * @returns {number} Number of processed items
         */
        Engine.prototype.process = function() {
            var processed = 0;

            while(this.queue.requested.length > 0) {
                var item = this.queue.requested.shift();
                this.processRequested(item);
                processed++;
            }

            return processed;
        };

        /**
         * Processes one requested URL/item
         *
         * It means following:
         *
         * * Push to this.queue.processing
         * * Do deferred HTTP request of item.url and than
         * * Load data to cheerio
         * * Call processor specified (ie, yelp.details)
         * * this.processFinishedResults()
         * * this.finishRequestedItem() - move from this.queue.processing -> this.queue.done
         *
         * @param item Item requested to be processed
         */
        Engine.prototype.processRequested = function(item) {
            this.queue.processing.push(item);

            var self = this;
            Request.request(item.url).then(function(data) {
                var doc = cheerio.load(data);
                var res = self.processors[item.processor](doc, item);
                self.processFinishedResults(res);
                self.finishRequested(item);
            }).done(function() {

            }, function(err) {
                console.log("ERROR: " + err);
            });
        };

        /**
         * Processes results returned by registered and used page processor
         *
         * It means following:
         *
         * * All results are iterated
         * * Results of {type: 'url'} are enqueued if not wasAlreadyEnqueued()
         * * Results of {type: 'data'} are emitted using EventEmitter
         *
         * @param results Results to be processed
         */
        Engine.prototype.processFinishedResults = function(results) {
            for(var i = 0; i < results.length; i++) {
                var result = results[i];
                if(result.type === 'data') {
                    this.emit('data', result);
                } else if(result.type === 'url') {
                    this.enqueueUrl(result.url, result.processor, result.data);
                }
            }
        };

        /**
         * Kick of first tick and keep running until necessary
         *
         * @returns {*} Promise to be resolved when done
         */
        Engine.prototype.run = function() {
            var d = deferred();

            this.scheduleTick(d);

            return d.promise();
        };

        /**
         * Schedule next tick
         * @param d Promise to be passed to tick function
         */
        Engine.prototype.scheduleTick = function(d) {
            var self = this;
            setTimeout(function() {
                self.tick(d);
            }, 10);
        };

        /**
         * Performs one engine tick
         *
         * It means following:
         *
         * * Check if everything isDone()
         * * Resolves promise if done
         * * Schedules next processing tick if not done
         *
         *  @param d Promise
         */
        Engine.prototype.tick = function(d) {
            this.process();

            if(this.isDone()) {
                d.resolve(true);
            } else {
                this.scheduleTick(d);
            }
        };

        /**
         * Checks if specified combination of url and processor was already enqueued
         * @param url Url to be enqueued
         * @param processor Processor to be used for processing url
         * @returns {boolean} True if already enqueued (or processing, or done ..)
         */
        Engine.prototype.wasAlreadyEnqueued = function(url, processor) {
            var queues = [
                this.queue.requested,
                this.queue.processing,
                this.queue.done,
                this.queue.failed
            ];

            // TODO: This is *VERY* naive implementation with unnecessary O(n) complexity
            //       It will be much better if there will be some parallel and synchronized
            //       container with better access complexity. Not important for now. (21/4/2014)
            for(var i = 0; i < queues.length; i++) {
                var queue = queues[i];
                for(var j = 0; j < queue.length; j++) {
                    var item = queue[j];
                    if(item.url === url && item.processor === processor) {
                        return true;
                    }
                }
            }

            return false;
        };

        // Export Engine
        module.exports = Engine;
    });
}());
<commit_msg>Support for argv to be passed from lib user<commit_after>// Copyright, 2013-2014, by Tomas Korcak. <korczis@gmail.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

(function () {
    'use strict';

    var define = require('amdefine')(module);

    /**
     * Array of modules this one depends on.
     * @type {Array}
     */
    var deps = [
        'cheerio',
        'deferred',
        'events',
        'fs',
        'minimist',
        'path',
        'util',
        '../helpers',
        '../request'
    ];

    define(deps, function(cheerio, deferred, Events, fs, minimist, path, util, Helpers, Request) {

        /**
         * Engine Class
         * @class
         */
        function Engine(opts) {
            this.opts = opts || Engine.defaultOptions;

            this.queue = {
                requested: [],
                processing: [],
                done: [],
                failed: []
            };

            this.processors = {};
        }

        util.inherits(Engine, Events.EventEmitter);

        Engine.defaultOptions = {
            // TODO: Add more default options here
        };

        /**
         * Enqueue new URL and specify processor to parse page (and optionally the data passed)
         *
         * Procedure is following:
         *
         * * Checks if wasAlreadyEnqueued() and returns if co
         * * Generates boxing structure wih following info
         *   * guid - generated using Helpers.guid()
         *   * url - url to be processed
         *   * processor - name of previously registered processor to parse results
         *
         * @param url Url to be enqueued
         * @param processor Name of processor to be used for processing result page
         * @param data Optional data to pass to processor
         * @returns {boolean} True if newly queued, false if already queued (or processing, done, failed ...)
         */
        Engine.prototype.enqueueUrl = function(url, processor, data) {
            if(this.wasAlreadyEnqueued(url, processor)) {
                return false;
            }

            var item = {
                guid: Helpers.guid(),
                url: url,
                processor: processor
            };

            if(data) {
                item.data = data;
            }

            this.queue.requested.push(item);
            return true;
        };

        /**
         * Process finished request
         *
         * It means following:
         *
         * * Remove item from this.queue.processing
         * * Add item to this.queue.done
         *
         * @param item Item to be moved from this.queue.processing to this.queue.done
         * @returns {boolean} True if the move happened
         */
        Engine.prototype.finishRequested = function(item) {
            for(var i = 0; i < this.queue.processing.length; i++) {
                var tmp = this.queue.processing[i];
                if(tmp.guid !== item.guid) {
                    continue;
                }

                this.queue.processing.splice(i, 1);
                this.queue.done.push(item);
                return true;
            }

            return false;
        };

        /**
         * Returns true if all request were processed
         *
         * It means following:
         *
         * * this.queue.requested is empty
         * * this.queue.processing is empty
         *
         * TODO: Decide how to handle failed items
         *
         * @returns {boolean} True if everything was processed
         */
        Engine.prototype.isDone = function() {
            return this.queue.requested.length === 0 &&
                this.queue.processing.length === 0;
        };

        /**
         * Loads and register processor from path (dir)
         *
         * The name of processor is generated in following way:
         *
         * * The extension is stripped
         * * path.sep is converted to dot ('.')
         *
         * Example filename to processor name transformation:
         *
         * 'google/listing.js' -> 'google.listing'
         *
         * @param pathDir Dir with processors (each in own folder) to be loaded
         * @returns {*} Promise
         */
        Engine.prototype.loadProcessors = function(pathDir) {
            var d = deferred();

            var self = this;
            Helpers.walk(pathDir, function(err, results) {
                if (err) {
                    d.reject(err);
                }

                var processors = [];

                for(var i = 0; i < results.length; i++) {
                    var fullPath = results[i];
                    var name = path.relative(pathDir, fullPath)
                        .replace(path.extname(fullPath), '')
                        .replace(path.sep, '.');

                    self.registerProcessor(name, require(fullPath));

                    processors.push({
                        name: name,
                        path: fullPath,
                        processor: require(fullPath)
                    });
                }

                d.resolve(processors);
            });

            return d.promise();
        };

        /**
         * Main CLI function
         *
         * Workflow is following:
         *
         * * Parse CLI arguments
         * * Process requested URLS
         *
         * @returns {*} Promise which is resolved when all work is done
         */
        Engine.prototype.main = function(passedArgv) {
            if(!passedArgv) {
                passedArgv = process.argv.slice(2);
            }
            var argv = minimist(passedArgv);

            if(argv.l) {
                var loadPaths = null;
                if(Object.prototype.toString.call(argv.l) !== '[object Array]') {
                    loadPaths = [argv.l];
                } else {
                    loadPaths = argv.l;
                }

                console.log(loadPaths);
                for(var i = 0; i < loadPaths.length; i++) {
                    this.loadProcessors(path.resolve(loadPaths[i]));
                }
            }

            for(var j = 0; j < argv._.length; j++) {
                var url = argv._[j];

                this.enqueueUrl(url, argv.p, null);
            }

            // Now just launch the engine and wait for results
            return this.run();
        };

        Engine.prototype.registerProcessor = function(name, processor) {
            if(!name) {
                throw new TypeError('name must be specified');
            }

            this.processors[name] = processor;
            return processor;
        };

        /**
         * Process this.queue.requested urls
         *
         * It means following:
         *
         * * Iterate over all this.queue.requested.items
         * * Remove from this.queue.requested
         * * processRequested() each of them - will add item to this.queue.processing
         *
         * @returns {number} Number of processed items
         */
        Engine.prototype.process = function() {
            var processed = 0;

            while(this.queue.requested.length > 0) {
                var item = this.queue.requested.shift();
                this.processRequested(item);
                processed++;
            }

            return processed;
        };

        /**
         * Processes one requested URL/item
         *
         * It means following:
         *
         * * Push to this.queue.processing
         * * Do deferred HTTP request of item.url and than
         * * Load data to cheerio
         * * Call processor specified (ie, yelp.details)
         * * this.processFinishedResults()
         * * this.finishRequestedItem() - move from this.queue.processing -> this.queue.done
         *
         * @param item Item requested to be processed
         */
        Engine.prototype.processRequested = function(item) {
            this.queue.processing.push(item);

            var self = this;
            Request.request(item.url).then(function(data) {
                var doc = cheerio.load(data);
                var res = self.processors[item.processor](doc, item);
                self.processFinishedResults(res);
                self.finishRequested(item);
            }).done(function() {

            }, function(err) {
                console.log("ERROR: " + err);
            });
        };

        /**
         * Processes results returned by registered and used page processor
         *
         * It means following:
         *
         * * All results are iterated
         * * Results of {type: 'url'} are enqueued if not wasAlreadyEnqueued()
         * * Results of {type: 'data'} are emitted using EventEmitter
         *
         * @param results Results to be processed
         */
        Engine.prototype.processFinishedResults = function(results) {
            for(var i = 0; i < results.length; i++) {
                var result = results[i];
                if(result.type === 'data') {
                    this.emit('data', result);
                } else if(result.type === 'url') {
                    this.enqueueUrl(result.url, result.processor, result.data);
                }
            }
        };

        /**
         * Kick of first tick and keep running until necessary
         *
         * @returns {*} Promise to be resolved when done
         */
        Engine.prototype.run = function() {
            var d = deferred();

            this.scheduleTick(d);

            return d.promise();
        };

        /**
         * Schedule next tick
         * @param d Promise to be passed to tick function
         */
        Engine.prototype.scheduleTick = function(d) {
            var self = this;
            setTimeout(function() {
                self.tick(d);
            }, 10);
        };

        /**
         * Performs one engine tick
         *
         * It means following:
         *
         * * Check if everything isDone()
         * * Resolves promise if done
         * * Schedules next processing tick if not done
         *
         *  @param d Promise
         */
        Engine.prototype.tick = function(d) {
            this.process();

            if(this.isDone()) {
                d.resolve(true);
            } else {
                this.scheduleTick(d);
            }
        };

        /**
         * Checks if specified combination of url and processor was already enqueued
         * @param url Url to be enqueued
         * @param processor Processor to be used for processing url
         * @returns {boolean} True if already enqueued (or processing, or done ..)
         */
        Engine.prototype.wasAlreadyEnqueued = function(url, processor) {
            var queues = [
                this.queue.requested,
                this.queue.processing,
                this.queue.done,
                this.queue.failed
            ];

            // TODO: This is *VERY* naive implementation with unnecessary O(n) complexity
            //       It will be much better if there will be some parallel and synchronized
            //       container with better access complexity. Not important for now. (21/4/2014)
            for(var i = 0; i < queues.length; i++) {
                var queue = queues[i];
                for(var j = 0; j < queue.length; j++) {
                    var item = queue[j];
                    if(item.url === url && item.processor === processor) {
                        return true;
                    }
                }
            }

            return false;
        };

        // Export Engine
        module.exports = Engine;
    });
}());
<|endoftext|>


<commit_before>
// Express - Core - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)

;(function(){

  process.mixin(GLOBAL, require('sys'))

  // --- Route

  Route = Class({
    init: function(method, path, fn, options){
      this.method = method
      this.path = path
      this.fn = fn
    }
  })

  // --- Server

  Server = Class({
    port: 3000,
    host: 'localhost',
    start: function(host, port){
      var self = this
      if (host) this.host = host
      if (port) this.port = port
      require('http')
        .createServer(function(request, response){
          self.request = request
          (self.response = response).headers = {}
          request.addListener('body', function(chunk){ request.body += chunk })
          request.addListener('complete', function(){ self.route() })
        })
        .listen(this.port, this.host)
      puts('Express started at http://' + this.host + ':' + this.port + '/')
    },
    route: function(){
      if (typeof (this.response.body = route(request)) == 'string')
        this.respond()
    },
    respond: function(body){
      if (body) this.response.body = body
      this.response.sendHeader(200, this.response.headers)
      this.response.sendBody(this.response.body || '')
      this.response.finish()
    }
  })

  // --- Express

  Express = {
    version: '0.0.1',
    routes: [],
    server: new Server
  }

  // --- Utilities

  /**
   * Return a routing function for _method_.
   *
   * @param  {string} method
   * @return {function}
   * @api private
   */

  function route(method) {
    return function(path, options, fn){
      if (options instanceof Function)
        callback = options, options = {}
      Express.routes.push(new Route(method, path, fn, options))
    }
  }

  // --- Routing API

  get = view = route('get')
  post = create = route('post')
  del = destroy = route('delete')
  put = update = route('put')

})()<commit_msg>createServer callback does not like referencing the server instance<commit_after>
// Express - Core - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)

;(function(){

  process.mixin(GLOBAL, require('sys'))

  // --- Route

  Route = Class({
    init: function(method, path, fn, options){
      this.method = method
      this.path = path
      this.fn = fn
    }
  })

  // --- Router

  Router = Class({
    route: function(request){
      return 'test'
    }
  })

  // --- Server

  Server = Class({
    port: 3000,
    host: 'localhost',
    router: new Router,
    start: function(host, port){
      var self = this
      if (host) this.host = host
      if (port) this.port = port
      require('http')
        .createServer(function(request, response){
          response.headers = {}
          request.addListener('body', function(chunk){ request.body += chunk })
          request.addListener('complete', function(){ self.route(request, response) })
        })
        .listen(this.port, this.host)
      puts('Express started at http://' + this.host + ':' + this.port + '/')
    },
    route: function(request, response){
      this.request = request, this.response = response
      if (typeof (this.response.body = this.router.route(request)) == 'string')
        this.respond()
    },
    respond: function(body){
      if (body) this.response.body = body
      this.response.sendHeader(200, this.response.headers)
      this.response.sendBody(this.response.body || '')
      this.response.finish()
    }
  })

  // --- Express

  Express = {
    version: '0.0.1',
    routes: [],
    server: new Server
  }

  // --- Utilities

  /**
   * Return a routing function for _method_.
   *
   * @param  {string} method
   * @return {function}
   * @api private
   */

  function route(method) {
    return function(path, options, fn){
      if (options instanceof Function)
        callback = options, options = {}
      Express.routes.push(new Route(method, path, fn, options))
    }
  }

  // --- Routing API

  get = view = route('get')
  post = create = route('post')
  del = destroy = route('delete')
  put = update = route('put')

})()<|endoftext|>


<commit_before>var https = require('https');
var http = require('http');
var mime = require('mime');
var _ = require('underscore');
var url = require('url');
var fs = require('fs');

function FileHandler(file_object) {
  this.filename = file_object.filename;

  if (file_object.contentType) {
    this.contentType = file_object.contentType;
  }

  if (file_object.content) {
    this.type = 'content';
    this.content = file_object.content;
    if (!this.contentType) {
      this.contentType = mime.lookup(this.filename);
    }
  } else if (file_object.path) {
    this.type = 'path';
    this.path = file_object.path;
    if (_.isEmpty(file_object.contentType)) {
      this.contentType = mime.lookup(this.path);
    }
  } else if (file_object.url) {
    this.type = 'url';
    this.url = file_object.url;
    if (!this.contentType) {
      this.contentType = mime.lookup(this.url);
    }
  } else {
    this.type = 'none';
    this.contentType = 'none';
  }
}

FileHandler.handlers = {
  content: function(file, callback) {
    if (file.content) {
      callback(true);
    } else {
      callback(false, 'Content was missing');
    }
  },
  path: function(file, callback) {
    fs.readFile(file.path, function(error, data) {
      if (error) {
        return callback(false, error);
      } else {
        file.content = data;
        callback(true);
      }
    });
  },
  url: function(file, callback) {
    var urlParts = url.parse(file.url);
    var host = urlParts.host;
    var path = urlParts.path;

    // starting http request
    var req = http.request({host: host, path: path, method: 'GET'}, function(res) {
      // what follows is logic for handing the file being returned
      var bufferArray = [];
      var totalBufferSize = 0;
      res.setEncoding('base64');
      res.on('data', function(chunk) {
        totalBufferSize += Buffer.byteLength(chunk);
        bufferArray.push(chunk);
      });

      res.on('end', function() {
        var buffer = new Buffer(totalBufferSize, 'base64');
        var bufPos = 0;
        _(bufferArray).each(function(val) {
          bufPos += buffer.write(val, bufPos, 'base64');
        });
        file.content = buffer;
        callback(true);
      });
    });

    req.on('error', function(error) {
      callback(false, error);
    });

    req.end();
  },
  none: function(file, callback) {
    callback(false, 'File was not included');
  }
};

FileHandler.prototype.loadContent = function(callback) {
  var handler = FileHandler.handlers[this.type];
  if (handler) {
    FileHandler.handlers[this.type](this, callback);
  } else {
    callback(false, 'Upload method not supported');
  }
}

module.exports = FileHandler;
<commit_msg>callbacks return an error as the first argument<commit_after>var https = require('https');
var http = require('http');
var mime = require('mime');
var _ = require('underscore');
var url = require('url');
var fs = require('fs');

function FileHandler(file_object) {
  this.filename = file_object.filename;

  if (file_object.contentType) {
    this.contentType = file_object.contentType;
  }

  if (file_object.content) {
    this.type = 'content';
    this.content = file_object.content;
    if (!this.contentType) {
      this.contentType = mime.lookup(this.filename);
    }
  } else if (file_object.path) {
    this.type = 'path';
    this.path = file_object.path;
    if (_.isEmpty(file_object.contentType)) {
      this.contentType = mime.lookup(this.path);
    }
  } else if (file_object.url) {
    this.type = 'url';
    this.url = file_object.url;
    if (!this.contentType) {
      this.contentType = mime.lookup(this.url);
    }
  } else {this
    this.type = 'none';
    this.contentType = 'none';
  }
}

FileHandler.handlers = {
  content: function(file, callback) {
    if (file.content) {
      callback();
    } else {
      callback(true, 'Content was missing');
    }
  },
  path: function(file, callback) {
    fs.readFile(file.path, function(error, data) {
      if (error) {
        return callback(true, error);
      } else {
        file.content = data;
        callback();
      }
    });
  },
  url: function(file, callback) {
    var urlParts = url.parse(file.url);
    var host = urlParts.host;
    var path = urlParts.path;

    // starting http request
    var req = http.request({host: host, path: path, method: 'GET'}, function(res) {
      // what follows is logic for handing the file being returned
      var bufferArray = [];
      var totalBufferSize = 0;
      res.setEncoding('base64');
      res.on('data', function(chunk) {
        totalBufferSize += Buffer.byteLength(chunk);
        bufferArray.push(chunk);
      });

      res.on('end', function() {
        var buffer = new Buffer(totalBufferSize, 'base64');
        var bufPos = 0;
        _(bufferArray).each(function(val) {
          bufPos += buffer.write(val, bufPos, 'base64');
        });
        file.content = buffer;
        callback();
      });
    });

    req.on('error', function(error) {
      callback(true, error);
    });

    req.end();
  },
  none: function(file, callback) {
    callback(true, 'File was not included');
  }
};

FileHandler.prototype.loadContent = function(callback) {
  var handler = FileHandler.handlers[this.type];
  if (handler) {
    FileHandler.handlers[this.type](this, callback);
  } else {
    callback(true, 'Upload method not supported');
  }
}

module.exports = FileHandler;
<|endoftext|>


<commit_before>const moment = require('moment');

function formatDate(date) {
  const year = date.getFullYear();
  let month = date.getMonth() + 1;
  let day = date.getDate();
  const MIN_MONTH_WITH_TWO_DIGITS = 10;

  if (month < MIN_MONTH_WITH_TWO_DIGITS) {
    month = `0${month}`;
  }

  if (day < MIN_MONTH_WITH_TWO_DIGITS) {
    day = `0${day}`;
  }

  return year + '-' + month + '-' + day;
}

function getDatesInRange(first, last) {
  const start = moment(first);
  const end = moment(last);
  const dates = [formatDate(start.toDate())];

  function getNextDate(date) {
    const next = moment(date.toDate()).add(1, 'days');
    dates.push(formatDate(next.toDate()));

    if (next < end) {
      getNextDate(next);
    }
  }

  getNextDate(start);

  return dates;
}

function format(rows) {
  const result = rows.reduce(function(prev, row) {
    prev[formatDate(row.created)] = row.value;
    return prev;
  }, {});

  const keys = Object.keys(result);
  const dates = getDatesInRange(keys[0], keys[keys.length - 1]);

  const fixedResults = dates.reduce(function(prev, date) {
    const value = result[date] || prev[prev.length - 1].value;

    prev.push({
      created: date,
      value: value
    });

    return prev;
  }, []);

  return fixedResults;
}

module.exports = format;
<commit_msg>Fix error in server.js<commit_after>const moment = require('moment');

function formatDate(date) {
  const year = date.getFullYear();
  var month = date.getMonth() + 1; // eslint-disable-line no-var
  var day = date.getDate(); // eslint-disable-line no-var

  if (month < 10) {
    month = `0${month}`;
  }

  if (day < 10) {
    day = `0${day}`;
  }

  return `${year}-${month}-${day}`;
}

function getDatesInRange(first, last) {
  const start = moment(first);
  const end = moment(last);
  const dates = [formatDate(start.toDate())];

  function getNextDate(date) {
    const next = moment(date.toDate()).add(1, 'days');
    dates.push(formatDate(next.toDate()));

    if (next < end) {
      getNextDate(next);
    }
  }

  getNextDate(start);

  return dates;
}

function format(rows) {
  const result = rows.reduce(function(prev, row) {
    prev[formatDate(row.created)] = row.value;
    return prev;
  }, {});

  const keys = Object.keys(result);
  const dates = getDatesInRange(keys[0], keys[keys.length - 1]);

  const fixedResults = dates.reduce(function(prev, date) {
    const value = result[date] || prev[prev.length - 1].value;

    prev.push({
      created: date,
      value: value
    });

    return prev;
  }, []);

  return fixedResults;
}

module.exports = format;
<|endoftext|>