<commit_before>/**
 * @file        Entry library for EasyRTC server. Houses the primary listen function.
 * @author      Priologic Software, info@easyrtc.com
 * @copyright   Copyright 2013 Priologic Software. All rights reserved.
 * @license     BSD v2, see LICENSE file in module root folder.
 */

var g           = require("./general_util");        // General utility functions local module
g.checkModules(); // Check to ensure all required modules are available

var _           = require("underscore");            // General utility functions external module
var pub         = require("./easyrtc_public_obj");  // EasyRTC public object

/**
 * Listener for starting the EasyRTC server. The successCallback can be used to determine when EasyRTC is fully running.
 *
 * @param       {Object} httpApp        express http object. Allows EasyRTC to interact with the http server.
 * @param       {Object} socketServer   socket.io server object. Allows EasyRTC to interact with the socket server.
 * @param       {Object} options        EasyRTC options object. Sets configurable options. If null, than defaults will be used.
 * @param       {function(Error, Object)} listenCallback Called when the start up routines are complete. In form of successCallback(err, pub). The parameter 'err' will null unless an error occurs and 'pub' is the EasyRTC public object for interacting with the server.
 */
exports.listen = function(httpApp, socketServer, options, listenCallback) {
    var eu              = require("./easyrtc_util");        // EasyRTC utility functions

    pub.util.logInfo("Starting EasyRTC Server (v" + pub.getVersion() +") on Node (" + process.version + ")");

    // Set server object references in public object
    pub.httpApp         = httpApp;
    pub.socketServer    = socketServer;

    if (options){
        pub.util.logDebug("Overriding options", options);

        for (var optionName in options) {
            pub.setOption(optionName, options[optionName]);
        }
    }

    pub.util.logDebug("Emitting event 'startup'");
    pub.eventHandler.emit("startup", function(err) {
        if (err) {
            pub.util.logError("Error occured upon startup", err);
            if(_.isFunction(listenCallback)) {
                listenCallback(err, null);
            }
        }
        else {
            pub.util.logInfo("EasyRTC Server Ready For Connections (v"+ pub.getVersion() + ")");
            if(_.isFunction(listenCallback)) {
                listenCallback(err, pub);
            }
        }
    });
};

/**
 * Returns an EasyRTC options object with a copy of the default options.
 *
 * @returns     {Object}                EasyRTC options object
 */
exports.getDefaultOptions = function() {
    var defaultOptions = require("./easyrtc_default_options");
    return g.deepCopy(defaultOptions);
};

/**
 * Sets listener for a given EasyRTC event. Only one listener is allowed per event. Any other listeners for an event are removed before adding the new one.
 *
 * @param       {String} event          Listener name.
 * @param       {Object} listener       Function
 */
exports.on = function(event, listener) {
    if (event && _.isFunction(listener)) {
        pub.events.removeAllListeners(event);
        pub.events.on(event, listener);
    }
    else {
        pub.util.logError("Unable to add listener to event '" + event + "'");
    }
};

/**
 * Removes all listeners for an event. If there is a default EasyRTC listener, it will be added.
 *
 * @param       {String} event          Listener name.
 * @param       {Object} listener       Function
 */
exports.removeAllListeners = function(event) {
    if (event) {
        pub.events.removeAllListeners(event);
        pub.events.setDefaultListener(event);
    }
};

/**
 * Returns the listeners for an event.
 *
 * @param       {String} event          Listener name.
 */
exports.listeners   = pub.events.listeners;

/**
 * General logging function which emits a log event
 *
 * @param       {String} logText    Text for log
 * @param       {Object} logFields  Simple JSON object which contains extra fields to be logged.
 */
exports.logDebug    = pub.util.logDebug;
exports.logInfo     = pub.util.logInfo;
exports.logWarning  = pub.util.logWarning;
exports.logError    = pub.util.logError;

/**
 * Expose public utility functions
 */

exports.util        = pub.util;

/**
 * Sets individual option.
 *
 * @param       {Object} option Option name
 * @param       {Object} value  Option value
 * @returns     {Boolean} true on success, false on failure
 */
exports.setOption   = pub.setOption;
<commit_msg>No need to expose log functions now that we are exposing the whole util object.<commit_after>/**
 * @file        Entry library for EasyRTC server. Houses the primary listen function.
 * @author      Priologic Software, info@easyrtc.com
 * @copyright   Copyright 2013 Priologic Software. All rights reserved.
 * @license     BSD v2, see LICENSE file in module root folder.
 */

var g           = require("./general_util");        // General utility functions local module
g.checkModules(); // Check to ensure all required modules are available

var _           = require("underscore");            // General utility functions external module
var pub         = require("./easyrtc_public_obj");  // EasyRTC public object

/**
 * Listener for starting the EasyRTC server. The successCallback can be used to determine when EasyRTC is fully running.
 *
 * @param       {Object} httpApp        express http object. Allows EasyRTC to interact with the http server.
 * @param       {Object} socketServer   socket.io server object. Allows EasyRTC to interact with the socket server.
 * @param       {Object} options        EasyRTC options object. Sets configurable options. If null, than defaults will be used.
 * @param       {function(Error, Object)} listenCallback Called when the start up routines are complete. In form of successCallback(err, pub). The parameter 'err' will null unless an error occurs and 'pub' is the EasyRTC public object for interacting with the server.
 */
exports.listen = function(httpApp, socketServer, options, listenCallback) {
    var eu              = require("./easyrtc_util");        // EasyRTC utility functions

    pub.util.logInfo("Starting EasyRTC Server (v" + pub.getVersion() +") on Node (" + process.version + ")");

    // Set server object references in public object
    pub.httpApp         = httpApp;
    pub.socketServer    = socketServer;

    if (options){
        pub.util.logDebug("Overriding options", options);

        for (var optionName in options) {
            pub.setOption(optionName, options[optionName]);
        }
    }

    pub.util.logDebug("Emitting event 'startup'");
    pub.eventHandler.emit("startup", function(err) {
        if (err) {
            pub.util.logError("Error occured upon startup", err);
            if(_.isFunction(listenCallback)) {
                listenCallback(err, null);
            }
        }
        else {
            pub.util.logInfo("EasyRTC Server Ready For Connections (v"+ pub.getVersion() + ")");
            if(_.isFunction(listenCallback)) {
                listenCallback(err, pub);
            }
        }
    });
};

/**
 * Returns an EasyRTC options object with a copy of the default options.
 *
 * @returns     {Object}                EasyRTC options object
 */
exports.getDefaultOptions = function() {
    var defaultOptions = require("./easyrtc_default_options");
    return g.deepCopy(defaultOptions);
};

/**
 * Sets listener for a given EasyRTC event. Only one listener is allowed per event. Any other listeners for an event are removed before adding the new one.
 *
 * @param       {String} event          Listener name.
 * @param       {Object} listener       Function
 */
exports.on = function(event, listener) {
    if (event && _.isFunction(listener)) {
        pub.events.removeAllListeners(event);
        pub.events.on(event, listener);
    }
    else {
        pub.util.logError("Unable to add listener to event '" + event + "'");
    }
};

/**
 * Removes all listeners for an event. If there is a default EasyRTC listener, it will be added.
 *
 * @param       {String} event          Listener name.
 * @param       {Object} listener       Function
 */
exports.removeAllListeners = function(event) {
    if (event) {
        pub.events.removeAllListeners(event);
        pub.events.setDefaultListener(event);
    }
};

/**
 * Returns the listeners for an event.
 *
 * @param       {String} event          Listener name.
 */
exports.listeners   = pub.events.listeners;

/**
 * Expose public utility functions
 */
exports.util        = pub.util;

/**
 * Sets individual option.
 *
 * @param       {Object} option Option name
 * @param       {Object} value  Option value
 * @returns     {Boolean} true on success, false on failure
 */
exports.setOption   = pub.setOption;
<|endoftext|>


<commit_before>// https://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript
function parseQuery() {
    var query = window.location.search.substring(1);
    var vars = query.split("&");
    var obj = {};
    for (var i=0;i<vars.length;i++) {
        if (vars[i]) {
            var pair = vars[i].split("=", 2);
            var list = obj[pair[0]];
            if (typeof(list) === 'undefined') {
                obj[pair[0]] = list = [];
            }
            list.push(decodeURIComponent(pair[1]));
        }
    }
    return obj;
}

/**
 * Parse a cookie seed.
 * @param {string} importSeed - the import seed
 * @param {object} query - the query object, which may specify options of how to interpret the seed
 * @param {function(string,object,err)} - function to which bad seeds are passed
 * @returns {object|boolean} - the cookie, or false if the seed could not be parsed
 */
function parseImportSeed(importSeed, query, badSeedHandler) {
    badSeedHandler = badSeedHandler || (x => false);
    var result;
    try {
        result = JSON.parse(importSeed);
    } catch (err) {
        console.info("failed to interpret cookie seed", importSeed, query, err);
        result = badSeedHandler(importSeed, query, err);
    }
    return result;
}

/**
 * @param {function(number, object, boolean, object, string)} setCookieCallback the cookie callback;
 *      parameters are cookie index (integer), input cookie object (or null), import success (boolean),
 *      output cookie object (or null if !success), error info (string)
 */
function processImports(input, setCookieCallback) {
    var cookieImports = input['import'] || [];
    var numImportSeeds = cookieImports.length;
    var newCookies = cookieImports
        .map(importSeed => parseImportSeed(importSeed, input));
    var numGoodCookies = newCookies.filter(c => !!c).length;
    console.debug(numGoodCookies + " good cookies among " + numImportSeeds + " import seeds");
    newCookies.forEach((newCookie, index) => {
        if (newCookie) {
            try {
                chrome.cookies.set(newCookie, outCookie => {
                    setCookieCallback(index, numImportSeeds, newCookie, true, outCookie, "OK");
                });
            } catch (err) {
                console.info("chrome.cookies.set failed", err);
                setCookieCallback(index, numImportSeeds, newCookie, false, null, err.toString());
            }
        } else {
            setCookieCallback(index, numImportSeeds, null, false, null, 'seed_parse_failed');
        }
    })
}

