<commit_before>import Ember from 'ember';
import TypeClass from 'ember-bootstrap-components/mixins/type-class';
import SizeClass from 'ember-bootstrap-components/mixins/size-class';
import I18nSupport from 'ember-bootstrap-components/mixins/i18n-support';

/**
 Implements a HTML button element, with support for all [Bootstrap button CSS styles](http://getbootstrap.com/css/#buttons)
 as well as advanced functionality such as button states.

 ### Basic Usage

 ```hbs
 \{{#bs-button type="primary" icon="glyphicon glyphicon-download"}}
    Downloads
 \{{/bs-button}}
 ```

 ### Actions

 Set the action property of the component to send an action to your controller. The following parameters will be sent:
 * value: the button's value, see the `value` property
 * event: the browsers event object
 * callback: a function that may be called from the action handler to supply a Promise to the button component for automatic state handling

 ```hbs
 \{{#bs-button type="primary" icon="glyphicon glyphicon-download" action="download"}}
    Download
 \{{/bs-button}}
 ```

 ### States

 Use the `textState` property to change the label of the button. You can bind it to a controller property to set a "loading" state for example.
 The label of the button will be taken from the `<state>Text` property.

 ```hbs
 \{{bs-button type="primary" icon="glyphicon glyphicon-download" textState=buttonState defaultText="Download" loadingText="Loading..." action="download"}}
 ```

 ```js
 App.ApplicationController = Ember.Controller.extend({
   buttonState: "default"
   actions: {
     download: function() {
       this.set("buttonState", "loading");
     }
   }
 });
 ```

 ### Promise support for automatic state change

 When using the callback function of the click action to supply a Promise for any asynchronous operation the button will
 manage its `textState` property automatically, changing its value according to the state of the promise:
 "default" > "pending" > "resolved"/"rejected"

 ```hbs
 \{{bs-button type="primary" icon="glyphicon glyphicon-download" defaultText="Download" pendingText="Loading..." resolvedText="Completed!" rejectedText="Oups!?" action="download"}}
 ```

 ```js
 App.ApplicationController = Ember.Controller.extend({
   actions: {
     download: function(actionParam, evt, cb) {
       promise = new Ember.RSVP.Promise(...);
       cb(promise);
     }
   }
 });
 ```

 ### I18n support

 Supports translateable properties if [ember-i18n](https://github.com/jamesarosen/ember-i18n) is present.
 See {{#crossLink "Mixins.I18nSupport"}}{{/crossLink}}

 @class Button
 @namespace Components
 @extends Ember.Component
 @uses Mixins.TypeClass
 @uses Mixins.SizeClass
 @uses Mixins.I18nSupport
*/
export default Ember.Component.extend(TypeClass, SizeClass, I18nSupport, {
    tagName: 'button',
    classNames: ['btn'],
    classNameBindings: ['active', 'block:btn-block'],

    /**
     * @property classTypePrefix
     * @type String
     * @default 'btn'
     * @protected
     */
    classTypePrefix: 'btn',

    attributeBindings: ['id', 'disabled', 'buttonType:type', 'dismiss:data-dismiss', 'contentDismiss:data-dismiss', '_type:type', 'style'],

    getPojoProperties: function(pojo) {
      if (Ember.isEmpty(pojo)) {
        return [];
      }
      return Object.keys(pojo);
    },
    getProxiedProperties: function(proxyObject) {
      var contentProperties, objectProperties, prototypeProperties;
      contentProperties = this.getObjectProperties(proxyObject.get('content'));
      prototypeProperties = Object.keys(proxyObject.constructor.prototype);
      objectProperties = this.getPojoProperties(proxyObject);
      return contentProperties.concat(prototypeProperties).concat(objectProperties).uniq();
    },
    getEmberObjectProperties: function(emberObject) {
      var objectProperties, prototypeProperties;
      prototypeProperties = Object.keys(emberObject.constructor.prototype);
      objectProperties = this.getPojoProperties(emberObject);
      return prototypeProperties.concat(objectProperties).uniq();
    },
    getEmberDataProperties: function(emberDataObject) {
      var attributes, keys;
      attributes = Ember.get(emberDataObject.constructor, 'attributes');
      keys = Ember.get(attributes, 'keys.list');
      return Ember.getProperties(emberDataObject, keys);
    },
    getObjectProperties: function(object) {
      if (object instanceof DS.Model) {
        return this.getEmberDataProperties(object);
      } else if (object instanceof Ember.ObjectProxy || Ember._ProxyMixin.detect(object)) {
        return this.getProxiedProperties(object);
      } else if (object instanceof Ember.Object) {
        return this.getEmberObjectProperties(object);
      } else {
        return this.getPojoProperties(object);
      }
    },

    init: function() {
      var me, properties;
      this._super();
      me = this;
      if ((this.get('content') != null) && Ember.typeOf(this.get('content')) === 'instance') {
        properties = this.getObjectProperties(this.get('content'));
        return this.getProperties(properties);
      } else {
        if (this.get('defaultText') == null) {
          return this.set('defaultText', this.get('content'));
        }
      }
    },

    /**
     * Default label of the button. Not need if used as a block component
     *
     * @property defaultText
     * @type string
     * @public
     */
    defaultText: null,

    /**
     * Property to disable the button
     *
     * @property disabled
     * @type boolaen
     * @default false
     * @public
     */
    disabled: false,

    /**
     * Set the type of the button, either 'button' or 'submit'
     *
     * @property buttonType
     * @type String
     * @default 'button'
     * @public
     */
    buttonType: 'button',

    /**
     * Set the 'active' class to apply active/pressed CSS styling
     *
     * @property active
     * @type boolean
     * @default false
     * @public
     */
    active: false,

    /**
     * Property for block level buttons
     *
     * See the [Bootstrap docs](http://getbootstrap.com/css/#buttons-sizes)
     * @property block
     * @type boolean
     * @default false
     * @public
     */
    block: false,

    /**
     * If toggle property is true, clicking the button will toggle the active state
     *
     * @property toggle
     * @type boolean
     * @default false
     * @public
     */
    toggle: false,

    /**
     * If button is active and this is set, the icon property will match this property
     *
     * @property iconActive
     * @type String
     * @public
     */
    iconActive: null,

    /**
     * If button is inactive and this is set, the icon property will match this property
     *
     * @property iconInactive
     * @type String
     * @public
     */
    iconInactive: null,

    /**
     * Class(es) (e.g. glyphicons or font awesome) to use as a button icon
     * This will render a  element in front of the button's label
     *
     * @property icon
     * @type String
     * @readonly
     * @protected
     */
    icon: Ember.computed('active', function() {
        if (this.get('active')) {
            return this.get('iconActive');
        } else {
            return this.get('iconInactive');
        }
    }),

    /**
     * Supply a value that will be associated with this button. This will be send
     * as a parameter of the default action triggered when clicking the button
     *
     * @property value
     * @type any
     * @public
     */
    value: null,

    /**
     * State of the button. The button's label (if not used as a block component) will be set to the
     * `<state>Text` property.
     * This property will automatically be set when using a click action that supplies the callback with an promise
     *
     * @property textState
     * @type String
     * @default 'default'
     * @protected
     */
    textState: 'default',

    /**
     * Set this to true to reset the state. A typical use case is to bind this attribute with ember-data isDirty flag.
     *
     * @property reset
     * @type boolean
     * @public
     */
    reset: null,

    /**
     * This will reset the state property to 'default', and with that the button's label to defaultText
     *
     * @method resetState
     * @protected
     */
    resetState: function() {
        this.set('textState', 'default');
    },

    resetObserver: Ember.observer('reset', function(){
        if(this.get('reset')){
            this.resetState();
        }
    }),

    text: Ember.computed('textState', 'defaultText', 'pendingText', 'resolvedText', 'rejectedText', function() {
        return this.getWithDefault(this.get('textState') + 'Text', this.get('defaultText'));
    }),

    /**
     * Click handler. This will send the default "action" action, with the following parameters:
     * * value of the button (that is the value of the "value" property)
     * * original event object of the click event
     * * callback: call that with a promise object, and the buttons state will automatically set to "pending", "resolved" and/or "rejected"
     *
     * @method click
     * @protected
     * @param evt
     */
    click: function(evt) {
        if (this.get('toggle')) {
            this.toggleProperty('active');
        }
        var that = this;
        var callback = function(promise) {
            if (promise) {
                that.set('textState', 'pending');
                promise.then(
                    function(){
                        if (!that.get('isDestroyed')) {
                            that.set('textState', 'resolved');
                        }
                    },
                    function(){
                        if (!that.get('isDestroyed')) {
                            that.set('textState', 'rejected');
                        }
                    }
                );
            }
        };
        this.sendAction('action', this.get('value'), evt, callback);
    }

});
<commit_msg>Add proxy to buttons<commit_after>import Ember from 'ember';
import TypeClass from 'ember-bootstrap-components/mixins/type-class';
import SizeClass from 'ember-bootstrap-components/mixins/size-class';
import I18nSupport from 'ember-bootstrap-components/mixins/i18n-support';

/**
 Implements a HTML button element, with support for all [Bootstrap button CSS styles](http://getbootstrap.com/css/#buttons)
 as well as advanced functionality such as button states.

 ### Basic Usage

 ```hbs
 \{{#bs-button type="primary" icon="glyphicon glyphicon-download"}}
    Downloads
 \{{/bs-button}}
 ```

 ### Actions

 Set the action property of the component to send an action to your controller. The following parameters will be sent:
 * value: the button's value, see the `value` property
 * event: the browsers event object
 * callback: a function that may be called from the action handler to supply a Promise to the button component for automatic state handling

 ```hbs
 \{{#bs-button type="primary" icon="glyphicon glyphicon-download" action="download"}}
    Download
 \{{/bs-button}}
 ```

 ### States

 Use the `textState` property to change the label of the button. You can bind it to a controller property to set a "loading" state for example.
 The label of the button will be taken from the `<state>Text` property.

 ```hbs
 \{{bs-button type="primary" icon="glyphicon glyphicon-download" textState=buttonState defaultText="Download" loadingText="Loading..." action="download"}}
 ```

 ```js
 App.ApplicationController = Ember.Controller.extend({
   buttonState: "default"
   actions: {
     download: function() {
       this.set("buttonState", "loading");
     }
   }
 });
 ```

 ### Promise support for automatic state change

 When using the callback function of the click action to supply a Promise for any asynchronous operation the button will
 manage its `textState` property automatically, changing its value according to the state of the promise:
 "default" > "pending" > "resolved"/"rejected"

 ```hbs
 \{{bs-button type="primary" icon="glyphicon glyphicon-download" defaultText="Download" pendingText="Loading..." resolvedText="Completed!" rejectedText="Oups!?" action="download"}}
 ```

 ```js
 App.ApplicationController = Ember.Controller.extend({
   actions: {
     download: function(actionParam, evt, cb) {
       promise = new Ember.RSVP.Promise(...);
       cb(promise);
     }
   }
 });
 ```

 ### I18n support

 Supports translateable properties if [ember-i18n](https://github.com/jamesarosen/ember-i18n) is present.
 See {{#crossLink "Mixins.I18nSupport"}}{{/crossLink}}

 @class Button
 @namespace Components
 @extends Ember.Component
 @uses Mixins.TypeClass
 @uses Mixins.SizeClass
 @uses Mixins.I18nSupport
*/
export default Ember.Component.extend(Ember._Proxy, TypeClass, SizeClass, I18nSupport, {
    tagName: 'button',
    classNames: ['btn'],
    classNameBindings: ['active', 'block:btn-block'],

    /**
     * @property classTypePrefix
     * @type String
     * @default 'btn'
     * @protected
     */
    classTypePrefix: 'btn',

    attributeBindings: ['id', 'disabled', 'buttonType:type', 'dismiss:data-dismiss', 'contentDismiss:data-dismiss', '_type:type', 'style'],

    getPojoProperties: function(pojo) {
      if (Ember.isEmpty(pojo)) {
        return [];
      }
      return Object.keys(pojo);
    },
    getProxiedProperties: function(proxyObject) {
      var contentProperties, objectProperties, prototypeProperties;
      contentProperties = this.getObjectProperties(proxyObject.get('content'));
      prototypeProperties = Object.keys(proxyObject.constructor.prototype);
      objectProperties = this.getPojoProperties(proxyObject);
      return contentProperties.concat(prototypeProperties).concat(objectProperties).uniq();
    },
    getEmberObjectProperties: function(emberObject) {
      var objectProperties, prototypeProperties;
      prototypeProperties = Object.keys(emberObject.constructor.prototype);
      objectProperties = this.getPojoProperties(emberObject);
      return prototypeProperties.concat(objectProperties).uniq();
    },
    getEmberDataProperties: function(emberDataObject) {
      var attributes, keys;
      attributes = Ember.get(emberDataObject.constructor, 'attributes');
      keys = Ember.get(attributes, 'keys.list');
      return Ember.getProperties(emberDataObject, keys);
    },
    getObjectProperties: function(object) {
      if (object instanceof DS.Model) {
        return this.getEmberDataProperties(object);
      } else if (object instanceof Ember.ObjectProxy || Ember._ProxyMixin.detect(object)) {
        return this.getProxiedProperties(object);
      } else if (object instanceof Ember.Object) {
        return this.getEmberObjectProperties(object);
      } else {
        return this.getPojoProperties(object);
      }
    },

    init: function() {
      var me, properties;
      this._super();
      me = this;
      if ((this.get('content') != null) && Ember.typeOf(this.get('content')) === 'instance') {
        properties = this.getObjectProperties(this.get('content'));
        return this.getProperties(properties);
      } else {
        if (this.get('defaultText') == null) {
          return this.set('defaultText', this.get('content'));
        }
      }
    },

    /**
     * Default label of the button. Not need if used as a block component
     *
     * @property defaultText
     * @type string
     * @public
     */
    defaultText: null,

    /**
     * Property to disable the button
     *
     * @property disabled
     * @type boolaen
     * @default false
     * @public
     */
    disabled: false,

    /**
     * Set the type of the button, either 'button' or 'submit'
     *
     * @property buttonType
     * @type String
     * @default 'button'
     * @public
     */
    buttonType: 'button',

    /**
     * Set the 'active' class to apply active/pressed CSS styling
     *
     * @property active
     * @type boolean
     * @default false
     * @public
     */
    active: false,

    /**
     * Property for block level buttons
     *
     * See the [Bootstrap docs](http://getbootstrap.com/css/#buttons-sizes)
     * @property block
     * @type boolean
     * @default false
     * @public
     */
    block: false,

    /**
     * If toggle property is true, clicking the button will toggle the active state
     *
     * @property toggle
     * @type boolean
     * @default false
     * @public
     */
    toggle: false,

    /**
     * If button is active and this is set, the icon property will match this property
     *
     * @property iconActive
     * @type String
     * @public
     */
    iconActive: null,

    /**
     * If button is inactive and this is set, the icon property will match this property
     *
     * @property iconInactive
     * @type String
     * @public
     */
    iconInactive: null,

    /**
     * Class(es) (e.g. glyphicons or font awesome) to use as a button icon
     * This will render a  element in front of the button's label
     *
     * @property icon
     * @type String
     * @readonly
     * @protected
     */
    icon: Ember.computed('active', function() {
        if (this.get('active')) {
            return this.get('iconActive');
        } else {
            return this.get('iconInactive');
        }
    }),

    /**
     * Supply a value that will be associated with this button. This will be send
     * as a parameter of the default action triggered when clicking the button
     *
     * @property value
     * @type any
     * @public
     */
    value: null,

    /**
     * State of the button. The button's label (if not used as a block component) will be set to the
     * `<state>Text` property.
     * This property will automatically be set when using a click action that supplies the callback with an promise
     *
     * @property textState
     * @type String
     * @default 'default'
     * @protected
     */
    textState: 'default',

    /**
     * Set this to true to reset the state. A typical use case is to bind this attribute with ember-data isDirty flag.
     *
     * @property reset
     * @type boolean
     * @public
     */
    reset: null,

    /**
     * This will reset the state property to 'default', and with that the button's label to defaultText
     *
     * @method resetState
     * @protected
     */
    resetState: function() {
        this.set('textState', 'default');
    },

    resetObserver: Ember.observer('reset', function(){
        if(this.get('reset')){
            this.resetState();
        }
    }),

    text: Ember.computed('textState', 'defaultText', 'pendingText', 'resolvedText', 'rejectedText', function() {
        return this.getWithDefault(this.get('textState') + 'Text', this.get('defaultText'));
    }),

    /**
     * Click handler. This will send the default "action" action, with the following parameters:
     * * value of the button (that is the value of the "value" property)
     * * original event object of the click event
     * * callback: call that with a promise object, and the buttons state will automatically set to "pending", "resolved" and/or "rejected"
     *
     * @method click
     * @protected
     * @param evt
     */
    click: function(evt) {
        if (this.get('toggle')) {
            this.toggleProperty('active');
        }
        var that = this;
        var callback = function(promise) {
            if (promise) {
                that.set('textState', 'pending');
                promise.then(
                    function(){
                        if (!that.get('isDestroyed')) {
                            that.set('textState', 'resolved');
                        }
                    },
                    function(){
                        if (!that.get('isDestroyed')) {
                            that.set('textState', 'rejected');
                        }
                    }
                );
            }
        };
        this.sendAction('action', this.get('value'), evt, callback);
    }

});
<|endoftext|>