function createTableCell(value, wrapTextInInput) {
    var cell = document.createElement('td');
    if (wrapTextInInput) {
        var input = document.createElement('input');
        input.type = 'text';
        input.value = value;
        cell.appendChild(input);
    } else {
        cell.innerText = value;
    }
    return cell;
}

document.addEventListener('DOMContentLoaded', function() {
    var inputTable = document.createElement('table');
    inputTable.id = 'input-params';
    var query = parseQuery();
    for (var name in query) {
        var values = query[name];
        values.forEach(value => {
            var nameCell = createTableCell(name);
            var valueCell = createTableCell(value, true);
            var row = document.createElement('tr');
            [nameCell, valueCell].forEach(cell => row.appendChild(cell));
            inputTable.appendChild(row);
        });
    }
    var inputDiv = document.getElementById('input');
    inputDiv.appendChild(inputTable);
    var resultTable = document.createElement('table');
    resultTable.id = 'import-results';
    var output = {
        imports: []
    };
    var outputDiv = document.getElementById('output');
    processImports(query, (index, numImportSeeds, newCookie, success, outCookie, s) => {
        console.debug('chrome.cookies.set', index, newCookie, success, outCookie, s);
        output.imports.push({
            'index': index,
            'success': success,
            'savedCookie': outCookie,
            'message': s || null
        });
        var cookieInfoCells = [createTableCell(outCookie.domain), createTableCell(outCookie.path), createTableCell(outCookie.name), createTableCell(outCookie.value, true)];
        var row = document.createElement(tag || 'tr');
        cookieInfoCells.forEach(c => row.appendChild(c));
        resultTable.appendChild(row);
        if (output.imports.length == numImportSeeds) {
            outputDiv.innerText = JSON.stringify(output, null, 2);
            var resultDiv = document.getElementById('result');
            resultDiv.appendChild(resultTable);
        }
    });
});<commit_msg>add table header to readable results table<commit_after>// https://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript
function parseQuery() {
    var query = window.location.search.substring(1);
    var vars = query.split("&");
    var obj = {};
    for (var i=0;i<vars.length;i++) {
        if (vars[i]) {
            var pair = vars[i].split("=", 2);
            var list = obj[pair[0]];
            if (typeof(list) === 'undefined') {
                obj[pair[0]] = list = [];
            }
            list.push(decodeURIComponent(pair[1]));
        }
    }
    return obj;
}

/**
 * Parse a cookie seed.
 * @param {string} importSeed - the import seed
 * @param {object} query - the query object, which may specify options of how to interpret the seed
 * @param {function(string,object,err)} - function to which bad seeds are passed
 * @returns {object|boolean} - the cookie, or false if the seed could not be parsed
 */
function parseImportSeed(importSeed, query, badSeedHandler) {
    badSeedHandler = badSeedHandler || (x => false);
    var result;
    try {
        result = JSON.parse(importSeed);
    } catch (err) {
        console.info("failed to interpret cookie seed", importSeed, query, err);
        result = badSeedHandler(importSeed, query, err);
    }
    return result;
}

/**
 * @param {function(number, object, boolean, object, string)} setCookieCallback the cookie callback;
 *      parameters are cookie index (integer), input cookie object (or null), import success (boolean),
 *      output cookie object (or null if !success), error info (string)
 */
function processImports(input, setCookieCallback) {
    var cookieImports = input['import'] || [];
    var numImportSeeds = cookieImports.length;
    var newCookies = cookieImports
        .map(importSeed => parseImportSeed(importSeed, input));
    var numGoodCookies = newCookies.filter(c => !!c).length;
    console.debug(numGoodCookies + " good cookies among " + numImportSeeds + " import seeds");
    newCookies.forEach((newCookie, index) => {
        if (newCookie) {
            try {
                chrome.cookies.set(newCookie, outCookie => {
                    setCookieCallback(index, numImportSeeds, newCookie, true, outCookie, "OK");
                });
            } catch (err) {
                console.info("chrome.cookies.set failed", err);
                setCookieCallback(index, numImportSeeds, newCookie, false, null, err.toString());
            }
        } else {
            setCookieCallback(index, numImportSeeds, null, false, null, 'seed_parse_failed');
        }
    })
}

function createTableCell(value, wrapTextInInput) {
    var cell = document.createElement('td');
    if (wrapTextInInput) {
        var input = document.createElement('input');
        input.type = 'text';
        input.value = value;
        cell.appendChild(input);
    } else {
        cell.innerText = value;
    }
    return cell;
}

document.addEventListener('DOMContentLoaded', function() {
    var inputTable = document.createElement('table');
    inputTable.id = 'input-params';
    var query = parseQuery();
    for (var name in query) {
        var values = query[name];
        values.forEach(value => {
            var nameCell = createTableCell(name);
            var valueCell = createTableCell(value, true);
            var row = document.createElement('tr');
            [nameCell, valueCell].forEach(cell => row.appendChild(cell));
            inputTable.appendChild(row);
        });
    }
    var inputDiv = document.getElementById('input');
    inputDiv.appendChild(inputTable);
    var resultTable = document.createElement('table');
    resultTable.id = 'import-results';
    var output = {
        imports: []
    };
    var outputDiv = document.getElementById('output');
    outputDiv.innerText = JSON.stringify({
        status: 'not_yet_processed'
    });
    var resultTableHeader;
    processImports(query, (index, numImportSeeds, newCookie, success, outCookie, s) => {
        console.debug('chrome.cookies.set', index, newCookie, success, outCookie, s);
        output.imports.push({
            'index': index,
            'success': success,
            'savedCookie': outCookie,
            'message': s || null
        });
        var cookieInfoCells = [createTableCell(outCookie.domain),
                createTableCell(outCookie.path),
                createTableCell(outCookie.name),
                createTableCell(outCookie.value, true)];
        var row = document.createElement('tr');
        cookieInfoCells.forEach(c => row.appendChild(c));
        if (typeof(resultTableHeader) === 'undefined') {
            resultTableHeader = document.createElement('tr');
            ['Domain', 'Path', 'Name', 'Value']
                    .map(t => createTableCell(t, false))
                    .forEach(c => resultTableHeader.appendChild(c));
            resultTable.appendChild(resultTableHeader);
        }
        resultTable.appendChild(row);
        if (output.imports.length == numImportSeeds) {
            output.status = 'all_imports_processed';
            outputDiv.innerText = JSON.stringify(output, null, 2);
            var resultDiv = document.getElementById('result');
            resultDiv.appendChild(resultTable);
        } else {
            outputDiv.innerText = JSON.stringify({
                status: 'some_imports_processed'
            });
        }
    });
});<|endoftext|>


<commit_before>
// bind dependencies
const grid       = require ('grid');
const alert      = require ('alert');
const crypto     = require ('crypto');
const controller = require ('controller');

// require models
const acl  = model ('acl');
const user = model ('user');

// bind local dependencies
const config = require ('config');

/**
 * build user admin controller
 *
 * @acl   admin.users.view
 * @fail  /
 * @mount /admin/user
 */
class admin extends controller {
  /**
   * construct user admin controller
   */
  constructor () {
    // run super
    super ();

    // bind methods
    this.gridAction         = this.gridAction.bind (this);
    this.indexAction        = this.indexAction.bind (this);
    this.createAction       = this.createAction.bind (this);
    this.updateAction       = this.updateAction.bind (this);
    this.removeAction       = this.removeAction.bind (this);
    this.createSubmitAction = this.createSubmitAction.bind (this);
    this.updateSubmitAction = this.updateSubmitAction.bind (this);
    this.removeSubmitAction = this.removeSubmitAction.bind (this);

    // bind private methods
    this._grid = this._grid.bind (this);
  }

  /**
   * index action
   *
   * @param req
   * @param res
   *
   * @icon    fa fa-user
   * @menu    {ADMIN} Users
   * @title   User Administration
   * @route   {get} /
   * @layout  admin
   */
  async indexAction (req, res) {
    // render user admin page
    let Grid = await this._grid ().render ();

    // render grid
    res.render ('user/admin', {
      'grid' : Grid
    });
  }

  /**
   * add/edit action
   *
   * @param req
   * @param res
   *
   * @route    {get} /create
   * @route    {get} /:id/edit
   * @menu     {USERS} Add User
   * @layout   admin
   * @priority 12
   */
  createAction (req, res) {
    // return update action
    return this.updateAction (req, res);
  }

  /**
   * update action
   *
   * @param req
   * @param res
   *
   * @route   {get} /:id/update
   * @layout  admin
   */
  async updateAction (req, res) {
    // set website variable
    let User   = new user ();
    let create = true;

    // check for website model
    if (req.params.id) {
      // load user
      User   = await user.findById (req.params.id);
      create = false;
    }

    // check for website
    if (!User) {
      User = new user ();
    }

    // get acls
    let uacls = (await User.get ('acl') || []).map ((Acl) => {
      // return id
      return Acl.get ('_id').toString ();
    });

    // set acls
    let acls = (await acl.find () || []).map ((Acl) => {
      // return sanitised
      return {
        'id'   : Acl.get ('_id').toString (),
        'has'  : uacls.indexOf (Acl.get ('_id').toString ()) > -1,
        'name' : Acl.get ('name')
      };
    });

    // render page
    res.render ('user/admin/update', {
      'usr'   : await User.sanitise (),
      'acls'  : acls,
      'title' : create ? 'Create New' : 'Update ' + (User.get ('username') || User.get ('email'))
    });
  }