<commit_before>(function () {
    "option strict"

    co = require('co');
    thunkify = require('thunkify');
    fs = require('fs');
    path = require('path');
    argv = require('optimist').argv;

    spawn = require('child_process').spawn;
    _exec = require('child_process').exec
    exec = thunkify(function(cmd, cb) {
        console.log(cmd);
        _exec(cmd, function(err, stdout, stderr) {
            cb(err, stdout.substring(0, stdout.length - 1));
        });
    });

    react = require('react-tools');

    module.exports = function(config) {

        ensureDirExists = function*(file) {
            var dir = path.dirname(file);
            if (!fs.existsSync(dir)) {
                yield exec("mkdir " + dir + " -p");
            }
        }

        /*
            When the build starts, recreate the app directory
        */
        config.onStart(function*() {
            console.log("Started fora/www-client build");
            yield exec("rm app -rf");
            yield exec("mkdir app");
        });

        /*
            Compile all coffee-script files
            Coffee doesn't do coffee {src} {dest} yet, hence the redirection.
        */
        config.files(["src/*.coffee"], function*(filePath) {
            var dest = filePath.replace(/^src\//, 'app/').replace(/\.coffee$/, '.js');
            yield ensureDirExists(dest);
            yield exec("coffee -cs < " + filePath + " > " + dest);
        });

        /*
            Compile all JSX files from the server directory.
        */
        config.files(["../server/app/app-lib/fora-ui/*.js", "../server/app/extensions/*.js", "../server/app/website/views/*.js"], function*(filePath) {
            var clientDest = filePath.replace(/^\.\.\/server\/app\//, "app/www/shared/");
            yield ensureDirExists(clientDest);
            yield exec("cp " + filePath + " " + clientDest);
        });

        /*
            Copy other files, except .coffee and .less
        */
        config.files(["src/www/*.*"], function*(filePath) {
            if (['.coffee', '.less'].indexOf(path.extname(filePath)) === -1) {
                var dest = filePath.replace(/^src\//, 'app/');
                yield ensureDirExists(dest);
                yield exec("cp " + filePath + " " + dest);
                yield appRestart.call(this);
            }
        });

        /*
            Compile less files
        */
        config.files(["src/www/css/*.less"], function*(filePath) {
            if (!this.state.lessCompilationPending) {
                this.state.lessCompilationPending = true;
                this.onComplete(function*() {
                    yield exec("lessc --verbose src/www/css/main.less app/www/css/main.css");
                });
            }
        });

        /*
            Do regeneator transform on all client side js files
        */
        config.files(["app/www/js/*.js", "app/www/shared/*.js"], function*(filePath) {
            yield exec("regenerator " + filePath + " > " + filePath);
        });
    }
}());

<commit_msg>Fixed regenerator output<commit_after>(function () {
    "option strict"

    co = require('co');
    thunkify = require('thunkify');
    fs = require('fs');
    path = require('path');
    argv = require('optimist').argv;

    spawn = require('child_process').spawn;
    _exec = require('child_process').exec
    exec = thunkify(function(cmd, cb) {
        console.log(cmd);
        _exec(cmd, function(err, stdout, stderr) {
            cb(err, stdout.substring(0, stdout.length - 1));
        });
    });

    react = require('react-tools');

    module.exports = function(config) {

        ensureDirExists = function*(file) {
            var dir = path.dirname(file);
            if (!fs.existsSync(dir)) {
                yield exec("mkdir " + dir + " -p");
            }
        }

        /*
            When the build starts, recreate the app directory
        */
        config.onStart(function*() {
            console.log("Started fora/www-client build");
            yield exec("rm app -rf");
            yield exec("mkdir app");
        });

        /*
            Compile all coffee-script files
            Coffee doesn't do coffee {src} {dest} yet, hence the redirection.
        */
        config.files(["src/*.coffee"], function*(filePath) {
            var dest = filePath.replace(/^src\//, 'app/').replace(/\.coffee$/, '.js');
            yield ensureDirExists(dest);
            yield exec("coffee -cs < " + filePath + " > " + dest);
        });

        /*
            Compile all JSX files from the server directory.
        */
        config.files(["../server/app/app-lib/fora-ui/*.js", "../server/app/extensions/*.js", "../server/app/website/views/*.js"], function*(filePath) {
            var clientDest = filePath.replace(/^\.\.\/server\/app\//, "app/www/shared/");
            yield ensureDirExists(clientDest);
            yield exec("cp " + filePath + " " + clientDest);
        });

        /*
            Copy other files, except .coffee and .less
        */
        config.files(["src/www/*.*"], function*(filePath) {
            if (['.coffee', '.less'].indexOf(path.extname(filePath)) === -1) {
                var dest = filePath.replace(/^src\//, 'app/');
                yield ensureDirExists(dest);
                yield exec("cp " + filePath + " " + dest);
                yield appRestart.call(this);
            }
        });

        /*
            Compile less files
        */
        config.files(["src/www/css/*.less"], function*(filePath) {
            if (!this.state.lessCompilationPending) {
                this.state.lessCompilationPending = true;
                this.onComplete(function*() {
                    yield exec("lessc --verbose src/www/css/main.less app/www/css/main.css");
                });
            }
        });

        /*
            Do regeneator transform on all client side js files
        */
        config.files(["app/www/js/*.js", "app/www/shared/*.js"], function*(filePath) {
            result = yield exec("regenerator " + filePath);
            fs.writeFileSync(filePath, result);
        });
    }
}());

<|endoftext|>


<commit_before>"use strict";
/*jslint node:true, indent: 2, nomen: true */
var crypto = require("crypto"),
  url = require("url"),
  events = require('events'),
  _ = require('underscore')._,
  express = require('express'),
  config = require('./config.json');

var tupasPath = "/tupas",
  cancelPath = tupasPath + "/cancel",
  okPath = tupasPath + "/ok",
  rejectPath = tupasPath + "/reject";

var SHA256 = "03",
  TUPAS_MESSAGE_TYPE = "701",
  LANG_CODES = ['FI', 'SV', 'EN'];

var tupasFormTemplate = _.template(
                         '<form id="<%= id %>-form" method="POST" action="<%= bankAuthUrl %>" class="tupas-button">'+
                         '<div id="<%= id %>-login" style="cursor: pointer">' +
                         '
<img src="<%= imgPath %>" alt="<%= name %>">
' +
                         '
<%= name %>
' +
                         '
' +
                         '<input name="A01Y_ACTION_ID" type="hidden" value="<%= messageType %>">' +
                         '<input name="A01Y_VERS" type="hidden" value="<%= version %>">' +
                         '<input name="A01Y_RCVID" type="hidden" value="<%= vendorId %>">' +
                         '<input name="A01Y_LANGCODE" type="hidden" value="<%= languageCode %>">' +
                         '<input name="A01Y_STAMP" type="hidden" value="<%= identifier %>">' +
                         '<input name="A01Y_IDTYPE" type="hidden" value="<%= idType %>">' +
                         '<input name="A01Y_RETLINK" type="hidden" value="<%= returnLink %>">' +
                         '<input name="A01Y_CANLINK" type="hidden" value="<%= cancelLink %>">' +
                         '<input name="A01Y_REJLINK" type="hidden" value="<%= rejectLink %>">' +
                         '<input name="A01Y_KEYVERS" type="hidden" value="<%= keyVersion %>">' +
                         '<input name="A01Y_ALG" type="hidden" value="<%= algorithmType %>">' +
                         '<input name="A01Y_MAC" type="hidden" value="<%= mac %>">' +
                         '' +
                         '</form>');

function requireArgument(argValue, argName) {
  if (typeof argValue === "undefined" || argValue === null) {
    throw new Error("Missing required argument " + argName + ".");
  }
}

exports.create = function (globalOpts, bankOpts) {
  requireArgument(globalOpts.appHandler, "globalOpts.appHandler");
  requireArgument(globalOpts.hostUrl, "globalOpts.hostUrl");
  var contextPath = url.parse(globalOpts.hostUrl).pathname;
  if (contextPath === '/') contextPath = '';

  var tupas = Object.create(events.EventEmitter.prototype),
    banks = updatedBankConfigsWith(bankOpts),
    vendorOpts = _.extend(
      {},
      globalOpts,
      { returnUrls : returnUrls(globalOpts.hostUrl) }
    );

  bindReturnUrlsToHandler(tupas, vendorOpts.appHandler, contextPath);
  vendorOpts.appHandler.use(express.static(__dirname + '/public'));

  tupas.banks = _.pluck(banks, 'id');
  tupas.requestMac = generateMacForRequest;
  tupas.responseMac = function (params) {
    return generateMacForResponse(params, banks)
  };

  tupas.buildRequestParams = function (bankId, languageCode, requestId) {
    return buildParamsForRequest(findConfig(bankId, banks),
      languageCode, vendorOpts.returnUrls, requestId);
  };

  tupas.tupasButton = function (bankId, languageCode, requestId) {
    var formParams = tupas.buildRequestParams(bankId, languageCode, requestId);
    return tupasFormTemplate(formParams);
  };

  return tupas;
};

function returnUrls (hostUrl) {
  return {
    ok: hostUrl + okPath,
    cancel: hostUrl + cancelPath,
    reject: hostUrl + rejectPath
  };
}

function updatedBankConfigsWith (bankOpts) {
  var updatedDefaults = mergeWithDefaults(bankOpts);
  var defaultBankIds = _.pluck(updatedDefaults, 'id');
  var newBanks = _.reject(bankOpts, function (bank) {
    return _.contains(defaultBankIds, bank.id)
  });

  return updatedDefaults.concat(newBanks);
}

function mergeWithDefaults (bankOpts) {
  return _.map(config.banks, function (bank) {
    var vendorOpts = _.find(bankOpts, function (bankConf) {
      return bankConf.id == bank.id;
    });

    if (vendorOpts) {
      return _.extend({}, bank, vendorOpts)
    } else {
      return bank
    }
  });
}

function bindReturnUrlsToHandler (tupas, handler, contextPath) {
  handler.post(contextPath + okPath, ok(tupas)); // Danske Bank uses POST.
  handler.post(contextPath + cancelPath, cancel(tupas));
  handler.post(contextPath + rejectPath, reject(tupas));
  handler.get(contextPath + okPath, ok(tupas));  // Others use GET.
  handler.get(contextPath + cancelPath, cancel(tupas));
  handler.get(contextPath + rejectPath, reject(tupas));
}

function buildParamsForRequest (bank, languageCode, returnUrls, requestId) {
  if (invalidLangCode(languageCode))
    throw new Error("Unsupported language code: " + languageCode + ".");
  if (requestId.length > 20)
    throw new Error("Request id too long: " + requestId + ".");

  var params = {
    name : bank.name,
    id : bank.id,
    bankAuthUrl: bank.authUrl,
    messageType: TUPAS_MESSAGE_TYPE,
    version: bank.version,
    vendorId: bank.vendorId,
    identifier: requestId,
    languageCode: languageCode,
    idType: bank.idType,
    returnLink: returnUrls.ok,
    cancelLink: returnUrls.cancel,
    rejectLink: returnUrls.reject,
    keyVersion: bank.keyVersion,
    algorithmType: SHA256,
    checksumKey: bank.checksumKey,
    imgPath : bank.imgPath
  };

  params.mac = generateMacForRequest (params);

  return params;
}

function invalidLangCode(langCode) {
  return !_.contains(LANG_CODES, langCode);
}

function findConfig (bankId, bankConfig) {
  return _.find(bankConfig, function (bank) {
    return bank.id == bankId;
  });
}

function generateMacForRequest (requestParams) {
  var macParams = [
    requestParams.messageType,
    requestParams.version,
    requestParams.vendorId,
    requestParams.languageCode,
    requestParams.identifier,
    requestParams.idType,
    requestParams.returnLink,
    requestParams.cancelLink,
    requestParams.rejectLink,
    requestParams.keyVersion,
    requestParams.algorithmType,
    requestParams.checksumKey
  ];

  return generateMac(macParams);
}

function generateMacForResponse (queryParams, bankConfig) {
  var bankNumber = queryParams.B02K_TIMESTMP.substr(0, 3);
  var bank = _.find(bankConfig, function (bank) {
    return bank.number == bankNumber;
  });

  var macParams = _.map(_.compact([
    queryParams.B02K_VERS,
    queryParams.B02K_TIMESTMP,
    queryParams.B02K_IDNBR,
    queryParams.B02K_STAMP,
    queryParams.B02K_CUSTNAME,
    queryParams.B02K_KEYVERS,
    queryParams.B02K_ALG,
    queryParams.B02K_CUSTID,
    queryParams.B02K_CUSTTYPE,
    queryParams.B02K_USERID,
    queryParams.B02K_USERNAME,
    bank.checksumKey
  ]), function (val) {
    return unescape(val).replace(/\+/g, ' ');
  });

  return generateMac(macParams);
}

function generateMac(params) {
  var joinedParams = params.join("&") + "&";
  return crypto.createHash('sha256').update(joinedParams).digest('hex').toUpperCase();
}

function ok (tupas) {
  return function (req, res) {
    if (req.query.B02K_MAC && req.query.B02K_MAC.toUpperCase() === tupas.responseMac(req.query)) {
      tupas.emit('success', req, res);
    } else {
      tupas.emit('mac-check-failed', req, res);
    }
  }
}

function cancel (tupas) {
  return function (req, res) {
    tupas.emit('cancel', req, res);
  }
}

function reject (tupas) {
  return function (req, res) {
    tupas.emit('reject', req, res);
  }
}
<commit_msg>Clickable div to button, don't require JavaScript for submit<commit_after>"use strict";
/*jslint node:true, indent: 2, nomen: true */
var crypto = require("crypto"),
  url = require("url"),
  events = require('events'),
  _ = require('underscore')._,
  express = require('express'),
  config = require('./config.json');

var tupasPath = "/tupas",
  cancelPath = tupasPath + "/cancel",
  okPath = tupasPath + "/ok",
  rejectPath = tupasPath + "/reject";

var SHA256 = "03",
  TUPAS_MESSAGE_TYPE = "701",
  LANG_CODES = ['FI', 'SV', 'EN'];

var tupasFormTemplate = _.template(
                         '<form id="<%= id %>-form" method="POST" action="<%= bankAuthUrl %>" class="tupas-button">'+
                         '<button type="submit" id="<%= id %>-login">' +
                         '
<img src="<%= imgPath %>" alt="<%= name %>">
' +
                         '
<%= name %>
' +
                         '</button>' +
                         '<input name="A01Y_ACTION_ID" type="hidden" value="<%= messageType %>">' +
                         '<input name="A01Y_VERS" type="hidden" value="<%= version %>">' +
                         '<input name="A01Y_RCVID" type="hidden" value="<%= vendorId %>">' +
                         '<input name="A01Y_LANGCODE" type="hidden" value="<%= languageCode %>">' +
                         '<input name="A01Y_STAMP" type="hidden" value="<%= identifier %>">' +
                         '<input name="A01Y_IDTYPE" type="hidden" value="<%= idType %>">' +
                         '<input name="A01Y_RETLINK" type="hidden" value="<%= returnLink %>">' +
                         '<input name="A01Y_CANLINK" type="hidden" value="<%= cancelLink %>">' +
                         '<input name="A01Y_REJLINK" type="hidden" value="<%= rejectLink %>">' +
                         '<input name="A01Y_KEYVERS" type="hidden" value="<%= keyVersion %>">' +
                         '<input name="A01Y_ALG" type="hidden" value="<%= algorithmType %>">' +
                         '<input name="A01Y_MAC" type="hidden" value="<%= mac %>">' +
                         '</form>');

function requireArgument(argValue, argName) {
  if (typeof argValue === "undefined" || argValue === null) {
    throw new Error("Missing required argument " + argName + ".");
  }
}

exports.create = function (globalOpts, bankOpts) {
  requireArgument(globalOpts.appHandler, "globalOpts.appHandler");
  requireArgument(globalOpts.hostUrl, "globalOpts.hostUrl");
  var contextPath = url.parse(globalOpts.hostUrl).pathname;
  if (contextPath === '/') contextPath = '';

  var tupas = Object.create(events.EventEmitter.prototype),
    banks = updatedBankConfigsWith(bankOpts),
    vendorOpts = _.extend(
      {},
      globalOpts,
      { returnUrls : returnUrls(globalOpts.hostUrl) }
    );

  bindReturnUrlsToHandler(tupas, vendorOpts.appHandler, contextPath);
  vendorOpts.appHandler.use(express.static(__dirname + '/public'));

  tupas.banks = _.pluck(banks, 'id');
  tupas.requestMac = generateMacForRequest;
  tupas.responseMac = function (params) {
    return generateMacForResponse(params, banks)
  };

  tupas.buildRequestParams = function (bankId, languageCode, requestId) {
    return buildParamsForRequest(findConfig(bankId, banks),
      languageCode, vendorOpts.returnUrls, requestId);
  };

  tupas.tupasButton = function (bankId, languageCode, requestId) {
    var formParams = tupas.buildRequestParams(bankId, languageCode, requestId);
    return tupasFormTemplate(formParams);
  };

  return tupas;
};

function returnUrls (hostUrl) {
  return {
    ok: hostUrl + okPath,
    cancel: hostUrl + cancelPath,
    reject: hostUrl + rejectPath
  };
}

function updatedBankConfigsWith (bankOpts) {
  var updatedDefaults = mergeWithDefaults(bankOpts);
  var defaultBankIds = _.pluck(updatedDefaults, 'id');
  var newBanks = _.reject(bankOpts, function (bank) {
    return _.contains(defaultBankIds, bank.id)
  });

  return updatedDefaults.concat(newBanks);
}

function mergeWithDefaults (bankOpts) {
  return _.map(config.banks, function (bank) {
    var vendorOpts = _.find(bankOpts, function (bankConf) {
      return bankConf.id == bank.id;
    });

    if (vendorOpts) {
      return _.extend({}, bank, vendorOpts)
    } else {
      return bank
    }
  });
}

function bindReturnUrlsToHandler (tupas, handler, contextPath) {
  handler.post(contextPath + okPath, ok(tupas)); // Danske Bank uses POST.
  handler.post(contextPath + cancelPath, cancel(tupas));
  handler.post(contextPath + rejectPath, reject(tupas));
  handler.get(contextPath + okPath, ok(tupas));  // Others use GET.
  handler.get(contextPath + cancelPath, cancel(tupas));
  handler.get(contextPath + rejectPath, reject(tupas));
}

function buildParamsForRequest (bank, languageCode, returnUrls, requestId) {
  if (invalidLangCode(languageCode))
    throw new Error("Unsupported language code: " + languageCode + ".");
  if (requestId.length > 20)
    throw new Error("Request id too long: " + requestId + ".");

  var params = {
    name : bank.name,
    id : bank.id,
    bankAuthUrl: bank.authUrl,
    messageType: TUPAS_MESSAGE_TYPE,
    version: bank.version,
    vendorId: bank.vendorId,
    identifier: requestId,
    languageCode: languageCode,
    idType: bank.idType,
    returnLink: returnUrls.ok,
    cancelLink: returnUrls.cancel,
    rejectLink: returnUrls.reject,
    keyVersion: bank.keyVersion,
    algorithmType: SHA256,
    checksumKey: bank.checksumKey,
    imgPath : bank.imgPath
  };

  params.mac = generateMacForRequest (params);

  return params;
}

function invalidLangCode(langCode) {
  return !_.contains(LANG_CODES, langCode);
}

function findConfig (bankId, bankConfig) {
  return _.find(bankConfig, function (bank) {
    return bank.id == bankId;
  });
}

function generateMacForRequest (requestParams) {
  var macParams = [
    requestParams.messageType,
    requestParams.version,
    requestParams.vendorId,
    requestParams.languageCode,
    requestParams.identifier,
    requestParams.idType,
    requestParams.returnLink,
    requestParams.cancelLink,
    requestParams.rejectLink,
    requestParams.keyVersion,
    requestParams.algorithmType,
    requestParams.checksumKey
  ];

  return generateMac(macParams);
}

function generateMacForResponse (queryParams, bankConfig) {
  var bankNumber = queryParams.B02K_TIMESTMP.substr(0, 3);
  var bank = _.find(bankConfig, function (bank) {
    return bank.number == bankNumber;
  });

  var macParams = _.map(_.compact([
    queryParams.B02K_VERS,
    queryParams.B02K_TIMESTMP,
    queryParams.B02K_IDNBR,
    queryParams.B02K_STAMP,
    queryParams.B02K_CUSTNAME,
    queryParams.B02K_KEYVERS,
    queryParams.B02K_ALG,
    queryParams.B02K_CUSTID,
    queryParams.B02K_CUSTTYPE,
    queryParams.B02K_USERID,
    queryParams.B02K_USERNAME,
    bank.checksumKey
  ]), function (val) {
    return unescape(val).replace(/\+/g, ' ');
  });

  return generateMac(macParams);
}

function generateMac(params) {
  var joinedParams = params.join("&") + "&";
  return crypto.createHash('sha256').update(joinedParams).digest('hex').toUpperCase();
}

function ok (tupas) {
  return function (req, res) {
    if (req.query.B02K_MAC && req.query.B02K_MAC.toUpperCase() === tupas.responseMac(req.query)) {
      tupas.emit('success', req, res);
    } else {
      tupas.emit('mac-check-failed', req, res);
    }
  }
}

function cancel (tupas) {
  return function (req, res) {
    tupas.emit('cancel', req, res);
  }
}

function reject (tupas) {
  return function (req, res) {
    tupas.emit('reject', req, res);
  }
}
<|endoftext|>


<commit_before>/* Copyright 2017 Open Ag Data Alliance
 *
 * Licensed under the Apache License, Version 2.0 (the 'License');
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an 'AS IS' BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

'use strict';

const debug = require('debug');
const info = debug('sync-handler:info');
const trace = debug('sync-handler:trace');
const warn = debug('sync-handler:warn');
const error = debug('sync-handler:error');

const Promise = require('bluebird');
const URL = require('url');
const {Responder} = require('../../libs/oada-lib-kafka');
const {resources, remoteResources} = require('../../libs/oada-lib-arangodb');
const config = require('./config');
const axios = require('axios');

// TODO: Where to store the syncs?
// I feel like putting webhooks in _syncs was a poor choice
const META_KEY = '_remote_syncs';

//---------------------------------------------------------
// Kafka intializations:
const responder = new Responder(
        config.get('kafka:topics:httpResponse'),
        null,
        'sync-handlers');

module.exports = function stopResp() {
    return responder.disconnect();
};

responder.on('request', async function handleReq(req) {
    if (req.msgtype !== 'write-response') {
        return;
    }
    if (req.code !== 'success') {
        return;
    }

    let id = req['resource_id'];
    let rev = req['_rev'];
    // TODO: Add AQL query for just syncs and newest change?
    let syncs = await resources.getResource(id, `/_meta/${META_KEY}`)
        .then(syncs => syncs || {})
        .then(Object.entries);

    if (syncs.length === 0) {
        // Nothing for us to do
        return;
    }

    // TODO: Figure out just what changed
    let ids = resources.getNewDescendants(id, '0-0');
    let changes = await ids.map(id => ({[id]: resources.getResource(id)}))
        .reduce((a, b) => Object.assign(a, b));

    let puts = Promise.map(syncs, async ([key, {url, domain, token}]) => {
        info(`Running sync ${key} for resource ${id}`);

        if (process.env.NODE_ENV !== 'production') {
          /*
            If running in dev environment localhost should
            be directed to the proxy server
          */
          domain = domain.replace('localhost', 'proxy');
        }

        // TODO: Cache this?
        let apiroot = Promise.resolve(axios({
            method: 'get',
            url: `https://${domain}/.well-known/oada-configuration`
        })).get('data').get('oada_base_uri').then(s => s.replace(/\/?$/, '/'));

        // Ensure each local resource has a corresponding remote one
        let rids = await remoteResources.getRemoteId(await ids, domain)
            .map(async ({id, rid}) => {
                if (rid) {
                    return {id, rid};
                }

                let url = `${await apiroot}resources/`;
                let type = (await changes[id])['_meta']['_type'];
                // Create any missing remote IDs
                let loc = await Promise.resolve(axios({
                    method: 'post',
                    data: {},
                    headers: {
                        'content-type': type,
                        authorization: token
                    },
                    url
                })).get('headers').get('location');

                // Parse resource ID from Location header
                let newid = URL.resolve(url, loc).replace(url, 'resources/');

                let newrid = {
                    rid: newid,
                    id: id
                };

                // Record new remote ID
                // TODO: Insert all new remoteResources at once?
                await remoteResources.addRemoteId(newrid, domain);

                return newrid;
            });

        // Create mapping of IDs here to IDs there
        let idmapping = rids.map(({id, rid}) => ({[id]: rid}))
            .reduce((a, b) => Object.assign(a, b));
        let docs = Promise.map(rids, async ({id, rid}) => {
            // TODO: Only send what is new
            let change = await changes[id];
            // Fix links etc.
            let body = JSON.stringify(change, (k, v) => {
                switch (k) {
                case '_meta':
                    // Don't send _meta
                    return undefined;
                case '_id':
                    // TODO: Better link detection?
                    if (idmapping[v]) {
                        return idmapping[v];
                    }
                    warn(`Could not resolve link to ${v} at ${domain}`);
                    // TODO: What to do in this case?
                    return '';
                default:
                    return v;
                }
            });
            let type = change['_meta']['_type'];
            // TODO: Support DELETE
            return axios({
                method: 'put',
                url: `${await apiroot}${rid}`,
                data: body,
                headers: {
                    'content-type': type,
                    authorization: token
                }
            });
        });

        // TODO: Only create URL link first time?
        let link = axios({
            method: 'put',
            url: `${await apiroot}${url}`,
            data: {
                // TODO: Should this be a versioned link?
                '_id': idmapping[id]
            },
            headers: {
                // TODO: How to set content type of this PUT?
                //'content-type': 'application/json',
                authorization: token
            }
        });

        return Promise.join(docs, link);
    });

    // TODO: Handle sync url
    await puts;
});
<commit_msg>Sync mostly works right<commit_after>/* Copyright 2017 Open Ag Data Alliance
 *
 * Licensed under the Apache License, Version 2.0 (the 'License');
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an 'AS IS' BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

'use strict';

const debug = require('debug');
const info = debug('sync-handler:info');
const trace = debug('sync-handler:trace');
const warn = debug('sync-handler:warn');
const error = debug('sync-handler:error');

const Promise = require('bluebird');
const URL = require('url');
const {Responder} = require('../../libs/oada-lib-kafka');
const {resources, remoteResources} = require('../../libs/oada-lib-arangodb');
const config = require('./config');
const axios = require('axios');

// TODO: Where to store the syncs?
// I feel like putting webhooks in _syncs was a poor choice
const META_KEY = '_remote_syncs';

//---------------------------------------------------------
// Kafka intializations:
const responder = new Responder(
        config.get('kafka:topics:httpResponse'),
        null,
        'sync-handlers');

module.exports = function stopResp() {
    return responder.disconnect();
};

responder.on('request', async function handleReq(req) {
    if (req.msgtype !== 'write-response') {
        return;
    }
    if (req.code !== 'success') {
        return;
    }

    let id = req['resource_id'];
    let rev = req['_rev'];
    // TODO: Add AQL query for just syncs and newest change?
    let syncs = await resources.getResource(id, `/_meta/${META_KEY}`)
        .then(syncs => syncs || {})
        .then(Object.entries)
        // Ignore sync entries that aren't objects
        .filter(sync => sync[1] && typeof sync[1] === 'object');

    if (syncs.length === 0) {
        // Nothing for us to do
        return;
    }

    // TODO: Figure out just what changed
    let ids = resources.getNewDescendants(id, '0-0');
    let changes = await ids.map(id => ({[id]: resources.getResource(id)}))
        .reduce((a, b) => Object.assign(a, b));

    let puts = Promise.map(syncs, async ([key, {url, domain, token}]) => {
        info(`Running sync ${key} for resource ${id}`);

        if (process.env.NODE_ENV !== 'production') {
          /*
            If running in dev environment localhost should
            be directed to the proxy server
          */
          domain = domain.replace('localhost', 'proxy');
        }

        // TODO: Cache this?
        let apiroot = Promise.resolve(axios({
            method: 'get',
            url: `https://${domain}/.well-known/oada-configuration`
        })).get('data').get('oada_base_uri').then(s => s.replace(/\/?$/, '/'));

        // Ensure each local resource has a corresponding remote one
        let rids = await remoteResources.getRemoteId(await ids, domain)
            .map(rid => rid.id === id ? {id: id, rid: url} : rid)
            .map(async ({id, rid}) => {
                if (rid) {
                    return {id, rid};
                }

                let url = `${await apiroot}resources/`;
                let type = (await changes[id])['_meta']['_type'];
                // Create any missing remote IDs
                let loc = await Promise.resolve(axios({
                    method: 'post',
                    data: {},
                    headers: {
                        'content-type': type,
                        authorization: token
                    },
                    url
                })).get('headers').get('location');

                // Parse resource ID from Location header
                let newid = URL.resolve(url, loc).replace(url, 'resources/');

                let newrid = {
                    rid: newid,
                    id: id
                };

                // Record new remote ID
                // TODO: Insert all new remoteResources at once?
                await remoteResources.addRemoteId(newrid, domain);

                return newrid;
            });

        // Create mapping of IDs here to IDs there
        let idmapping = rids.map(({id, rid}) => ({[id]: rid}))
            .reduce((a, b) => Object.assign(a, b));
        idmapping[id] = url;
        let docs = Promise.map(rids, async ({id, rid}) => {
            // TODO: Only send what is new
            let change = await changes[id];
            // Fix links etc.
            let body = JSON.stringify(change, (k, v) => {
                switch (k) {
                case '_meta':
                    // Don't send _meta
                    return undefined;
                case '_id':
                    // TODO: Better link detection?
                    if (idmapping[v]) {
                        return idmapping[v];
                    }
                    warn(`Could not resolve link to ${v} at ${domain}`);
                    // TODO: What to do in this case?
                    return '';
                default:
                    return v;
                }
            });
            let type = change['_meta']['_type'];
            // TODO: Support DELETE
            return axios({
                method: 'put',
                url: `${await apiroot}${rid}`,
                data: body,
                headers: {
                    'content-type': type,
                    authorization: token
                }
            });
        });

        return docs;
    });

    await puts;
});
<|endoftext|>