  /**
   * create submit action
   *
   * @param req
   * @param res
   *
   * @route   {post} /create
   * @layout  admin
   */
  createSubmitAction (req, res) {
    // return update action
    return this.updateSubmitAction (req, res);
  }

  /**
   * add/edit action
   *
   * @param req
   * @param res
   *
   * @route   {post} /:id/update
   * @layout  admin
   */
  async updateSubmitAction (req, res) {
    // set website variable
    let User   = new user ();
    let create = true;

    // check for website model
    if (req.params.id) {
      // load by id
      User   = await user.findById (req.params.id);
      create = false;
    }

    // get user acls
    let UAcls = await User.get ('acl');

    // loop acls
    if (req.body.roles && req.body.roles.length) {
      // check if Array
      if (!Array.isArray (req.body.roles)) req.body.roles = [req.body.roles];

      // loop roles
      UAcls = await Promise.all (req.body.roles.map ((role) => {
        // return found role
        return acl.findById (role);
      }));
    }

    // set User model variables
    User.set ('acl',      UAcls);
    User.set ('email',    req.body.email);
    User.set ('username', req.body.username);

    // check for user password
    if (req.body.password && req.body.password.length) {
      // everything checks out
      let hash = crypto.createHmac ('sha256', config.get ('secret'))
        .update (req.body.password)
        .digest ('hex');

      // set hash
      User.set ('hash', hash);
    }

    // save user
    await User.save ();

    // get acls
    let uacls = (await User.get ('acl')).map ((Acl) => {
      // return id
      return Acl.get ('_id').toString ();
    });

    // set acls
    let acls = (await acl.find ()).map ((Acl) => {
      // return sanitised
      return {
        'id'   : Acl.get ('_id').toString (),
        'has'  : uacls.indexOf (Acl.get ('_id').toString ()) > -1,
        'name' : Acl.get ('name')
      };
    });

    // update user
    req.alert ('success', 'Successfully updated user');

    // render page
    res.render ('user/admin/update', {
      'usr'   : await User.sanitise (),
      'acls'  : acls,
      'title' : create ? 'Create New' : 'Update ' + (User.get ('username') || User.get ('email'))
    });
  }

  /**
   * delete action
   *
   * @param req
   * @param res
   *
   * @route   {get} /:id/remove
   * @layout  admin
   */
  async removeAction (req, res) {
    // set website variable
    let User = false;

    // check for website model
    if (req.params.id) {
      // load user
      User = await user.findById (req.params.id);
    }

    // render page
    res.render ('user/admin/remove', {
      'usr'   : await User.sanitise (),
      'title' : 'Remove ' + (User.get ('username') || User.get ('email'))
    });
  }

  /**
   * delete action
   *
   * @param req
   * @param res
   *
   * @route   {post} /:id/remove
   * @title   User Administration
   * @layout  admin
   */
  async removeSubmitAction (req, res) {
    // set website variable
    let User = false;

    // check for website model
    if (req.params.id) {
      // load user
      User = await user.findById (req.params.id);
    }

    // delete website
    await User.remove ();

    // alert Removed
    req.alert ('success', 'Successfully removed ' + (User.get ('username') || User.get ('email')));

    // render index
    return this.indexAction (req, res);
  }

  /**
   * user alert emit
   *
   * @socket user.alert
   */
  async eventSocket (Socket, data) {
    // alert user
    alert.user (await user.findById (data.id), data.type, data.text);

    // alert socket
    alert.socket (Socket, 'success', 'successfully sent alert');
  }

  /**
   * user grid action
   *
   * @param req
   * @param res
   *
   * @route {post} /grid
   */
  gridAction (req, res) {
    // return post grid request
    return this._grid ().post (req, res);
  }

  /**
   * renders grid
   *
   * @return {grid}
   */
  _grid () {
    // create new grid
    let userGrid = new grid ();

    // set route
    userGrid.route ('/admin/user/grid');

    // set grid model
    userGrid.model (user);

    // add grid columns
    userGrid.column ('_id', {
      'title'  : 'ID',
      'width'  : '1%',
      'format' : async (col) => {
        return col ? '' + col.toString () + '' : 'N/A';
      }
    }).column ('username', {
      'sort'   : true,
      'title'  : 'Username',
      'format' : async (col) => {
        return col ? col.toString () : 'N/A';
      }
    }).column ('acl', {
      'title'  : 'Roles',
      'format' : async (col, row) => {
        // set acls
        let Acls = await row.get ('acl');
        let acls = [];

        // check acls
        if (!Acls) return 'N/A';

        // loop acls
        for (var i = 0; i < Acls.length; i++) {
          // add name to acls
          if (Acls[i].get) acls.push (Acls[i].get ('name'));
        }

        // resolve acls
        return acls.join (', ');
      }
    }).column ('email', {
      'sort'   : true,
      'title'  : 'Email',
      'format' : async (col) => {
        return col ? col.toString () : 'N/A';
      }
    }).column ('updated_at', {
      'sort'   : true,
      'title'  : 'Last Online',
      'format' : async (col) => {
        return col.toLocaleDateString ('en-GB', {
          'day'   : 'numeric',
          'month' : 'short',
          'year'  : 'numeric'
        });
      }
    }).column ('created_at', {
      'sort'   : true,
      'title'  : 'Registered',
      'format' : async (col) => {
        return col.toLocaleDateString ('en-GB', {
          'day'   : 'numeric',
          'month' : 'short',
          'year'  : 'numeric'
        });
      }
    }).column ('actions', {
      'type'   : false,
      'width'  : '1%',
      'title'  : 'Actions',
      'format' : async (col, row) => {
        return [
          '
',
          'Update',
          'Remove',
          '
'
        ].join ('');
      }
    });

    // add grid filters
    userGrid.filter ('username', {
      'title' : 'Username',
      'type'  : 'text',
      'query' : async (param) => {
        // another where
        userGrid.where ({
          'username' : new RegExp (param.toString ().toLowerCase (), 'i')
        });
      }
    }).filter ('email', {
      'title' : 'Email',
      'type'  : 'text',
      'query' : async (param) => {
        // another where
        userGrid.where ({
          'email' : new RegExp (param.toString ().toLowerCase (), 'i')
        });
      }
    });

    // set default sort order
    userGrid.sort ('created_at', 1);

    // return grid
    return userGrid;
  }
}

/**
 * export admin controller
 *
 * @type {admin}
 */
exports = module.exports = admin;
<commit_msg>update user grid to allow query string saving<commit_after>
// bind dependencies
const grid       = require ('grid');
const alert      = require ('alert');
const crypto     = require ('crypto');
const controller = require ('controller');

// require models
const acl  = model ('acl');
const user = model ('user');

// bind local dependencies
const config = require ('config');

/**
 * build user admin controller
 *
 * @acl   admin.users.view
 * @fail  /
 * @mount /admin/user
 */
class admin extends controller {
  /**
   * construct user admin controller
   */
  constructor () {
    // run super
    super ();

    // bind methods
    this.gridAction         = this.gridAction.bind (this);
    this.indexAction        = this.indexAction.bind (this);
    this.createAction       = this.createAction.bind (this);
    this.updateAction       = this.updateAction.bind (this);
    this.removeAction       = this.removeAction.bind (this);
    this.createSubmitAction = this.createSubmitAction.bind (this);
    this.updateSubmitAction = this.updateSubmitAction.bind (this);
    this.removeSubmitAction = this.removeSubmitAction.bind (this);

    // bind private methods
    this._grid = this._grid.bind (this);
  }

  /**
   * index action
   *
   * @param req
   * @param res
   *
   * @icon    fa fa-user
   * @menu    {ADMIN} Users
   * @title   User Administration
   * @route   {get} /
   * @layout  admin
   */
  async indexAction (req, res) {
    // render user admin page
    let Grid = await this._grid ().render (req);

    // render grid
    res.render ('user/admin', {
      'grid' : Grid
    });
  }

  /**
   * add/edit action
   *
   * @param req
   * @param res
   *
   * @route    {get} /create
   * @route    {get} /:id/edit
   * @menu     {USERS} Add User
   * @layout   admin
   * @priority 12
   */
  createAction (req, res) {
    // return update action
    return this.updateAction (req, res);
  }

  /**
   * update action
   *
   * @param req
   * @param res
   *
   * @route   {get} /:id/update
   * @layout  admin
   */
  async updateAction (req, res) {
    // set website variable
    let User   = new user ();
    let create = true;

    // check for website model
    if (req.params.id) {
      // load user
      User   = await user.findById (req.params.id);
      create = false;
    }

    // check for website
    if (!User) {
      User = new user ();
    }

    // get acls
    let uacls = (await User.get ('acl') || []).map ((Acl) => {
      // return id
      return Acl.get ('_id').toString ();
    });

    // set acls
    let acls = (await acl.find () || []).map ((Acl) => {
      // return sanitised
      return {
        'id'   : Acl.get ('_id').toString (),
        'has'  : uacls.indexOf (Acl.get ('_id').toString ()) > -1,
        'name' : Acl.get ('name')
      };
    });

    // render page
    res.render ('user/admin/update', {
      'usr'   : await User.sanitise (),
      'acls'  : acls,
      'title' : create ? 'Create New' : 'Update ' + (User.get ('username') || User.get ('email'))
    });
  }

  /**
   * create submit action
   *
   * @param req
   * @param res
   *
   * @route   {post} /create
   * @layout  admin
   */
  createSubmitAction (req, res) {
    // return update action
    return this.updateSubmitAction (req, res);
  }

  /**
   * add/edit action
   *
   * @param req
   * @param res
   *
   * @route   {post} /:id/update
   * @layout  admin
   */
  async updateSubmitAction (req, res) {
    // set website variable
    let User   = new user ();
    let create = true;

    // check for website model
    if (req.params.id) {
      // load by id
      User   = await user.findById (req.params.id);
      create = false;
    }

    // get user acls
    let UAcls = await User.get ('acl');

    // loop acls
    if (req.body.roles && req.body.roles.length) {
      // check if Array
      if (!Array.isArray (req.body.roles)) req.body.roles = [req.body.roles];

      // loop roles
      UAcls = await Promise.all (req.body.roles.map ((role) => {
        // return found role
        return acl.findById (role);
      }));
    }

    // set User model variables
    User.set ('acl',      UAcls);
    User.set ('email',    req.body.email);
    User.set ('username', req.body.username);

    // check for user password
    if (req.body.password && req.body.password.length) {
      // everything checks out
      let hash = crypto.createHmac ('sha256', config.get ('secret'))
        .update (req.body.password)
        .digest ('hex');

      // set hash
      User.set ('hash', hash);
    }

    // save user
    await User.save ();

    // get acls
    let uacls = (await User.get ('acl')).map ((Acl) => {
      // return id
      return Acl.get ('_id').toString ();
    });

    // set acls
    let acls = (await acl.find ()).map ((Acl) => {
      // return sanitised
      return {
        'id'   : Acl.get ('_id').toString (),
        'has'  : uacls.indexOf (Acl.get ('_id').toString ()) > -1,
        'name' : Acl.get ('name')
      };
    });

    // update user
    req.alert ('success', 'Successfully updated user');

    // render page
    res.render ('user/admin/update', {
      'usr'   : await User.sanitise (),
      'acls'  : acls,
      'title' : create ? 'Create New' : 'Update ' + (User.get ('username') || User.get ('email'))
    });
  }

  /**
   * delete action
   *
   * @param req
   * @param res
   *
   * @route   {get} /:id/remove
   * @layout  admin
   */
  async removeAction (req, res) {
    // set website variable
    let User = false;

    // check for website model
    if (req.params.id) {
      // load user
      User = await user.findById (req.params.id);
    }

    // render page
    res.render ('user/admin/remove', {
      'usr'   : await User.sanitise (),
      'title' : 'Remove ' + (User.get ('username') || User.get ('email'))
    });
  }

  /**
   * delete action
   *
   * @param req
   * @param res
   *
   * @route   {post} /:id/remove
   * @title   User Administration
   * @layout  admin
   */
  async removeSubmitAction (req, res) {
    // set website variable
    let User = false;

    // check for website model
    if (req.params.id) {
      // load user
      User = await user.findById (req.params.id);
    }

    // delete website
    await User.remove ();

    // alert Removed
    req.alert ('success', 'Successfully removed ' + (User.get ('username') || User.get ('email')));

    // render index
    return this.indexAction (req, res);
  }

  /**
   * user alert emit
   *
   * @socket user.alert
   */
  async eventSocket (Socket, data) {
    // alert user
    alert.user (await user.findById (data.id), data.type, data.text);

    // alert socket
    alert.socket (Socket, 'success', 'successfully sent alert');
  }

  /**
   * user grid action
   *
   * @param req
   * @param res
   *
   * @route {post} /grid
   */
  gridAction (req, res) {
    // return post grid request
    return this._grid ().post (req, res);
  }

  /**
   * renders grid
   *
   * @return {grid}
   */
  _grid () {
    // create new grid
    let userGrid = new grid ();

    // set route
    userGrid.route ('/admin/user/grid');

    // set grid model
    userGrid.model (user);

    // add grid columns
    userGrid.column ('_id', {
      'title'  : 'ID',
      'width'  : '1%',
      'format' : async (col) => {
        return col ? '' + col.toString () + '' : 'N/A';
      }
    }).column ('username', {
      'sort'   : true,
      'title'  : 'Username',
      'format' : async (col) => {
        return col ? col.toString () : 'N/A';
      }
    }).column ('acl', {
      'title'  : 'Roles',
      'format' : async (col, row) => {
        // set acls
        let Acls = await row.get ('acl');
        let acls = [];

        // check acls
        if (!Acls) return 'N/A';

        // loop acls
        for (var i = 0; i < Acls.length; i++) {
          // add name to acls
          if (Acls[i].get) acls.push (Acls[i].get ('name'));
        }

        // resolve acls
        return acls.join (', ');
      }
    }).column ('email', {
      'sort'   : true,
      'title'  : 'Email',
      'format' : async (col) => {
        return col ? col.toString () : 'N/A';
      }
    }).column ('updated_at', {
      'sort'   : true,
      'title'  : 'Last Online',
      'format' : async (col) => {
        return col.toLocaleDateString ('en-GB', {
          'day'   : 'numeric',
          'month' : 'short',
          'year'  : 'numeric'
        });
      }
    }).column ('created_at', {
      'sort'   : true,
      'title'  : 'Registered',
      'format' : async (col) => {
        return col.toLocaleDateString ('en-GB', {
          'day'   : 'numeric',
          'month' : 'short',
          'year'  : 'numeric'
        });
      }
    }).column ('actions', {
      'type'   : false,
      'width'  : '1%',
      'title'  : 'Actions',
      'format' : async (col, row) => {
        return [
          '
',
          'Update',
          'Remove',
          '
'
        ].join ('');
      }
    });

    // add grid filters
    userGrid.filter ('username', {
      'title' : 'Username',
      'type'  : 'text',
      'query' : async (param) => {
        // another where
        userGrid.where ({
          'username' : new RegExp (param.toString ().toLowerCase (), 'i')
        });
      }
    }).filter ('email', {
      'title' : 'Email',
      'type'  : 'text',
      'query' : async (param) => {
        // another where
        userGrid.where ({
          'email' : new RegExp (param.toString ().toLowerCase (), 'i')
        });
      }
    });

    // set default sort order
    userGrid.sort ('created_at', 1);

    // return grid
    return userGrid;
  }
}

/**
 * export admin controller
 *
 * @type {admin}
 */
exports = module.exports = admin;
<|endoftext|>


<commit_before>var React = require('react-native');
var {
  Image
} = React;

var GiftedFormManager = require('../GiftedFormManager');

module.exports = {

  getInitialState() {
    return {
      validationErrorMessage: null,
    };
  },

  propTypes: {
    name: React.PropTypes.string,
    title: React.PropTypes.string,
    formName: React.PropTypes.string,
    // image: ,
    widgetStyles: React.PropTypes.object,
    formStyles: React.PropTypes.object,
    validationImage: React.PropTypes.bool,
    openModal: React.PropTypes.func,
    // navigator: ,
    onFocus: React.PropTypes.func,
    onBlur: React.PropTypes.func,
    // If we want to store the state elsewhere (Redux store, for instance), we can use value and Form's onValueChange prop
    value: React.PropTypes.any,
  },

  getDefaultProps() {
    return {
      name: '',
      title: '',
      formName: '',
      image: null,
      widgetStyles: {},
      formStyles: {},
      validationImage: true,
      openModal: null,
      navigator: null,
      onFocus: () => {},
      onBlur: () => {},
    };
  },

  componentDidMount() {
    // get value from prop
    if (typeof this.props.value !== 'undefined') {
      this._setValue(this.props.value);
      return;
    }
    // get value from store
    var formState = GiftedFormManager.stores[this.props.formName];
    if (typeof formState !== 'undefined') {
      if (typeof formState.values[this.props.name] !== 'undefined') {
        this.setState({
          value: formState.values[this.props.name],
        });
        this._validate(formState.values[this.props.name]);
      }
    }
  },

  componentWillReceiveProps(nextProps) {
    if (typeof nextProps.value !== 'undefined' && nextProps.value !== this.props.value) {
      this._onChange(nextProps.value);
    }
  },

  // get the styles by priority
  // defaultStyles < formStyles < widgetStyles
  getStyle(styleNames = []) {
    if (typeof styleNames === 'string') {
      styleNames = [styleNames];
    }

    if (typeof this.defaultStyles === 'undefined') {
      this.defaultStyles = {};
    }

    var styles = [];

    for (let i = 0; i < styleNames.length; i++) {
      if (typeof this.defaultStyles[styleNames[i]] !== 'undefined') {
        styles.push(this.defaultStyles[styleNames[i]]);
      }
    }

    for (let i = 0; i < styleNames.length; i++) {
      if (typeof this.props.formStyles[this.props.type] !== 'undefined') {
        if (typeof this.props.formStyles[this.props.type][styleNames[i]] !== 'undefined') {
          styles.push(this.props.formStyles[this.props.type][styleNames[i]]);
        }
      }
    }

    for (let i = 0; i < styleNames.length; i++) {
      if (typeof this.props.widgetStyles[styleNames[i]] !== 'undefined') {
        styles.push(this.props.widgetStyles[styleNames[i]]);
      }
    }

    return styles;
  },

  focus() {
    this.refs.input && this.refs.input.focus()
  },

  _validate(value) {
    if (typeof value === 'undefined') {
      value = this.state.value;
    }

    // @todo option for live validation ?
    var validators = GiftedFormManager.getValidators(this.props.formName, this.props.name);
    if (Array.isArray(validators.validate)) {
      if (validators.validate.length > 0) {
        var validation = GiftedFormManager.validateAndParseOne(this.props.name, value, {validate: validators.validate, title: validators.title});
        if (validation.isValid === false) {
          this.setState({
            validationErrorMessage: validation.message
          });
        } else {
          this.setState({
            validationErrorMessage: null
          });
        }
        this.props.onValidation && this.props.onValidation();
        // @todo set isvalid of modal children here
      }
    }
  },

  _setValue(value) {
    this.setState({
      value: value
    });
    GiftedFormManager.updateValue(this.props.formName, this.props.name, value);
  },

  _onChange(value) {
    this.props.onChangeText && this.props.onChangeText(value); //should maintain similar API to core TextInput component

    this._setValue(value);
    this._validate(value);

    this.props.onValueChange && this.props.onValueChange();
    // @todo modal widgets validation - the modalwidget row should inform about validation status
  },

  // @todo options enable live checking
  _renderValidationError() {
    if (!(typeof this.state.value === 'undefined' || this.state.value === '') && this.state.validationErrorMessage !== null && this.state.validationErrorMessage !== '') {
      var ValidationErrorWidget = require('../widgets/ValidationErrorWidget');
      return (
        <ValidationErrorWidget
          {...this.props}
          message={this.state.validationErrorMessage}
        />
      );
    }
    return null;
  },

  _renderImage() {
    var validators = null;
    if (this.props.displayValue) {
      // in case of modal widget
      validators = GiftedFormManager.getValidators(this.props.formName, this.props.displayValue);
    } else {
      validators = GiftedFormManager.getValidators(this.props.formName, this.props.name);
    }

    let toValidate = false;
    if (Array.isArray(validators.validate)) {
      if (validators.validate.length > 0) {
        toValidate = true;
      }
    }

    // @todo image delete_sign / checkmark should be editable via option
    // @todo options enable live validation
    if (!(typeof this.state.value === 'undefined' || this.state.value === '') && this.state.validationErrorMessage !== null && this.props.image !== null && this.props.type !== 'OptionWidget' && this.props.validationImage === true && toValidate === true) {
      return (
        <Image
          style={this.getStyle('rowImage')}
          resizeMode={Image.resizeMode.contain}
          source={require('../icons/delete_sign.png')}
        />
      );
    } else if (!(typeof this.state.value === 'undefined' || this.state.value === '') && this.state.validationErrorMessage === null && this.props.image !== null && this.props.type !== 'OptionWidget' && this.props.validationImage === true && toValidate === true) {
      return (
        <Image
          style={this.getStyle('rowImage')}
          resizeMode={Image.resizeMode.contain}
          source={require('../icons/checkmark.png')}
        />
      );
    } else if (this.props.image !== null) {
      if (typeof this.props.image == 'object') {
        return(this.props.image);
      } else {
        return (
          <Image
            style={this.getStyle('rowImage')}
            resizeMode={Image.resizeMode.contain}
            source={this.props.image}
          />
        );
      }
    }
    return null;
  },
};
<commit_msg>in componentDidMount get value from store first<commit_after>var React = require('react-native');
var {
  Image
} = React;