<commit_before>var request = require('request');
var Promise = require('bluebird');
var aws     = require('aws-sdk');
var Github  = require('github');
var express = require('express');
var Webtask = require('webtask-tools');
var api     = express();

// Load the AWS
api.use('/sync', function (req, res, next) {
  var settings = req.webtaskContext.data;

  req.aws = Promise.promisifyAll(new aws.S3({
    accessKeyId:     settings.S3_ACCESS_KEY,
    secretAccessKey: settings.S3_SECRET,
    region:          settings.S3_REGION,
    params:          {
      Bucket: settings.S3_BUCKET
    }
  }));

  next();
});

api.get('/sync', function (req, res) {
  req.aws.getObjectAsync({
    Key: 'extensions/recipes/recipes.json'
  }).then(function (data) {
    res.status(200);
    res.send(data.Body.toString());
  });
});

api.post('/sync', function (req, res) {
  var context  = req.webtaskContext;
  var settings = context.data;

  if (!(context.body.action === 'closed' && context.body.pull_request.merged)) {
    res.status(200);
    res.send(JSON.stringify({status: 'PR not merged'}));
    return;
  }

  var reposUrl = 'https://api.github.com/repos/'+settings.GH_USER+'/'+settings.GH_REPO+'/contents/'+settings.GH_PATH;

  request({
    method: 'GET',
    uri: reposUrl,
    headers: {
      'User-Agent': 'webtask'
    }
  }, function (error, response, contents) {
    if (error) {
      res.status(500);
      res.send({
        error: error,
        status: 'Error getting contents'
      });
      return;
    }

    if (!error && response.statusCode == 200) {
      var promises = [];

      JSON.parse(contents).forEach(function (content) {
        if (content.name !== 'index.js') {
          var promise = new Promise(function (resolve, reject) {
            request({
              method: 'GET',
              uri: content.download_url,
            }, function (error, response, body) {
              if (error) {
                reject();
              }

              if (!error && response.statusCode == 200) {
                resolve(body);
              }
            });
          });

          promises.push(promise);
        }
      });

      Promise.all(promises).then(function (results) {
        var tasks   = [];
        var recipes = [];

        results.forEach(function (file) {
          var parsed = JSON.parse(file);
          recipes.push(parsed);
        });

        tasks.push(req.aws.putObjectAsync({
          Key:  'extensions/recipes/recipes.json',
          Body: JSON.stringify(recipes)
        }));

        return Promise.all(tasks);
      }).then(function () {
        res.status(200);
        res.send(JSON.stringify({status: 'success'}));
      }).catch(function (e) {
        res.status(500);
        res.send(JSON.stringify({
          error: e,
          status: 'Error uploading to S3'
        }));
      });
    }
  });
});