var GiftedFormManager = require('../GiftedFormManager');

module.exports = {

  getInitialState() {
    return {
      validationErrorMessage: null,
    };
  },

  propTypes: {
    name: React.PropTypes.string,
    title: React.PropTypes.string,
    formName: React.PropTypes.string,
    // image: ,
    widgetStyles: React.PropTypes.object,
    formStyles: React.PropTypes.object,
    validationImage: React.PropTypes.bool,
    openModal: React.PropTypes.func,
    // navigator: ,
    onFocus: React.PropTypes.func,
    onBlur: React.PropTypes.func,
    // If we want to store the state elsewhere (Redux store, for instance), we can use value and Form's onValueChange prop
    value: React.PropTypes.any,
  },

  getDefaultProps() {
    return {
      name: '',
      title: '',
      formName: '',
      image: null,
      widgetStyles: {},
      formStyles: {},
      validationImage: true,
      openModal: null,
      navigator: null,
      onFocus: () => {},
      onBlur: () => {},
    };
  },

  componentDidMount() {
    //store input on parent form component so it can trigger highlighting all inline errors
    if(this.props.form) {
      this.props.form.inputs[this.props.name] = this;
    }

    // get value from store
    var formState = GiftedFormManager.stores[this.props.formName];
    if (typeof formState !== 'undefined') {
      if (typeof formState.values[this.props.name] !== 'undefined') {
        this.setState({
          value: formState.values[this.props.name],
        });
        this._validate(formState.values[this.props.name]);
        return;
      }
    }

    // get value from prop
    if(typeof this.props.value !== 'undefined') {
      this._setValue(this.props.value);
    }
  },

  componentWillReceiveProps(nextProps) {
    if (typeof nextProps.value !== 'undefined' && nextProps.value !== this.props.value) {
      this._onChange(nextProps.value);
    }
  },

  // get the styles by priority
  // defaultStyles < formStyles < widgetStyles
  getStyle(styleNames = []) {
    if (typeof styleNames === 'string') {
      styleNames = [styleNames];
    }

    if (typeof this.defaultStyles === 'undefined') {
      this.defaultStyles = {};
    }

    var styles = [];

    for (let i = 0; i < styleNames.length; i++) {
      if (typeof this.defaultStyles[styleNames[i]] !== 'undefined') {
        styles.push(this.defaultStyles[styleNames[i]]);
      }
    }

    for (let i = 0; i < styleNames.length; i++) {
      if (typeof this.props.formStyles[this.props.type] !== 'undefined') {
        if (typeof this.props.formStyles[this.props.type][styleNames[i]] !== 'undefined') {
          styles.push(this.props.formStyles[this.props.type][styleNames[i]]);
        }
      }
    }

    for (let i = 0; i < styleNames.length; i++) {
      if (typeof this.props.widgetStyles[styleNames[i]] !== 'undefined') {
        styles.push(this.props.widgetStyles[styleNames[i]]);
      }
    }

    return styles;
  },

  focus() {
    this.refs.input && this.refs.input.focus()
  },

  _validate(value) {
    if (typeof value === 'undefined') {
      value = this.state.value;
    }

    // @todo option for live validation ?
    var validators = GiftedFormManager.getValidators(this.props.formName, this.props.name);
    if (Array.isArray(validators.validate)) {
      if (validators.validate.length > 0) {
        var validation = GiftedFormManager.validateAndParseOne(this.props.name, value, {validate: validators.validate, title: validators.title});
        if (validation.isValid === false) {
          this.setState({
            validationErrorMessage: validation.message
          });
        } else {
          this.setState({
            validationErrorMessage: null
          });
        }
        this.props.onValidation && this.props.onValidation();
        // @todo set isvalid of modal children here
      }
    }
  },

  _setValue(value) {
    this.setState({
      value: value
    });
    GiftedFormManager.updateValue(this.props.formName, this.props.name, value);
  },

  _onChange(value) {
    this.props.onChangeText && this.props.onChangeText(value); //should maintain similar API to core TextInput component

    this._setValue(value);
    this._validate(value);

    this.props.onValueChange && this.props.onValueChange();
    // @todo modal widgets validation - the modalwidget row should inform about validation status
  },

  // @todo options enable live checking
  _renderValidationError() {
    if (!(typeof this.state.value === 'undefined' || this.state.value === '') && this.state.validationErrorMessage !== null && this.state.validationErrorMessage !== '') {
      var ValidationErrorWidget = require('../widgets/ValidationErrorWidget');
      return (
        <ValidationErrorWidget
          {...this.props}
          message={this.state.validationErrorMessage}
        />
      );
    }
    return null;
  },

  _renderImage() {
    var validators = null;
    if (this.props.displayValue) {
      // in case of modal widget
      validators = GiftedFormManager.getValidators(this.props.formName, this.props.displayValue);
    } else {
      validators = GiftedFormManager.getValidators(this.props.formName, this.props.name);
    }

    let toValidate = false;
    if (Array.isArray(validators.validate)) {
      if (validators.validate.length > 0) {
        toValidate = true;
      }
    }

    // @todo image delete_sign / checkmark should be editable via option
    // @todo options enable live validation
    if (!(typeof this.state.value === 'undefined' || this.state.value === '') && this.state.validationErrorMessage !== null && this.props.image !== null && this.props.type !== 'OptionWidget' && this.props.validationImage === true && toValidate === true) {
      return (
        <Image
          style={this.getStyle('rowImage')}
          resizeMode={Image.resizeMode.contain}
          source={require('../icons/delete_sign.png')}
        />
      );
    } else if (!(typeof this.state.value === 'undefined' || this.state.value === '') && this.state.validationErrorMessage === null && this.props.image !== null && this.props.type !== 'OptionWidget' && this.props.validationImage === true && toValidate === true) {
      return (
        <Image
          style={this.getStyle('rowImage')}
          resizeMode={Image.resizeMode.contain}
          source={require('../icons/checkmark.png')}
        />
      );
    } else if (this.props.image !== null) {
      if (typeof this.props.image == 'object') {
        return(this.props.image);
      } else {
        return (
          <Image
            style={this.getStyle('rowImage')}
            resizeMode={Image.resizeMode.contain}
            source={this.props.image}
          />
        );
      }
    }
    return null;
  },
};
<|endoftext|>


<commit_before>const {toFeatureCollection} = require('sepal/ee/aoi')
const {hasImagery: hasOpticalImagery} = require('sepal/ee/optical/collection')
const {hasImagery: hasRadarImagery} = require('sepal/ee/radar/collection')
const tile = require('sepal/ee/tile')
const {exportImageToSepal$} = require('../jobs/export/toSepal')
const {mkdirSafe$} = require('root/rxjs/fileSystem')
const {concat, forkJoin, from, of} = require('rx')
const Path = require('path')
const {map, mergeMap, scan, switchMap, tap} = require('rx/operators')
const {swallow} = require('sepal/rxjs/operators')
const {terminal$} = require('sepal/terminal')
const {sequence} = require('sepal/utils/array')
const moment = require('moment')
const ee = require('ee')
const {getCurrentContext$} = require('root/jobs/service/context')
const {getCollection$} = require('sepal/ee/timeSeries/collection')
const _ = require('lodash')
const log = require('sepal/log').getLogger('task')