// Load Github config
api.use('/share', function (req, res, next) {
  var context = req.webtaskContext;

  var config  = {
    user: context.data.GH_USER,
    repo: context.data.GH_REPO,
    commitMessage: function () {
      return 'Contributing with recipe \'' + this.recipe + '\'';
    },
    getFilePath: function () {
      return 'recipes/' + this.recipe.replace(/ /g, '-').toLocaleLowerCase() + '.json';
    },
    getReferenceName: function () {
      return 'recipes/' + this.userInfo.nickname.replace(/ /g, '-').toLocaleLowerCase() + '/' + this.recipe.replace(/ /g, '-').toLocaleLowerCase();
    }
  };

  config.recipe   = context.body.recipe;
  config.content  = context.body.content;
  config.userInfo = context.body.userInfo;

  if (typeof config.userInfo === 'undefined' || config.userInfo === null) {
    config.userInfo = { nickname: 'dashboard', email: 'auth0@support.com' };
  }

  config.userInfo.nickname = config.userInfo.nickname || 'dashboard';
  config.userInfo.email    = config.userInfo.email || 'auth0@support.com';

  var github = new Github({
    version:  '3.0.0',
    protocol: 'https',
    host:     'api.github.com',
    timeout:  5000,
  });

  github.authenticate({
    type:  'token',
    token: context.data.GH_TOKEN
  });

  req.config = config;
  req.client = github;

  next();
});

api.post('/share', function (req, response) {
  var github = req.client;
  var config = req.config;

  github.gitdata.getAllReferences({
      user: config.user,
      repo: config.repo,
    }, function(err, res) {
      if (err !== null) {
        response.status(500);
        response.send({
          step:  'GETTING REFERENCES',
          error: err
        });
        return;
      } else {
        var lastCommitSHA = res.filter(function (repo) { return repo.ref === 'refs/heads/master'}).pop().object.sha;
        var treeSHA;
        var commitSHA;

        // Create a File
        github.gitdata.createTree({
            base_tree: lastCommitSHA,
            user: config.user,
            repo: config.repo,
            tree: [{
              path:    config.getFilePath(),
              mode:    '100644',
              type:    'blob',
              content: JSON.stringify(config.content)
            }]
          }, function (err, res) {
            if (err !== null) {
              response.status(500);
              response.send({
                step:  'FILE CREATION',
                error: err
              });
              return;
            } else {
              treeSHA = res.sha;
              // Create a Commit
              github.gitdata.createCommit({
                  user:    config.user,
                  repo:    config.repo,
                  message: config.commitMessage(),
                  tree:    treeSHA,
                  parents: [
                    lastCommitSHA // -> Master lastcommit
                  ],
                  author: {
                    name:  config.userInfo.nickname,
                    email: config.userInfo.email,
                    date:  (new Date()).toISOString()
                  }
                }, function(err, res) {
                  if (err !== null) {
                    response.status(500);
                    response.send({
                      step:  'COMMIT CREATION',
                      error: err
                    });
                    return;
                  } else {
                    commitSHA = res.sha;
                    // Create a Reference
                    github.gitdata.createReference({
                        user: config.user,
                        repo: config.repo,
                        ref:  'refs/heads/' + config.getReferenceName(),
                        sha:  commitSHA
                      }, function(err, res) {
                        if (err !== null) {
                          response.status(500);
                          response.send({
                            step:  'REFERENCE CREATION',
                            error: err
                          });
                          return;
                        } else {
                          // Create a PR
                          github.pullRequests.create({
                              user:  config.user,
                              repo:  config.repo,
                              title: config.commitMessage(),
                              body:  '',
                              base:  'master',
                              head:  config.getReferenceName()
                            }, function(err, res) {
                              if (err !== null) {
                                response.status(500);
                                response.send({
                                  step:  'PR CREATION',
                                  error: err
                                });
                                return;
                              }

                              response.status(201);
                              response.send({
                                link: res.html_url
                              });
                            });
                        }
                      });
                  }
                });
            }
          });
      }
    });
});

module.exports = Webtask.fromExpress(api);
<commit_msg>Update default user on share process<commit_after>var request = require('request');
var Promise = require('bluebird');
var aws     = require('aws-sdk');
var Github  = require('github');
var express = require('express');
var Webtask = require('webtask-tools');
var api     = express();

// Load the AWS
api.use('/sync', function (req, res, next) {
  var settings = req.webtaskContext.data;

  req.aws = Promise.promisifyAll(new aws.S3({
    accessKeyId:     settings.S3_ACCESS_KEY,
    secretAccessKey: settings.S3_SECRET,
    region:          settings.S3_REGION,
    params:          {
      Bucket: settings.S3_BUCKET
    }
  }));

  next();
});

api.get('/sync', function (req, res) {
  req.aws.getObjectAsync({
    Key: 'extensions/recipes/recipes.json'
  }).then(function (data) {
    res.status(200);
    res.send(data.Body.toString());
  });
});

api.post('/sync', function (req, res) {
  var context  = req.webtaskContext;
  var settings = context.data;

  if (!(context.body.action === 'closed' && context.body.pull_request.merged)) {
    res.status(200);
    res.send(JSON.stringify({status: 'PR not merged'}));
    return;
  }

  var reposUrl = 'https://api.github.com/repos/'+settings.GH_USER+'/'+settings.GH_REPO+'/contents/'+settings.GH_PATH;

  request({
    method: 'GET',
    uri: reposUrl,
    headers: {
      'User-Agent': 'webtask'
    }
  }, function (error, response, contents) {
    if (error) {
      res.status(500);
      res.send({
        error: error,
        status: 'Error getting contents'
      });
      return;
    }

    if (!error && response.statusCode == 200) {
      var promises = [];

      JSON.parse(contents).forEach(function (content) {
        if (content.name !== 'index.js') {
          var promise = new Promise(function (resolve, reject) {
            request({
              method: 'GET',
              uri: content.download_url,
            }, function (error, response, body) {
              if (error) {
                reject();
              }

              if (!error && response.statusCode == 200) {
                resolve(body);
              }
            });
          });

          promises.push(promise);
        }
      });

      Promise.all(promises).then(function (results) {
        var tasks   = [];
        var recipes = [];

        results.forEach(function (file) {
          var parsed = JSON.parse(file);
          recipes.push(parsed);
        });

        tasks.push(req.aws.putObjectAsync({
          Key:  'extensions/recipes/recipes.json',
          Body: JSON.stringify(recipes)
        }));

        return Promise.all(tasks);
      }).then(function () {
        res.status(200);
        res.send(JSON.stringify({status: 'success'}));
      }).catch(function (e) {
        res.status(500);
        res.send(JSON.stringify({
          error: e,
          status: 'Error uploading to S3'
        }));
      });
    }
  });
});

// Load Github config
api.use('/share', function (req, res, next) {
  var context = req.webtaskContext;

  var config  = {
    user: context.data.GH_USER,
    repo: context.data.GH_REPO,
    commitMessage: function () {
      return 'Contributing with recipe \'' + this.recipe + '\'';
    },
    getFilePath: function () {
      return 'recipes/' + this.recipe.replace(/ /g, '-').toLocaleLowerCase() + '.json';
    },
    getReferenceName: function () {
      return 'recipes/' + this.userInfo.nickname.replace(/ /g, '-').toLocaleLowerCase() + '/' + this.recipe.replace(/ /g, '-').toLocaleLowerCase();
    }
  };

  config.recipe   = context.body.recipe;
  config.content  = context.body.content;
  config.userInfo = context.body.userInfo;

  if (typeof config.userInfo === 'undefined' || config.userInfo === null) {
    config.userInfo = { nickname: 'anonymous', email: 'auth0@support.com' };
  }

  config.userInfo.nickname = config.userInfo.nickname || 'anonymous';
  config.userInfo.email    = config.userInfo.email || 'auth0@support.com';

  var github = new Github({
    version:  '3.0.0',
    protocol: 'https',
    host:     'api.github.com',
    timeout:  5000,
  });

  github.authenticate({
    type:  'token',
    token: context.data.GH_TOKEN
  });

  req.config = config;
  req.client = github;

  next();
});

api.post('/share', function (req, response) {
  var github = req.client;
  var config = req.config;

  github.gitdata.getAllReferences({
      user: config.user,
      repo: config.repo,
    }, function(err, res) {
      if (err !== null) {
        response.status(500);
        response.send({
          step:  'GETTING REFERENCES',
          error: err
        });
        return;
      } else {
        var lastCommitSHA = res.filter(function (repo) { return repo.ref === 'refs/heads/master'}).pop().object.sha;
        var treeSHA;
        var commitSHA;

        // Create a File
        github.gitdata.createTree({
            base_tree: lastCommitSHA,
            user: config.user,
            repo: config.repo,
            tree: [{
              path:    config.getFilePath(),
              mode:    '100644',
              type:    'blob',
              content: JSON.stringify(config.content)
            }]
          }, function (err, res) {
            if (err !== null) {
              response.status(500);
              response.send({
                step:  'FILE CREATION',
                error: err
              });
              return;
            } else {
              treeSHA = res.sha;
              // Create a Commit
              github.gitdata.createCommit({
                  user:    config.user,
                  repo:    config.repo,
                  message: config.commitMessage(),
                  tree:    treeSHA,
                  parents: [
                    lastCommitSHA // -> Master lastcommit
                  ],
                  author: {
                    name:  config.userInfo.nickname,
                    email: config.userInfo.email,
                    date:  (new Date()).toISOString()
                  }
                }, function(err, res) {
                  if (err !== null) {
                    response.status(500);
                    response.send({
                      step:  'COMMIT CREATION',
                      error: err
                    });
                    return;
                  } else {
                    commitSHA = res.sha;
                    // Create a Reference
                    github.gitdata.createReference({
                        user: config.user,
                        repo: config.repo,
                        ref:  'refs/heads/' + config.getReferenceName(),
                        sha:  commitSHA
                      }, function(err, res) {
                        if (err !== null) {
                          response.status(500);
                          response.send({
                            step:  'REFERENCE CREATION',
                            error: err
                          });
                          return;
                        } else {
                          // Create a PR
                          github.pullRequests.create({
                              user:  config.user,
                              repo:  config.repo,
                              title: config.commitMessage(),
                              body:  '',
                              base:  'master',
                              head:  config.getReferenceName()
                            }, function(err, res) {
                              if (err !== null) {
                                response.status(500);
                                response.send({
                                  step:  'PR CREATION',
                                  error: err
                                });
                                return;
                              }

                              response.status(201);
                              response.send({
                                link: res.html_url
                              });
                            });
                        }
                      });
                  }
                });
            }
          });
      }
    });
});

module.exports = Webtask.fromExpress(api);
<|endoftext|>


<commit_before>console.log('Loading function');

var AWS = require("aws-sdk");
var dynamodb_client = new AWS.DynamoDB({apiVersion: '2012-08-10'});

var beepoch = new Date(2016,0,1);

exports.handler = function(event, context) {
    console.log('Received event:', JSON.stringify(event, null, 2));
    event.Records.forEach(function(record) {
        console.log(record.eventID);
        console.log(record.eventName);
        console.log('DynamoDB Record: %j', record.dynamodb);

        var datapoint = record.dynamodb.NewImage;

        updateHourlyAggregate(datapoint, context);

        console.log("Record Loop Complete");
    });
};