const TILE_DEGREES = 2
const EE_EXPORT_SHARD_SIZE = 256
const EE_EXPORT_FILE_DIMENSIONS = 512
const DATE_DELTA = 3
const DATE_DELTA_UNIT = 'months'

module.exports = {
    submit$: (id, {description, recipe, indicator, scale}) =>
        getCurrentContext$().pipe(
            switchMap(({config}) => {
                const preferredDownloadDir = `${config.homeDir}/downloads/${description}/`
                return mkdirSafe$(preferredDownloadDir, {recursive: true}).pipe(
                    switchMap(downloadDir =>
                        export$({description, downloadDir, recipe, indicator, scale})
                    )
                )
            })
        )
}

const export$ = ({downloadDir, description, recipe, indicator, scale}) => {
    const aoi = recipe.model.aoi
    const dataSets = recipe.model.sources.dataSets
    const {startDate, endDate} = recipe.model.dates
    const reflectance = recipe.model.options.corrections.includes('SR') ? 'SR' : 'TOA'
    const tiles = tile(toFeatureCollection(aoi), TILE_DEGREES) // synchronous EE

    const exportTiles$ = tileIds => {
        const totalTiles = tileIds.length
        const tile$ = from(
            tileIds.map((tileId, tileIndex) =>
                ({tileId, tileIndex})
            )
        )
        return concat(
            of({totalTiles}),
            tile$.pipe(
                mergeMap(tile => exportTile$(tile), 1)
            )
        )
    }
    const exportTile$ = ({tileId, tileIndex}) =>
        concat(
            of({tileIndex, chunks: 0}),
            exportChunks$(createChunks$({tileId, tileIndex})),
            postProcess$(Path.join(downloadDir, `${tileIndex}`))
        )

    const createChunks$ = ({tileId, tileIndex}) => {
        const tile = tiles.filterMetadata('system:index', 'equals', tileId).first()
        const from = moment(startDate)
        const to = moment(endDate)
        const duration = moment(to).subtract(1, 'day').diff(from, DATE_DELTA_UNIT)
        const dateOffsets = sequence(0, duration, DATE_DELTA)
        const chunks$ = dateOffsets.map(dateOffset => {
            const start = moment(from).add(dateOffset, DATE_DELTA_UNIT)
            const startString = start.format('YYYY-MM-DD')
            const end = moment.min(moment(start).add(DATE_DELTA, DATE_DELTA_UNIT), to)
            const endString = end.format('YYYY-MM-DD')
            const dateRange = `${startString}_${endString}`
            return hasImagery$(
                tile.geometry(),
                ee.Date(startString),
                ee.Date(endString)
            ).pipe(
                switchMap(notEmpty => {
                    if (notEmpty) {
                        return createTimeSeries$(tile, startString, endString).pipe(
                            map(timeSeries =>
                                ({tileIndex, timeSeries, dateRange, notEmpty})
                            )
                        )
                    } else {
                        return of({tileIndex, dateRange, notEmpty})
                    }
                })
            )
        })
        return forkJoin(chunks$)
    }

    const isRadar = () => _.isEqual(Object.values(dataSets).flat(), ['SENTINEL_1'])

    const createTimeSeries$ = (feature, startDate, endDate) => {
        const images$ = getCollection$({recipe, bands: [indicator], startDate, endDate})
        return images$.pipe(
            map(images => {
                images = images.select(indicator)
                const distinctDateImages = images.distinct('date')
                log.error('bands', images.first().bandNames().getInfo())
                const timeSeries = ee.ImageCollection(
                    ee.Join.saveAll('images')
                        .apply({
                            primary: distinctDateImages,
                            secondary: images,
                            condition: ee.Filter.equals({
                                leftField: 'date',
                                rightField: 'date'
                            })
                        })
                        .map(image => ee.ImageCollection(ee.List(image.get('images')))
                            .median()
                            .rename(image.getString('date'))
                        ))
                    .toBands()
                    .regexpRename('.*(.{10})', '$1')
                    .clip(feature.geometry())
                return timeSeries.select(timeSeries.bandNames().sort())
            })
        )
    }

    const hasImagery$ = (geometry, startDate, endDate) =>
        ee.getInfo$(
            isRadar()
                ? hasRadarImagery({geometry, startDate, endDate})
                : hasOpticalImagery({dataSets: extractDataSets(dataSets), reflectance, geometry, startDate, endDate}),
            'check if date range has imagery'
        )

    const exportChunks$ = chunks$ =>
        chunks$.pipe(
            switchMap(chunks => {
                const nonEmptyChunks = chunks.filter(({notEmpty}) => notEmpty)
                const totalChunks = nonEmptyChunks.length
                return concat(
                    of({totalChunks}),
                    from(nonEmptyChunks).pipe(
                        mergeMap(chunk => exportChunk$(chunk))
                    )
                )
            })
        )

    const exportChunk$ = ({tileIndex, timeSeries, dateRange}) => {
        const chunkDescription = `${description}_${tileIndex}_${dateRange}`
        const chunkDownloadDir = `${downloadDir}/${tileIndex}/chunk-${dateRange}`
        const export$ = exportImageToSepal$({
            image: timeSeries,
            folder: chunkDescription,
            description: chunkDescription,
            downloadDir: chunkDownloadDir,
            scale,
            crs: 'EPSG:4326',
            shardSize: EE_EXPORT_SHARD_SIZE,
            fileDimensions: EE_EXPORT_FILE_DIMENSIONS
        }).pipe(swallow())
        return concat(
            export$,
            of({completedChunk: true})
        )
    }

    const tileIds$ = ee.getInfo$(tiles.aggregate_array('system:index'), `time-series image ids ${description}`)

    return tileIds$.pipe(
        switchMap(tileIds => exportTiles$(tileIds)),
        tap(progress => log.trace(`time-series: ${JSON.stringify(progress)}`)),
        scan(
            (acc, progress) => {
                return ({
                    ...acc,
                    ...progress,
                    chunks: progress.chunks === undefined
                        ? acc.chunks + (progress.completedChunk ? 1 : 0)
                        : progress.chunks
                })
            },
            {tileIndex: 0, chunks: 0}
        ),
        map(toProgress)
    )
}

const postProcess$ = downloadDir =>
    terminal$('sepal-stack-time-series', [downloadDir])
        .pipe(
            tap(({stream, value}) => {
                if (value)
                    stream === 'stdout' ? log.info(value) : log.warn(value)
            }),
            swallow()
        )

const toProgress = ({totalTiles = 0, tileIndex = 0, totalChunks = 0, chunks = 0}) => {
    const currentTilePercent = totalChunks ? Math.round(100 * chunks / totalChunks) : 0
    const currentTile = tileIndex + 1
    return currentTilePercent < 100
        ? {
            totalChunks,
            chunks,
            totalTiles,
            tileIndex,
            defaultMessage: `Exported ${currentTilePercent}% of tile ${currentTile} out of ${totalTiles}.`,
            messageKey: 'tasks.retrieve.time_series_to_sepal.progress',
            messageArgs: {currentTilePercent, currentTile, totalTiles}
        }
        : {
            totalChunks,
            chunks,
            totalTiles,
            tileIndex,
            defaultMessage: `Assembling tile ${currentTile} out of ${totalTiles}...`,
            messageKey: 'tasks.retrieve.time_series_to_sepal.assembling',
            messageArgs: {currentTile, totalTiles}
        }
}

const extractDataSets = sources =>
    Object.values(sources)
        .flat()
        .map(dataSet =>
            dataSet === 'LANDSAT_TM'
                ? ['LANDSAT_4', 'LANDSAT_5']
                : dataSet === 'LANDSAT_TM_T2'
                    ? ['LANDSAT_4_T2', 'LANDSAT_5_T2']
                    : dataSet
        )
        .flat()
<commit_msg>Removed debug log.<commit_after>const {toFeatureCollection} = require('sepal/ee/aoi')
const {hasImagery: hasOpticalImagery} = require('sepal/ee/optical/collection')
const {hasImagery: hasRadarImagery} = require('sepal/ee/radar/collection')
const tile = require('sepal/ee/tile')
const {exportImageToSepal$} = require('../jobs/export/toSepal')
const {mkdirSafe$} = require('root/rxjs/fileSystem')
const {concat, forkJoin, from, of} = require('rx')
const Path = require('path')
const {map, mergeMap, scan, switchMap, tap} = require('rx/operators')
const {swallow} = require('sepal/rxjs/operators')
const {terminal$} = require('sepal/terminal')
const {sequence} = require('sepal/utils/array')
const moment = require('moment')
const ee = require('ee')
const {getCurrentContext$} = require('root/jobs/service/context')
const {getCollection$} = require('sepal/ee/timeSeries/collection')
const _ = require('lodash')
const log = require('sepal/log').getLogger('task')

const TILE_DEGREES = 2
const EE_EXPORT_SHARD_SIZE = 256
const EE_EXPORT_FILE_DIMENSIONS = 512
const DATE_DELTA = 3
const DATE_DELTA_UNIT = 'months'

module.exports = {
    submit$: (id, {description, recipe, indicator, scale}) =>
        getCurrentContext$().pipe(
            switchMap(({config}) => {
                const preferredDownloadDir = `${config.homeDir}/downloads/${description}/`
                return mkdirSafe$(preferredDownloadDir, {recursive: true}).pipe(
                    switchMap(downloadDir =>
                        export$({description, downloadDir, recipe, indicator, scale})
                    )
                )
            })
        )
}

const export$ = ({downloadDir, description, recipe, indicator, scale}) => {
    const aoi = recipe.model.aoi
    const dataSets = recipe.model.sources.dataSets
    const {startDate, endDate} = recipe.model.dates
    const reflectance = recipe.model.options.corrections.includes('SR') ? 'SR' : 'TOA'
    const tiles = tile(toFeatureCollection(aoi), TILE_DEGREES) // synchronous EE

    const exportTiles$ = tileIds => {
        const totalTiles = tileIds.length
        const tile$ = from(
            tileIds.map((tileId, tileIndex) =>
                ({tileId, tileIndex})
            )
        )
        return concat(
            of({totalTiles}),
            tile$.pipe(
                mergeMap(tile => exportTile$(tile), 1)
            )
        )
    }
    const exportTile$ = ({tileId, tileIndex}) =>
        concat(
            of({tileIndex, chunks: 0}),
            exportChunks$(createChunks$({tileId, tileIndex})),
            postProcess$(Path.join(downloadDir, `${tileIndex}`))
        )

    const createChunks$ = ({tileId, tileIndex}) => {
        const tile = tiles.filterMetadata('system:index', 'equals', tileId).first()
        const from = moment(startDate)
        const to = moment(endDate)
        const duration = moment(to).subtract(1, 'day').diff(from, DATE_DELTA_UNIT)
        const dateOffsets = sequence(0, duration, DATE_DELTA)
        const chunks$ = dateOffsets.map(dateOffset => {
            const start = moment(from).add(dateOffset, DATE_DELTA_UNIT)
            const startString = start.format('YYYY-MM-DD')
            const end = moment.min(moment(start).add(DATE_DELTA, DATE_DELTA_UNIT), to)
            const endString = end.format('YYYY-MM-DD')
            const dateRange = `${startString}_${endString}`
            return hasImagery$(
                tile.geometry(),
                ee.Date(startString),
                ee.Date(endString)
            ).pipe(
                switchMap(notEmpty => {
                    if (notEmpty) {
                        return createTimeSeries$(tile, startString, endString).pipe(
                            map(timeSeries =>
                                ({tileIndex, timeSeries, dateRange, notEmpty})
                            )
                        )
                    } else {
                        return of({tileIndex, dateRange, notEmpty})
                    }
                })
            )
        })
        return forkJoin(chunks$)
    }

    const isRadar = () => _.isEqual(Object.values(dataSets).flat(), ['SENTINEL_1'])

    const createTimeSeries$ = (feature, startDate, endDate) => {
        const images$ = getCollection$({recipe, bands: [indicator], startDate, endDate})
        return images$.pipe(
            map(images => {
                images = images.select(indicator)
                const distinctDateImages = images.distinct('date')
                const timeSeries = ee.ImageCollection(
                    ee.Join.saveAll('images')
                        .apply({
                            primary: distinctDateImages,
                            secondary: images,
                            condition: ee.Filter.equals({
                                leftField: 'date',
                                rightField: 'date'
                            })
                        })
                        .map(image => ee.ImageCollection(ee.List(image.get('images')))
                            .median()
                            .rename(image.getString('date'))
                        ))
                    .toBands()
                    .regexpRename('.*(.{10})', '$1')
                    .clip(feature.geometry())
                return timeSeries.select(timeSeries.bandNames().sort())
            })
        )
    }

    const hasImagery$ = (geometry, startDate, endDate) =>
        ee.getInfo$(
            isRadar()
                ? hasRadarImagery({geometry, startDate, endDate})
                : hasOpticalImagery({dataSets: extractDataSets(dataSets), reflectance, geometry, startDate, endDate}),
            'check if date range has imagery'
        )

    const exportChunks$ = chunks$ =>
        chunks$.pipe(
            switchMap(chunks => {
                const nonEmptyChunks = chunks.filter(({notEmpty}) => notEmpty)
                const totalChunks = nonEmptyChunks.length
                return concat(
                    of({totalChunks}),
                    from(nonEmptyChunks).pipe(
                        mergeMap(chunk => exportChunk$(chunk))
                    )
                )
            })
        )

    const exportChunk$ = ({tileIndex, timeSeries, dateRange}) => {
        const chunkDescription = `${description}_${tileIndex}_${dateRange}`
        const chunkDownloadDir = `${downloadDir}/${tileIndex}/chunk-${dateRange}`
        const export$ = exportImageToSepal$({
            image: timeSeries,
            folder: chunkDescription,
            description: chunkDescription,
            downloadDir: chunkDownloadDir,
            scale,
            crs: 'EPSG:4326',
            shardSize: EE_EXPORT_SHARD_SIZE,
            fileDimensions: EE_EXPORT_FILE_DIMENSIONS
        }).pipe(swallow())
        return concat(
            export$,
            of({completedChunk: true})
        )
    }

    const tileIds$ = ee.getInfo$(tiles.aggregate_array('system:index'), `time-series image ids ${description}`)

    return tileIds$.pipe(
        switchMap(tileIds => exportTiles$(tileIds)),
        tap(progress => log.trace(`time-series: ${JSON.stringify(progress)}`)),
        scan(
            (acc, progress) => {
                return ({
                    ...acc,
                    ...progress,
                    chunks: progress.chunks === undefined
                        ? acc.chunks + (progress.completedChunk ? 1 : 0)
                        : progress.chunks
                })
            },
            {tileIndex: 0, chunks: 0}
        ),
        map(toProgress)
    )
}

const postProcess$ = downloadDir =>
    terminal$('sepal-stack-time-series', [downloadDir])
        .pipe(
            tap(({stream, value}) => {
                if (value)
                    stream === 'stdout' ? log.info(value) : log.warn(value)
            }),
            swallow()
        )

const toProgress = ({totalTiles = 0, tileIndex = 0, totalChunks = 0, chunks = 0}) => {
    const currentTilePercent = totalChunks ? Math.round(100 * chunks / totalChunks) : 0
    const currentTile = tileIndex + 1
    return currentTilePercent < 100
        ? {
            totalChunks,
            chunks,
            totalTiles,
            tileIndex,
            defaultMessage: `Exported ${currentTilePercent}% of tile ${currentTile} out of ${totalTiles}.`,
            messageKey: 'tasks.retrieve.time_series_to_sepal.progress',
            messageArgs: {currentTilePercent, currentTile, totalTiles}
        }
        : {
            totalChunks,
            chunks,
            totalTiles,
            tileIndex,
            defaultMessage: `Assembling tile ${currentTile} out of ${totalTiles}...`,
            messageKey: 'tasks.retrieve.time_series_to_sepal.assembling',
            messageArgs: {currentTile, totalTiles}
        }
}

const extractDataSets = sources =>
    Object.values(sources)
        .flat()
        .map(dataSet =>
            dataSet === 'LANDSAT_TM'
                ? ['LANDSAT_4', 'LANDSAT_5']
                : dataSet === 'LANDSAT_TM_T2'
                    ? ['LANDSAT_4_T2', 'LANDSAT_5_T2']
                    : dataSet
        )
        .flat()
<|endoftext|>


<commit_before>var https = require("https");
var hu = require("./hubot_utils.js");
var interval = 60*10*1000; // 10 min interval in ms

/**
* Just needed for robot.brain.set and get functions
*/
var setRobot = function(r){
	robot = r;
};
module.exports.setRobot = setRobot;

/**
* Return balance for the address provided in parameters
* Params:
* 	- addr : string of the address.
*	- callback : callback function. (err, data);
*/
var getBalanceByAddr = function(addr, callback){
	var baseUrl = "https://etherchain.org/api/account/";
	var addr_call = baseUrl+addr;

	https.get(addr_call,function(res){
		var data="";
		var err = null;

		res.on('data', function(d) {
			data+=d;
		});

		res.on('end', function(){
			try{
				data = JSON.parse(data);
				if(data.status !== 1){
					callback("Erreur dans la balance", null);
				}else{
					callback(err, data);
				}
			}catch(e){
				console.log('Erreur dans la recuperation de la balance : ', e);
				console.log(data);
				return callback("Erreur dans la balance", null);
			}
		});
	});
};
module.exports.getBalanceByAddr = getBalanceByAddr;
/**
* Return balance for all address of an user
* Params:
* 	- user : string of the user (name/UID).
*	- callback : callback function. (err, data);
*/
var getBalanceByUser = function(user, callback){
	hu.listAddrFromUser(user, function(err, list){
		var accounts = {};
			accounts.data = [];
		var done = 0;

		if(err){
			return callback(err, null);
		}

		if(list.length == 0){
			return callback("No addresses found", null);
		}

		for(var i = 0; i < list.length; i++){
			(function(l, index, output){
				var addr = l[index];
				getBalanceByAddr(addr, function(err, data){

					if(err){
						return callback(err, null);
					}
					if(data.success){
						accounts.success = 1;
					}
					accounts.data.push({"addr" : addr, "balance" : data.data[0].balance});
					done++;

					console.log(done + '/' + l.length);

					if(done == l.length){
						return callback(null, accounts);
					}
				});
			})(list, i, accounts);
		}

	});
};
module.exports.getBalanceByUser = getBalanceByUser;