function updateHourlyAggregate(datapoint, context) {
  var measurement_time = new Date(datapoint.measured_at.N*1000);
  var beepoch_second = measurement_time.getTime()/1000 - beepoch.getTime()/1000;
  var beepoch_hour = Math.floor(beepoch_second/3600);
  var beepoch_day = Math.floor(beepoch_hour/24);
  var beepoch_week = Math.floor(beepoch_day/7);

  var beepoch_year = measurement_time.getFullYear() - 2016;
  var beepoch_month = (beepoch_year*12)+measurement_time.getMonth();

  var record_key = {
    "hive_id_span": { S: datapoint.hive_id.N + 'h' },
    "beepoch_hour": { N: beepoch_hour.toString() }
  };

  console.log("Initializing Aggregate: " + datapoint.hive_id.N + "/" + beepoch_hour);

  dynamodb_client.getItem(
    {
      TableName: 'beta_hivebot_aggregates',
      Key: record_key
    },
    function(err, data) {
      if (err) {
        console.log("Aggregator Init Error: %j", err.stack);
      } else {
        item = data.Item || {};
        dynamodb_client.updateItem(
          {
            TableName: 'beta_hivebot_aggregates',
            Key: record_key,
            UpdateExpression: 'SET last_updated_at = :last_datapoint_measured_at, \
                                   max_outside_temp = :max_outside_temp, \
                                   min_outside_temp = :min_outside_temp \
                               ADD datapoints_count :one, \
                                   total_of_all_outside_temps :outside_temp',
            ExpressionAttributeValues: {
              ":last_datapoint_measured_at": { N: datapoint.measured_at.N },
              ":max_outside_temp": { N: Math.max((item.max_outside_temp || { N: -999 }).N, datapoint.outside_temp.N).toString() },
              ":min_outside_temp": { N: Math.min((item.min_outside_temp || { N: 999 }).N, datapoint.outside_temp.N).toString() },
              ":one": { N: '1' },
              ":outside_temp": { N: datapoint.outside_temp.N }
            }
          },
          function(err, data) {
            if (err) {
              console.log("Aggregator Init Error: %j", err.stack);
            } else {
              console.log("Aggregator Init Succ: %j", data);
              context.succeed("context succeed");
            }
          }
        );
      }
    }
  );
}
<commit_msg>extract out beepoch calculations<commit_after>console.log('Loading function');

var AWS = require("aws-sdk");
var dynamodb_client = new AWS.DynamoDB({apiVersion: '2012-08-10'});

var beepoch = new Date(2016,0,1);
var beepoch_memo = {};

exports.handler = function(event, context) {
    console.log('Received event:', JSON.stringify(event, null, 2));
    event.Records.forEach(function(record) {
        beepoch_memo = {};
        console.log(record.eventID);
        console.log(record.eventName);
        console.log('DynamoDB Record: %j', record.dynamodb);

        var datapoint = record.dynamodb.NewImage;

        updateHourlyAggregate(datapoint, context);

        console.log("Record Loop Complete");
    });
};

function updateHourlyAggregate(datapoint, context) {
  var record_key = {
    "hive_id_span": { S: datapoint.hive_id.N + 'h' },
    "beepoch_hour": { N: getBeepoch('hour', datapoint).toString() }
  };

  dynamodb_client.getItem(
    {
      TableName: 'beta_hivebot_aggregates',
      Key: record_key
    },
    function(err, data) {
      if (err) {
        console.log("Aggregator Init Error: %j", err.stack);
      } else {
        item = data.Item || {};
        dynamodb_client.updateItem(
          {
            TableName: 'beta_hivebot_aggregates',
            Key: record_key,
            UpdateExpression: 'SET last_updated_at = :last_datapoint_measured_at, \
                                   max_outside_temp = :max_outside_temp, \
                                   min_outside_temp = :min_outside_temp \
                               ADD datapoints_count :one, \
                                   total_of_all_outside_temps :outside_temp',
            ExpressionAttributeValues: {
              ":last_datapoint_measured_at": { N: datapoint.measured_at.N },
              ":max_outside_temp": { N: Math.max((item.max_outside_temp || { N: -999 }).N, datapoint.outside_temp.N).toString() },
              ":min_outside_temp": { N: Math.min((item.min_outside_temp || { N: 999 }).N, datapoint.outside_temp.N).toString() },
              ":one": { N: '1' },
              ":outside_temp": { N: datapoint.outside_temp.N }
            }
          },
          function(err, data) {
            if (err) {
              console.log("Aggregator Init Error: %j", err.stack);
            } else {
              console.log("Aggregator Init Succ: %j", data);
              context.succeed("context succeed");
            }
          }
        );
      }
    }
  );
}

function getBeepoch(span, datapoint) {
  switch(span) {
    case 'second':
      return getBeepochSecond(datapoint);
    case 'hour':
      return getBeepochHour(datapoint);
    case 'day':
      return getBeepochDay(datapoint);
    case 'week':
      return getBeepochWeek(datapoint);
    case 'month':
      return getBeepochMonth(datapoint);
    case 'year':
      return getBeepochYear(datapoint);
  }
}

function getBeepochSecond(datapoint) {
  return beepoch_memo['second'] |= datapoint.measured_at.N - beepoch.getTime()/1000;
}

function getBeepochHour(datapoint) {
  return beepoch_memo['hour'] |= Math.floor(getBeepochSecond(datapoint)/3600);
}

function getBeepochDay(datapoint) {
  return beepoch_memo['day'] |= Math.floor(getBeepochHour(datapoint)/24);
}

function getBeepochWeek(datapoint) {
  return beepoch_memo['week'] |= Math.floor(getBeepochDay(datapoint)/7);
}

function getBeepochMonth(datapoint) {
  return beepoch_memo['month'] |=
    (getBeepochYear(datapoint)*12)+getBeepochMeasurementTime(datapoint).getMonth();
}

function getBeepochYear(datapoint) {
  return beepoch_memo['year'] |=
    getBeepochMeasurementTime(datapoint).getFullYear() - 2016;
}

function getBeepochMeasurementTime(datapoint) {
  return beepoch_memo['measurement_time'] |= new Date(datapoint.measured_at.N*1000);
}
<|endoftext|>