/*
* Return all transaction made (well, actually, only the first "page") by address
* Params:
* 	- addr : string of the address.
*	- offset : page number
*	- callback : callback function. (err, data);
*/
var getTransactionByAddr = function(addr, offset, callback){
	// set default and errored offset to 0
	if(!offset || offset < 0){
		offset = 0;
	}
	var baseUrl = "https://etherchain.org/api/account/";
	var addr_call = baseUrl+addr+"/tx/"+offset;

	https.get(addr_call,function(res){
		var data="";

		res.on('data', function(d) {
			data+=d;
		});

		res.on('end', function(){
			try{
				console.log(data);
				data = JSON.parse(data);

				if(data.status == 1){
					delete data.status;
					callback(null, data);
				}else{
					callback("Erreur dans les transactions", null);
				}
			}catch(e){
				console.error('Erreur dans la recuperation des transactions : ', e);
				// console.log(data);
				callback("Erreur dans les transactions" + e, null);
			}
		});
	});
};
module.exports.getTransactionByAddr = getTransactionByAddr;

/**
* Return all transaction made (well, actually, only the first "page") by an user
* Params:
* 	- user : string of the user (name/UID).
*	- callback : callback function. (err, data);
*/
var getTransactionByUser = function(user, callback){
	hu.listAddrFromUser(user, function(err, list){
		var tx = [];
		var done = 0;

		if(err){
			return callback(err, null);
		}
		for(var i = 0; i < list.length; i++){
			(function(l, index, output){
				var addr = l[index];
				getTransactionByAddr(addr, 0,function(err, data){

					if(err){
						return callback(err, null);
					}
					tx = data.data;
					console.log("tx :", tx);
					done++;

					console.log(done + '/' + l.length);

					if(done == l.length){
						return callback(null, tx);
					}
				});
			})(list, i, tx);
		}

	});
};
module.exports.getTransactionByUser = getTransactionByUser;

/**
* Private method for getting price of ether from different currency :
* usd, eur, cny, gbp, cad, rub, hkd, jpy, aud, btc
* This request provide price, market cap, supply and volume
*/
var _getPrice = function(callback){
	// https://coinmarketcap-nexuist.rhcloud.com/api/eth
	var http = require("https");

	var options = {
		"method": "GET",
		"hostname": "coinmarketcap-nexuist.rhcloud.com",
		"port": null,
		"path": "/api/eth"
	};

	var req = http.request(options, function (res) {
		var chunks = [];

		res.on("data", function (chunk) {
			chunks.push(chunk);
		});

		res.on("end", function () {
			var body = Buffer.concat(chunks);
			var data = JSON.parse(body);
			callback(null, data);
		});
	});

	req.end();
};

/**
* Wrapper for getPrice, get price from memory instead of request whenever possible.
*/
var getPrice = function(callback){
	var _price = robot.brain.get("ether_price");

	if((parseInt(_price.requestTimestamp)*1000)-interval < Date.now()){
		console.warn("get price from internet");
		_getPrice(callback);
	}else{
		console.warn("get price from memory");
		callback(null, robot.brain.get("ether_price"));
	}
};
module.exports.getPrice = getPrice;

/**
* Care : Writing to requestTimestamp instead of timestamp on the "data" object.
* The actual "timestamp" object is not the timestamp of the request but the timestamp of the update in their system.
*/
function init(){

	_getPrice(function(err, data){
		console.warn("Get price init");
		if(err){
			console.error("Error in getPrice", err);
		}
		data.requestTimestamp = Date.now();
		robot.brain.set("ether_price", data);
	});

	setInterval(function(){
		console.warn("Starting getPrice interval");
		_getPrice(function(err, data){
			if(err){
				console.error("Error in getPrice", err);
			}
			data.requestTimestamp = Date.now();
			robot.brain.set("ether_price", data);
			console.warn("getPrice interval ended : ", data);
		});
	},interval);
}
init();<commit_msg>Add support for empty accounts<commit_after>var https = require("https");
var hu = require("./hubot_utils.js");
var interval = 60*10*1000; // 10 min interval in ms

/**
* Just needed for robot.brain.set and get functions
*/
var setRobot = function(r){
	robot = r;
};
module.exports.setRobot = setRobot;

/**
* Return balance for the address provided in parameters
* Params:
* 	- addr : string of the address.
*	- callback : callback function. (err, data);
*/
var getBalanceByAddr = function(addr, callback){
	var baseUrl = "https://etherchain.org/api/account/";
	var addr_call = baseUrl+addr;

	https.get(addr_call,function(res){
		var data="";
		var err = null;

		res.on('data', function(d) {
			data+=d;
		});

		res.on('end', function(){
			try{
				data = JSON.parse(data);
				if(data.status !== 1){
					callback("Erreur dans la balance", null);
				}else{
					callback(err, data);
				}
			}catch(e){
				console.log('Erreur dans la recuperation de la balance : ', e);
				console.log(data);
				return callback("Erreur dans la balance", null);
			}
		});
	});
};
module.exports.getBalanceByAddr = getBalanceByAddr;
/**
* Return balance for all address of an user
* Params:
* 	- user : string of the user (name/UID).
*	- callback : callback function. (err, data);
*/
var getBalanceByUser = function(user, callback){
	hu.listAddrFromUser(user, function(err, list){
		var accounts = {};
			accounts.data = [];
		var done = 0;

		if(err){
			return callback(err, null);
		}

		if(list.length == 0){
			return callback("No addresses found", null);
		}

		for(var i = 0; i < list.length; i++){
			(function(l, index, acc){
				var addr = l[index];
				getBalanceByAddr(addr, function(err, data){
					if(err){
						return callback(err, null);
					}
					if(data.success){
						acc.success = 1;
					}

					/*
					* Set balance to 0 if no data found (no transaction, brand new address)
					*/
					var balanceTmp = 0;
					try{
						balanceTmp = data.data[0].balance;
					}catch(e){
						console.error("Empty balance or malformed object in getBalanceByUser : ", e);
						balanceTmp = 0;
					}

					acc.data.push({"addr" : addr, "balance" : balanceTmp});
					done++;

					console.log(done + '/' + l.length);

					if(done == l.length){
						return callback(null, acc);
					}
				});
			})(list, i, accounts);
		}

	});
};
module.exports.getBalanceByUser = getBalanceByUser;

/*
* Return all transaction made (well, actually, only the first "page") by address
* Params:
* 	- addr : string of the address.
*	- offset : page number
*	- callback : callback function. (err, data);
*/
var getTransactionByAddr = function(addr, offset, callback){
	// set default and errored offset to 0
	if(!offset || offset < 0){
		offset = 0;
	}
	var baseUrl = "https://etherchain.org/api/account/";
	var addr_call = baseUrl+addr+"/tx/"+offset;

	https.get(addr_call,function(res){
		var data="";

		res.on('data', function(d) {
			data+=d;
		});

		res.on('end', function(){
			try{
				console.log(data);
				data = JSON.parse(data);

				if(data.status == 1){
					delete data.status;
					callback(null, data);
				}else{
					callback("Erreur dans les transactions", null);
				}
			}catch(e){
				console.error('Erreur dans la recuperation des transactions : ', e);
				// console.log(data);
				callback("Erreur dans les transactions" + e, null);
			}
		});
	});
};
module.exports.getTransactionByAddr = getTransactionByAddr;

/**
* Return all transaction made (well, actually, only the first "page") by an user
* Params:
* 	- user : string of the user (name/UID).
*	- callback : callback function. (err, data);
*/
var getTransactionByUser = function(user, callback){
	hu.listAddrFromUser(user, function(err, list){
		var tx = [];
		var done = 0;

		if(err){
			return callback(err, null);
		}
		for(var i = 0; i < list.length; i++){
			(function(l, index, output){
				var addr = l[index];
				getTransactionByAddr(addr, 0,function(err, data){

					if(err){
						return callback(err, null);
					}
					tx = data.data;
					console.log("tx :", tx);
					done++;

					console.log(done + '/' + l.length);

					if(done == l.length){
						return callback(null, tx);
					}
				});
			})(list, i, tx);
		}

	});
};
module.exports.getTransactionByUser = getTransactionByUser;

/**
* Private method for getting price of ether from different currency :
* usd, eur, cny, gbp, cad, rub, hkd, jpy, aud, btc
* This request provide price, market cap, supply and volume
*/
var _getPrice = function(callback){
	// https://coinmarketcap-nexuist.rhcloud.com/api/eth
	var http = require("https");

	var options = {
		"method": "GET",
		"hostname": "coinmarketcap-nexuist.rhcloud.com",
		"port": null,
		"path": "/api/eth"
	};

	var req = http.request(options, function (res) {
		var chunks = [];

		res.on("data", function (chunk) {
			chunks.push(chunk);
		});

		res.on("end", function () {
			var body = Buffer.concat(chunks);
			var data = JSON.parse(body);
			callback(null, data);
		});
	});

	req.end();
};

/**
* Wrapper for getPrice, get price from memory instead of request whenever possible.
*/
var getPrice = function(callback){
	var _price = robot.brain.get("ether_price");

	if((parseInt(_price.requestTimestamp)*1000)-interval < Date.now()){
		console.warn("get price from internet");
		_getPrice(callback);
	}else{
		console.warn("get price from memory");
		callback(null, robot.brain.get("ether_price"));
	}
};
module.exports.getPrice = getPrice;

/**
* Care : Writing to requestTimestamp instead of timestamp on the "data" object.
* The actual "timestamp" object is not the timestamp of the request but the timestamp of the update in their system.
*/
function init(){

	_getPrice(function(err, data){
		console.warn("Get price init");
		if(err){
			console.error("Error in getPrice", err);
		}
		data.requestTimestamp = Date.now();
		robot.brain.set("ether_price", data);
	});

	setInterval(function(){
		console.warn("Starting getPrice interval");
		_getPrice(function(err, data){
			if(err){
				console.error("Error in getPrice", err);
			}
			data.requestTimestamp = Date.now();
			robot.brain.set("ether_price", data);
			console.warn("getPrice interval ended : ", data);
		});
	},interval);
}
init();<|endoftext|>