<commit_before>(function () {
    var schema = {
        properties: {
            target: {
                type: 'string',
                required: true
            },
            width: {
                type: 'integer',
                required: true
            },
            height: {
                type: 'integer',
                required: true
            },
            strokeStyle: {
                type: "string",
                format: "color",
                required: true
            },
            mincolor: {
                type: 'array',
                items: {
                    type: 'integer'
                }
            },
            maxcolor: {
                type: 'array',
                items: {
                    type: 'integer'
                }
            },
            glyph: {
                enum: ['circle']
            },
            radius: {
                type: 'number'
            },
			axes: {
				type: 'boolean'
			},
			boundingBox: {
				type: 'boolean'
			},
			scale: {
				type: 'array',
				items: {
					enum: ['linear','log']
				}
			},
			xPadding: {
				type: 'integer'
			},
			yPadding: {
				type: 'integer'
			},
			x: { type: 'integer' },
			y: { type: 'integer' },
			colorBy: { type: 'integer' },
			scaleBy: { type: 'integer' },
            data: {
                required: true,
                type: 'array',
                items: {
                    type: 'array',
                    minItems: 2,
                    maxItems: 4,
                    items: {
                        type: 'number'
                    }
                }
            }
        }
    };
    Iris.Renderer.extend({
        about: function() {
            return {
                name: "scatterplot",
                author: "Andrew Olson",
                version: "1.0",
                requires: [],
                options: {
                    'key': 'value',
                    'target': 'test',
                    'data': 'example_data()'
                },
                classes: [],
                data_format: "list of tuples"
            }
        },
        example_data: function() {
			var sampleData = new Array();
			for(var i=0; i< 1000; i++) {
				sampleData[i] = new Array();
				sampleData[i][0] = Math.random()*100 - 90;
				sampleData[i][1] = Math.random()*100 - 50;
				sampleData[i][2] = Math.random()*10;
				sampleData[i][3] = Math.floor(Math.random()*21);
			}
			return sampleData;
//            return [[-5, -3, 0], [1, 1, 2], [2, 2, 2], [3, 30, 2], [10, 4, 3]];
        },
        render: function(settings) {
            this.options = {
                target: "test",
                width: 800,
                height: 400,
                strokeStyle: "black",
                minColor: [150, 150, 150],
                maxColor: [0, 0, 0],
                glyph: 'circle',
                minRadius: 1,
				maxRadius: 20,
				xPadding: 10,
				yPadding: 10,
				axes: true,
				boundingBox: true,
				x: 0,
				y: 1,
				colorBy: 1,
				scaleBy: 0,
				scale: ['linear','linear','linear','linear'],
                data: []

            };
            jQuery.extend(this.options, settings);
			var opt = this.options;
            var check = window.json.validate(opt, schema);
            if (!check['valid']) {
                $.error(check['errors']);
            }
            var data = this.options.data;

            var target = document.getElementById(this.options.target);
            var canvas;
            if (target.tagName === "CANVAS") {
                canvas = target;
            } else {
                canvas = document.createElement('canvas');
                target.appendChild(canvas);
            }
            canvas.width = this.options.width;
            canvas.height = this.options.height;
            var ctx = canvas.getContext('2d');
            ctx.strokeStyle = this.options.strokeStyle;
			if (opt.boundingBox) {
				ctx.strokeRect(0, 0, canvas.width, canvas.height);
			}
            var colorRange = [
            	opt.maxColor[0] - opt.minColor[0],
				opt.maxColor[1] - opt.minColor[1],
				opt.maxColor[2] - opt.minColor[2]];
			var axes = new Array();
            for (var j = 0; j < data[0].length; j++) {
				axes[j] = new Object();
				axes[j].min = data[0][j];
				axes[j].max = data[0][j];
            }
            for (var i = 1; i < data.length; i++) {
                for (var j = 0; j < data[i].length; j++) {
                    if (data[i][j] < axes[j].min) {
                        axes[j].min = data[i][j];
                    }
                    if (data[i][j] > axes[j].max) {
                        axes[j].max = data[i][j];
                    }
                }
            }
			if (this.options.axes) {
				// adjust min and max if necessary
				if(0 < axes[opt.x].min) { axes[opt.x].min = 0 }
				if(0 > axes[opt.x].max) { axes[opt.x].max = 0 }
				if(0 < axes[opt.y].min) { axes[opt.y].min = 0 }
				if(0 > axes[opt.y].max) { axes[opt.y].max = 0 }
			}
			for (var i = 0; i<axes.length; i++) {
				axes[i].range = axes[i].max - axes[i].min;
			}
			axes[opt.x].scale = function(value) {
				return opt.xPadding + (value - axes[opt.x].min) * (opt.width - 2*opt.xPadding) / axes[opt.x].range;
			}
			axes[opt.y].scale = function(value) {
				return opt.yPadding + (axes[opt.y].max - value) * (opt.height - 2*opt.yPadding) / axes[opt.y].range;
			}
			var doColor = false;
			if (opt.colorBy < axes.length && opt.colorBy >= 0) {
				doColor = true;
				axes[opt.colorBy].toRGB = function(value) {
					var ratio = (value - axes[opt.colorBy].min)/axes[opt.colorBy].range;
	                var r = opt.minColor[0] + Math.floor(ratio * colorRange[0]);
	                var g = opt.minColor[1] + Math.floor(ratio * colorRange[1]);
	                var b = opt.minColor[2] + Math.floor(ratio * colorRange[2]);
	                return "rgb(" + r + "," + g + "," + b + ")";
				}
			}
			var doScale = false;
			if (opt.scaleBy < axes.length && opt.scaleBy >= 0) {
				doScale = true;
				axes[opt.scaleBy].radius = function(value) {
					return opt.minRadius + (opt.maxRadius - opt.minRadius) * (value - axes[opt.scaleBy].min) / axes[opt.scaleBy].range;
				}
			}
            for (var i = 0; i < data.length; i++) {
  			    var x = axes[opt.x].scale(data[i][0]);
  			    var y = axes[opt.y].scale(data[i][1]);
				if (doColor) {
					ctx.fillStyle = axes[opt.colorBy].toRGB(data[i][opt.colorBy]);
				}
				if (this.options.glyph === 'circle') {
					var radius = doScale ? axes[opt.scaleBy].radius(data[i][opt.scaleBy]) : opt.radius;
                    ctx.beginPath();
                    ctx.arc(Math.floor(x), Math.floor(y), radius, 0, 2 * Math.PI, true);
                    ctx.closePath();
                    ctx.fill();
                } else {
                    ctx.fillRect(Math.floor(x), Math.floor(y), 2, 2);
                }
            }
			if (this.options.axes) {
				ctx.strokeRect(
					Math.floor(axes[0].scale(axes[0].min)),
					Math.floor(axes[1].scale(0)),
					opt.width - 2*opt.xPadding,1);
				ctx.strokeRect(
					Math.floor(axes[0].scale(0)),
					Math.floor(axes[1].scale(axes[1].max)),
					1,opt.height - 2*opt.yPadding);
			}
        }
    });
}).call(this);
<commit_msg>logscale transformation<commit_after>(function () {
    var schema = {
        properties: {
            target: {
                type: 'string',
                required: true
            },
            width: {
                type: 'integer',
                required: true
            },
            height: {
                type: 'integer',
                required: true
            },
            strokeStyle: {
                type: "string",
                format: "color",
                required: true
            },
            mincolor: {
                type: 'array',
                items: {
                    type: 'integer'
                }
            },
            maxcolor: {
                type: 'array',
                items: {
                    type: 'integer'
                }
            },
            glyph: {
                enum: ['circle']
            },
            radius: {
                type: 'number'
            },
			axes: {
				type: 'boolean'
			},
			boundingBox: {
				type: 'boolean'
			},
			scale: {
				type: 'array',
				items: {
					enum: ['linear','log']
				}
			},
			xPadding: {
				type: 'integer'
			},
			yPadding: {
				type: 'integer'
			},
			x: { type: 'integer' },
			y: { type: 'integer' },
			colorBy: { type: 'integer' },
			scaleBy: { type: 'integer' },
            data: {
                required: true,
                type: 'array',
                items: {
                    type: 'array',
                    minItems: 2,
                    maxItems: 4,
                    items: {
                        type: 'number'
                    }
                }
            }
        }
    };
    Iris.Renderer.extend({
        about: function() {
            return {
                name: "scatterplot",
                author: "Andrew Olson",
                version: "1.0",
                requires: [],
                options: {
                    'key': 'value',
                    'target': 'test',
                    'data': 'example_data()'
                },
                classes: [],
                data_format: "list of tuples"
            }
        },
        example_data: function() {
			var sampleData = new Array();
			for(var i=0; i< 1000; i++) {
				sampleData[i] = new Array();
				sampleData[i][0] = 2^i;
				sampleData[i][1] = Math.random()*100 - 50;
				sampleData[i][2] = Math.random()*10;
				sampleData[i][3] = Math.floor(Math.random()*21);
			}
			return sampleData;
//            return [[-5, -3, 0], [1, 1, 2], [2, 2, 2], [3, 30, 2], [10, 4, 3]];
        },
        render: function(settings) {
            this.options = {
                target: "test",
                width: 800,
                height: 400,
                strokeStyle: "black",
                minColor: [150, 150, 150],
                maxColor: [0, 0, 0],
                glyph: 'circle',
                minRadius: 1,
				maxRadius: 20,
				xPadding: 10,
				yPadding: 10,
				axes: true,
				boundingBox: true,
				x: 0,
				y: 1,
				colorBy: 2,
				scaleBy: 3,
				scale: ['linear','linear','linear','linear'],
                data: []

            };
            jQuery.extend(this.options, settings);
			var opt = this.options;
            var check = window.json.validate(opt, schema);
            if (!check['valid']) {
                $.error(check['errors']);
            }
            var data = this.options.data;

            var target = document.getElementById(this.options.target);
            var canvas;
            if (target.tagName === "CANVAS") {
                canvas = target;
            } else {
                canvas = document.createElement('canvas');
                target.appendChild(canvas);
            }
            canvas.width = this.options.width;
            canvas.height = this.options.height;
            var ctx = canvas.getContext('2d');
            ctx.strokeStyle = this.options.strokeStyle;
			if (opt.boundingBox) {
				ctx.strokeRect(0, 0, canvas.width, canvas.height);
			}
            var colorRange = [
            	opt.maxColor[0] - opt.minColor[0],
				opt.maxColor[1] - opt.minColor[1],
				opt.maxColor[2] - opt.minColor[2]];
			var axes = new Array();
            for (var j = 0; j < data[0].length; j++) {
				axes[j] = new Object();
				axes[j].min = data[0][j];
				axes[j].max = data[0][j];
            }
            for (var i = 1; i < data.length; i++) {
                for (var j = 0; j < data[i].length; j++) {
                    if (data[i][j] < axes[j].min) {
                        axes[j].min = data[i][j];
                    }
                    if (data[i][j] > axes[j].max) {
                        axes[j].max = data[i][j];
                    }
                }
            }
			if (this.options.axes) {
				// adjust min and max if necessary
				if(0 < axes[opt.x].min) { axes[opt.x].min = 0 }
				if(0 > axes[opt.x].max) { axes[opt.x].max = 0 }
				if(0 < axes[opt.y].min) { axes[opt.y].min = 0 }
				if(0 > axes[opt.y].max) { axes[opt.y].max = 0 }
			}
			for (var i = 0; i<axes.length; i++) {
				axes[i].range = axes[i].max - axes[i].min;
			}
			axes[opt.x].scale = function(value) {
				if (opt.scale[opt.x] === 'linear') {
					return opt.xPadding + (value - axes[opt.x].min) * (opt.width - 2*opt.xPadding) / axes[opt.x].range;
				} else {
					if (value === axes[opt.x].min) {
						return opt.xPadding;
					}
					return opt.xPadding + Math.log(value - axes[opt.x].min) * (opt.width - 2*opt.xPadding) / Math.log(axes[opt.x].range);
				}
			}
			axes[opt.y].scale = function(value) {
				if (opt.scale[opt.y] === 'linear') {
					return opt.yPadding + (axes[opt.y].max - value) * (opt.height - 2*opt.yPadding) / axes[opt.y].range;
				} else {
					if (value === axes[opt.y].min) {
						return opt.yPadding;
					}
					return opt.yPadding + opt.height - Math.log(value - axes[opt.y].min) * (opt.height - 2*opt.yPadding) / Math.log(axes[opt.y].range);
				}
			}
			var doColor = false;
			if (opt.colorBy < axes.length && opt.colorBy >= 0) {
				doColor = true;
				axes[opt.colorBy].toRGB = function(value) {
					var ratio = (value - axes[opt.colorBy].min)/axes[opt.colorBy].range;
	                var r = opt.minColor[0] + Math.floor(ratio * colorRange[0]);
	                var g = opt.minColor[1] + Math.floor(ratio * colorRange[1]);
	                var b = opt.minColor[2] + Math.floor(ratio * colorRange[2]);
	                return "rgb(" + r + "," + g + "," + b + ")";
				}
			}
			var doScale = false;
			if (opt.scaleBy < axes.length && opt.scaleBy >= 0) {
				doScale = true;
				axes[opt.scaleBy].radius = function(value) {
					return opt.minRadius + (opt.maxRadius - opt.minRadius) * (value - axes[opt.scaleBy].min) / axes[opt.scaleBy].range;
				}
			}
            for (var i = 0; i < data.length; i++) {
  			    var x = axes[opt.x].scale(data[i][0]);
  			    var y = axes[opt.y].scale(data[i][1]);
				if (doColor) {
					ctx.fillStyle = axes[opt.colorBy].toRGB(data[i][opt.colorBy]);
				}
				if (this.options.glyph === 'circle') {
					var radius = doScale ? axes[opt.scaleBy].radius(data[i][opt.scaleBy]) : opt.radius;
                    ctx.beginPath();
                    ctx.arc(Math.floor(x), Math.floor(y), radius, 0, 2 * Math.PI, true);
                    ctx.closePath();
                    ctx.fill();
                } else {
                    ctx.fillRect(Math.floor(x), Math.floor(y), 2, 2);
                }
            }
			if (this.options.axes) {
				ctx.strokeRect(
					Math.floor(axes[0].scale(axes[0].min)),
					Math.floor(axes[1].scale(0)),
					opt.width - 2*opt.xPadding,1);
				ctx.strokeRect(
					Math.floor(axes[0].scale(0)),
					Math.floor(axes[1].scale(axes[1].max)),
					1,opt.height - 2*opt.yPadding);
			}
        }
    });
}).call(this);
<|endoftext|>