<commit_before>/*

© 2010 by Brian Noguchi

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

*/

var sys = require("sys"),
    Reply = require("../reply").Reply,
    CR = require("../reply").CR,
    LF = require("../reply").LF,
    BulkReply = require("./bulkReply").BulkReply,
    IntegerReply = require("./integerReply").IntegerReply;

MultibulkReply = exports.MultibulkReply = function (client, context) {
    this.client = client;
    this.context = context;
    this.isComplete = false;
    this.replyValue = null;
    this.expectedProxy = {replyValue: "", i: 0, isComplete: false};
};

Object.defineProperty(MultibulkReply.prototype, 'latest', {
    get: function () {
        var ctx = this.context;
        return (ctx.scope === "exec") ?
               ctx.currCommandName :
               ctx.scope;
    }
});

MultibulkReply.prototype.parse = function (data, atDataIndex) {
    var dataLen = data.length,
        childReplies = this.replies, numChildren = this.numChildren,
        expected, expectedProxy;
    while (atDataIndex < dataLen) {
        if (typeof this.expected === "undefined") {
            expectedProxy = this.expectedProxy;
            atDataIndex = IntegerReply.prototype.parse.call(expectedProxy, data, atDataIndex);
            if (expectedProxy.isComplete) {
                expected = this.expected = expectedProxy.replyValue;
                if (expected === -1) {
                    this.replyValue = null;
                    this.isComplete = true;
                    break;
                }
                if (expected === 0) { // For '*0\r\n'
                    this.replyValue = this.isHashValuable[this.latest] ? null : [];
                    this.isComplete = true;
                    break;
                }
                childReplies = this.replies = new Array(expected);
                numChildren = this.numChildren = 0;
            }
            continue;
        }

        latestReply = childReplies[numChildren-1];
        if (latestReply && !latestReply.isComplete) {
            atDataIndex = latestReply.parse(data, atDataIndex);
        } else {
            var newReply = Reply.fromTypeCode( data[atDataIndex++], this.client, this.context );
            if (!newReply) {
                continue;
            }
            latestReply = childReplies[numChildren++] = newReply;
        }
        if (latestReply.isComplete) {
            this.numChildren = numChildren;
            if (numChildren === 1) {
                // Find the child2ValueFn to use to add any child replyValue to this.replyValue.
                // We do this by evaluating the first reply in this multibulk. This gives us enough
                // clues about what form this.replyValue should take - either a Message, PMessage,
                // Hash, or Array.

                // Determine if this is a PUBSUB message by peeking at the first child reply
                var childReply1Value = latestReply.replyValue,
                    childReply1ValueLen = childReply1Value && childReply1Value.length;
                if (expected === 3 && childReply1ValueLen === 7 && childReply1Value === "message") {
                    // If this is a PUBSUB message
                    this.isMessage = true;
                    this.child2ValueFn = this.child2MessageValue;
                    this.replyValue = {};
                } else if (expected === 4 && childReply1ValueLen === 8 && childReply1Value === "pmessage") {
                    // If this is a PUBSUB pmessage
                    this.isPMessage = true;
                    this.child2ValueFn = this.child2PMessageValue;
                    this.replyValue = {};
                } else {
                    this.child2ValueFn = this.getTransformerFromContext();
                }
            }
            this.child2ValueFn();
            if (this.expected === numChildren) {
                this.isComplete = true;
                break;
            }
        }
    }
    return atDataIndex;
};

MultibulkReply.prototype.child2MessageValue = function () {
    var childReplies = this.replies,
        numChildReplies = this.numChildren,
        childReply = childReplies[numChildReplies-1];
    if (numChildReplies === 1) {
        // Do nothing because the 1st reply is just "message" or "pmessage"
    } else if (numChildReplies === 2) {
        this.replyValue.channelName = childReply.replyValue;
    } else if (numChildReplies === 3) { // === expected
        this.replyValue.message = childReply.replyValue;
    } else {
        throw new Error("Out of bounds unexpected.");
    }
};

MultibulkReply.prototype.child2PMessageValue = function () {
    var childReplies = this.replies,
        numChildReplies = this.numChildren,
        childReply = childReplies[numChildReplies-1];
    if (numChildReplies === 2) {
        this.replyValue.channelName = childReply.replyValue;
    } else if (numChildReplies === 3) { // === expected
        this.replyValue.channelPattern = childReply.replyValue;
    } else if (numChildReplies === 4) {
        this.replyValue.message = childReply.replyValue;
    } else {
        throw "Out of bounds unexpected.";
    }
};

MultibulkReply.prototype.child2HashValue = function () {
    var childReplies = this.replies,
        numChildReplies = this.numChildren,
        latestChildReply = childReplies[numChildReplies-1];
    if (numChildReplies % 2 === 1) {
        this.nextKey = latestChildReply.replyValue;
    } else {
        this.replyValue[this.nextKey] = latestChildReply.replyValue;
    }
};

MultibulkReply.prototype.child2ArrayValue = function () {
    var childReplies = this.replies,
        numChildReplies = this.numChildren,
        latestChildReply = childReplies[numChildReplies-1];
    this.replyValue.push(latestChildReply.replyValue);
};

// Multibulks can be found with:
// -Transactions => return an array of replies
// -Sort => return an array where elements are hashes or values
// -Hgetall => return a hash
// -Mget => return an array
// -Others => return an array of values
// Most extreme case is a transaction of sorts
MultibulkReply.prototype.getTransformerFromContext = function () {
    var context = this.context,
        latest = this.latest,
        ret;

    if (latest === "sort") {
        if (!context.parsingSort) {
            ret = this.child2ArrayValue;
            this.replyValue = [];
        }
        else {
            ret = this.child2HashValue;
            this.replyValue = {};
        }
    } else if (this.isHashValuable[latest]) {
        console.log("!");
        ret = this.child2HashValue;
        this.replyValue = {};
    } else {
        // Among other scenarios, this else also takes care of (latest === "exec") or
        // when we're in special transaction exiting territory
        ret = this.child2ArrayValue;
        this.replyValue = [];
    }
    return ret;
};

MultibulkReply.prototype.isHashValuable = {
    hgetall: true
};
<commit_msg>Fixed psubscribe.<commit_after>/*

© 2010 by Brian Noguchi

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

*/

var sys = require("sys"),
    Reply = require("../reply").Reply,
    CR = require("../reply").CR,
    LF = require("../reply").LF,
    BulkReply = require("./bulkReply").BulkReply,
    IntegerReply = require("./integerReply").IntegerReply;

MultibulkReply = exports.MultibulkReply = function (client, context) {
    this.client = client;
    this.context = context;
    this.isComplete = false;
    this.replyValue = null;
    this.expectedProxy = {replyValue: "", i: 0, isComplete: false};
};

Object.defineProperty(MultibulkReply.prototype, 'latest', {
    get: function () {
        var ctx = this.context;
        return (ctx.scope === "exec") ?
               ctx.currCommandName :
               ctx.scope;
    }
});

MultibulkReply.prototype.parse = function (data, atDataIndex) {
    var dataLen = data.length,
        childReplies = this.replies, numChildren = this.numChildren,
        expected, expectedProxy;
    while (atDataIndex < dataLen) {
        if (typeof this.expected === "undefined") {
            expectedProxy = this.expectedProxy;
            atDataIndex = IntegerReply.prototype.parse.call(expectedProxy, data, atDataIndex);
            if (expectedProxy.isComplete) {
                expected = this.expected = expectedProxy.replyValue;
                if (expected === -1) {
                    this.replyValue = null;
                    this.isComplete = true;
                    break;
                }
                if (expected === 0) { // For '*0\r\n'
                    this.replyValue = this.isHashValuable[this.latest] ? null : [];
                    this.isComplete = true;
                    break;
                }
                childReplies = this.replies = new Array(expected);
                numChildren = this.numChildren = 0;
            }
            continue;
        }

        latestReply = childReplies[numChildren-1];
        if (latestReply && !latestReply.isComplete) {
            atDataIndex = latestReply.parse(data, atDataIndex);
        } else {
            var newReply = Reply.fromTypeCode( data[atDataIndex++], this.client, this.context );
            if (!newReply) {
                continue;
            }
            latestReply = childReplies[numChildren++] = newReply;
        }
        if (latestReply.isComplete) {
            this.numChildren = numChildren;
            if (numChildren === 1) {
                // Find the child2ValueFn to use to add any child replyValue to this.replyValue.
                // We do this by evaluating the first reply in this multibulk. This gives us enough
                // clues about what form this.replyValue should take - either a Message, PMessage,
                // Hash, or Array.

                // Determine if this is a PUBSUB message by peeking at the first child reply
                var childReply1Value = latestReply.replyValue,
                    childReply1ValueLen = childReply1Value && childReply1Value.length;
                if (expected === 3 && childReply1ValueLen === 7 && childReply1Value === "message") {
                    // If this is a PUBSUB message
                    this.isMessage = true;
                    this.child2ValueFn = this.child2MessageValue;
                    this.replyValue = {};
                } else if (expected === 4 && childReply1ValueLen === 8 && childReply1Value === "pmessage") {
                    // If this is a PUBSUB pmessage
                    this.isPMessage = true;
                    this.child2ValueFn = this.child2PMessageValue;
                    this.replyValue = {};
                } else {
                    this.child2ValueFn = this.getTransformerFromContext();
                }
            }
            this.child2ValueFn();
            if (this.expected === numChildren) {
                this.isComplete = true;
                break;
            }
        }
    }
    return atDataIndex;
};

MultibulkReply.prototype.child2MessageValue = function () {
    var childReplies = this.replies,
        numChildReplies = this.numChildren,
        childReply = childReplies[numChildReplies-1];
    if (numChildReplies === 1) {
        // Do nothing because the 1st reply is just "message" or "pmessage"
    } else if (numChildReplies === 2) {
        this.replyValue.channelName = childReply.replyValue;
    } else if (numChildReplies === 3) { // === expected
        this.replyValue.message = childReply.replyValue;
    } else {
        throw new Error("Out of bounds unexpected.");
    }
};

MultibulkReply.prototype.child2PMessageValue = function () {
    var childReplies = this.replies,
        numChildReplies = this.numChildren,
        childReply = childReplies[numChildReplies-1];
    if (numChildReplies === 1) {
        // Do nothing because the 1st reply is just "message" or "pmessage"
    } else if (numChildReplies === 2) {
        this.replyValue.channelPattern = childReply.replyValue;
    } else if (numChildReplies === 3) { // === expected
        this.replyValue.channelName = childReply.replyValue;
    } else if (numChildReplies === 4) {
        this.replyValue.message = childReply.replyValue;
    } else {
        throw new Error("Out of bounds unexpected.");
    }
};

MultibulkReply.prototype.child2HashValue = function () {
    var childReplies = this.replies,
        numChildReplies = this.numChildren,
        latestChildReply = childReplies[numChildReplies-1];
    if (numChildReplies % 2 === 1) {
        this.nextKey = latestChildReply.replyValue;
    } else {
        this.replyValue[this.nextKey] = latestChildReply.replyValue;
    }
};

MultibulkReply.prototype.child2ArrayValue = function () {
    var childReplies = this.replies,
        numChildReplies = this.numChildren,
        latestChildReply = childReplies[numChildReplies-1];
    this.replyValue.push(latestChildReply.replyValue);
};

// Multibulks can be found with:
// -Transactions => return an array of replies
// -Sort => return an array where elements are hashes or values
// -Hgetall => return a hash
// -Mget => return an array
// -Others => return an array of values
// Most extreme case is a transaction of sorts
MultibulkReply.prototype.getTransformerFromContext = function () {
    var context = this.context,
        latest = this.latest,
        ret;

    if (latest === "sort") {
        if (!context.parsingSort) {
            ret = this.child2ArrayValue;
            this.replyValue = [];
        }
        else {
            ret = this.child2HashValue;
            this.replyValue = {};
        }
    } else if (this.isHashValuable[latest]) {
        ret = this.child2HashValue;
        this.replyValue = {};
    } else {
        // Among other scenarios, this else also takes care of (latest === "exec") or
        // when we're in special transaction exiting territory
        ret = this.child2ArrayValue;
        this.replyValue = [];
    }
    return ret;
};

MultibulkReply.prototype.isHashValuable = {
    hgetall: true
};
<|endoftext|>


<commit_before>'use strict';

var indent = require('../strutils').indent;
var printf = require('printf');

function DotReporter(silent, out) {
  this.out = out || process.stdout;
  this.silent = silent;
  this.stoppedOnError = null;
  this.id = 1;
  this.total = 0;
  this.pass = 0;
  this.skipped = 0;
  this.results = [];
  this.startTime = new Date();
  this.endTime = null;
  this.currentLineChars = 0;
  this.out.write('\n');
  this.out.write('  ');
}
DotReporter.prototype = {
  report: function(prefix, data) {
    this.results.push({
      launcher: prefix,
      result: data
    });
    this.display(prefix, data);
    this.total++;
    if (data.skipped) {
      this.skipped++;
    } else if (data.passed) {
      this.pass++;
    }
  },

  display: function(prefix, result) {
    if (this.silent) {
      return;
    }
    if (this.currentLineChars > 60) {
      this.currentLineChars = 0;
      this.out.write('\n  ');
    }
    if (result.passed) {
      this.out.write('.');
    } else if (result.skipped) {
      this.out.write('*');
    } else {
      this.out.write('F');
    }
    this.currentLineChars += 1;
  },
  finish: function() {
    if (this.silent) {
      return;
    }
    this.endTime = new Date();
    this.out.write('\n\n');
    this.out.write(this.summaryDisplay());
    this.out.write('\n\n');
    this.displayErrors();
  },
  displayErrors: function() {
    this.results.forEach(function(data, idx) {
      var result = data.result;
      var error = result.error;
      if (!error) {
        return;
      }

      printf(this.out, '%*d) [%s] %s\n', idx + 1, 3, data.launcher, result.name);

      if (error.message) {
        printf(this.out, '     %s\n', error.message);
      }

      if ('expected' in error && 'actual' in error) {
        printf(this.out, '\n' +
               '     expected: %O\n' +
               '       actual: %O\n', error.expected, error.actual);
      }

      if (error.stack) {
        printf(this.out, '\n%s', indent(error.stack, 5));
      }

      this.out.write('\n');
    }, this);
  },
  summaryDisplay: function() {
    return printf('  %d tests complete (%d ms)', this.total, this.duration());
  },
  duration: function() {
    return Math.round((this.endTime - this.startTime));
  }
};

module.exports = DotReporter;
<commit_msg>separate each test failure report by an empty line<commit_after>'use strict';

var indent = require('../strutils').indent;
var printf = require('printf');

function DotReporter(silent, out) {
  this.out = out || process.stdout;
  this.silent = silent;
  this.stoppedOnError = null;
  this.id = 1;
  this.total = 0;
  this.pass = 0;
  this.skipped = 0;
  this.results = [];
  this.startTime = new Date();
  this.endTime = null;
  this.currentLineChars = 0;
  this.out.write('\n');
  this.out.write('  ');
}
DotReporter.prototype = {
  report: function(prefix, data) {
    this.results.push({
      launcher: prefix,
      result: data
    });
    this.display(prefix, data);
    this.total++;
    if (data.skipped) {
      this.skipped++;
    } else if (data.passed) {
      this.pass++;
    }
  },

  display: function(prefix, result) {
    if (this.silent) {
      return;
    }
    if (this.currentLineChars > 60) {
      this.currentLineChars = 0;
      this.out.write('\n  ');
    }
    if (result.passed) {
      this.out.write('.');
    } else if (result.skipped) {
      this.out.write('*');
    } else {
      this.out.write('F');
    }
    this.currentLineChars += 1;
  },
  finish: function() {
    if (this.silent) {
      return;
    }
    this.endTime = new Date();
    this.out.write('\n\n');
    this.out.write(this.summaryDisplay());
    this.out.write('\n\n');
    this.displayErrors();
  },
  displayErrors: function() {
    this.results.forEach(function(data, idx) {
      var result = data.result;
      var error = result.error;
      if (!error) {
        return;
      }

      printf(this.out, '%*d) [%s] %s\n', idx + 1, 3, data.launcher, result.name);

      if (error.message) {
        printf(this.out, '     %s\n', error.message);
      }

      if ('expected' in error && 'actual' in error) {
        printf(this.out, '\n' +
               '     expected: %O\n' +
               '       actual: %O\n', error.expected, error.actual);
      }

      if (error.stack) {
        printf(this.out, '\n%s', indent(error.stack, 5));
      }

      this.out.write('\n\n');
    }, this);
  },
  summaryDisplay: function() {
    return printf('  %d tests complete (%d ms)', this.total, this.duration());
  },
  duration: function() {
    return Math.round((this.endTime - this.startTime));
  }
};

module.exports = DotReporter;
<|endoftext|>


<commit_before>/*jslint browser, this, for*/
/*global window, $, Morris*/
var Main = (function () {
    'use strict';

    $.ajaxSetup({
        cache: false
    });

    var publicMethods = {};

    publicMethods.init = function (CONFIG) {
        var loadingIconClassName = 'loading-icon';
        var $WINDOW = $(window);
        var $CHART = $('#chart');
        var $TABLE = $('#table');
        var $FORM = $('form');

        var TEXT = {
            sys: 'SYS',
            dia: 'DIA',
            pulse: 'Pulse'
        };

        var RGB_COLORS = {
            sys: [234, 27, 19],
            dia: [230, 100, 19],
            pulse: [110, 110, 245]
        };

        var GOALS = [120, 80, 60];

        function getRgbaColor(rgbArray, opacity) {
            opacity = opacity || 1;
            return 'rgba(' + rgbArray[0] + ', ' + rgbArray[1] + ', ' + rgbArray[2] + ', ' + opacity + ')';
        }

        var GOAL_OPACITY = 0.6;

        var MORRIS_OPTIONS = {
            element: 'chart',
            data: [],
            xkey: CONFIG.keys.dateTime,
            ykeys: [CONFIG.keys.sys, CONFIG.keys.dia, CONFIG.keys.pulse],
            labels: [TEXT.sys, TEXT.dia, TEXT.pulse],
            lineColors: [getRgbaColor(RGB_COLORS.sys), getRgbaColor(RGB_COLORS.dia), getRgbaColor(RGB_COLORS.pulse)],
            lineWidth: 1,
            pointSize: 0,
            smooth: false,
            resize: true,
            continuousLine: true,
            goals: GOALS,
            goalStrokeWidth: 1,
            goalLineColors: [
                getRgbaColor(RGB_COLORS.sys, GOAL_OPACITY),
                getRgbaColor(RGB_COLORS.dia, GOAL_OPACITY),
                getRgbaColor(RGB_COLORS.pulse, GOAL_OPACITY)
            ]
        };

        function showLoadingIcon(jElement) {
            jElement.css('position', 'relative');
            jElement.append($('
', {class: loadingIconClassName}));
        }

        function hideLoadingIcon(jElement) {
            jElement.find('.' + loadingIconClassName).remove();
            jElement.css('position', 'initial');
        }

        function setTableData(logData) {
            $TABLE.empty();

            var table = $('
');

            var firstRow = $('
');
            firstRow.append($(''));
            firstRow.append($('', {html: TEXT.sys + '
[mmHg]'}));
            firstRow.append($('', {html: TEXT.dia + '
[mmHg]'}));
            firstRow.append($('', {html: TEXT.pulse + '
[1/min]'}));
            table.append(firstRow);

            var bloodPressureCategories = {
                sys: [120, 130, 140, 160, 180],
                dia: [80, 85, 90, 100, 110]
            };
            var bloodPressureClasses = [
                'bp-optimal',
                'bp-normal',
                'bp-normalhigh',
                'bp-hypertension1',
                'bp-hypertension2',
                'bp-hypertension3'
            ];
            var getBloodPressureClass = function (categoryValues, value) {
                var idx, bloodPressureClass;
                for (idx = categoryValues.length - 1; idx >= 0; idx -= 1) {
                    if (value >= categoryValues[idx]) {
                        bloodPressureClass = bloodPressureClasses[idx + 1];
                        break;
                    }
                }
                return bloodPressureClass || bloodPressureClasses[0];
            };

            logData.forEach(function (entry) {
                var sysClass = getBloodPressureClass(bloodPressureCategories.sys, entry[CONFIG.keys.sys]);
                var diaClass = getBloodPressureClass(bloodPressureCategories.dia, entry[CONFIG.keys.dia]);

                var newRow = $('
');
                newRow.append($('', {text: entry[CONFIG.keys.dateTime]}));
                newRow.append($('', {text: entry[CONFIG.keys.sys], class: sysClass}));
                newRow.append($('', {text: entry[CONFIG.keys.dia], class: diaClass}));
                newRow.append($('', {text: entry[CONFIG.keys.pulse]}));
                table.append(newRow);
            });

            $TABLE.append(table);
        }

        function getExtremeOfObjectsArray(objectsArray, extremeFn, keys) {
            if (keys === undefined && objectsArray.length > 0) {
                keys = objectsArray[0].keys();
            }

            var currentExtreme;
            objectsArray.forEach(function (object) {
                var objectValues = [];
                keys.forEach(function (objectKey) {
                    objectValues.push(object[objectKey]);
                });

                var value = extremeFn.apply(null, objectValues);
                var isNewExtreme = (extremeFn === Math.min)
                    ? (value < currentExtreme)
                    : (value > currentExtreme);
                if (currentExtreme === undefined || isNewExtreme) {
                    currentExtreme = value;
                }
            });
            return currentExtreme;
        }

        function round(number, exponent, roundingFunction, base) {
            var factor = Math.pow(base || 10, exponent || 0);
            return roundingFunction(number * factor) / factor;
        }

        function getExtremeValueIncludingGoals(extremeFn, logData, keys, goals) {
            var extremeValue = getExtremeOfObjectsArray(logData, extremeFn, keys);
            var minValueIncludingGoals = extremeFn(extremeValue, extremeFn.apply(null, goals));
            return minValueIncludingGoals;
        }

        function refreshData() {
            showLoadingIcon($CHART);
            var keys = [CONFIG.keys.sys, CONFIG.keys.dia, CONFIG.keys.pulse];

            $.getJSON(CONFIG.api.endPoints.getLog).done(function (logData) {
                MORRIS_OPTIONS.ymin = round(getExtremeValueIncludingGoals(Math.min, logData, keys, GOALS), -1, Math.floor);
                MORRIS_OPTIONS.ymax = round(getExtremeValueIncludingGoals(Math.max, logData, keys, GOALS), -1, Math.ceil);
                $CHART.empty();
                Morris.Line(MORRIS_OPTIONS).setData(logData);

                setTableData(JSON.parse(JSON.stringify(logData)).reverse());
            }).always(function () {
                hideLoadingIcon($CHART);
            });
        }

        function resizeUI() {
            $CHART.height($WINDOW.innerHeight() - $FORM.innerHeight());
        }

        $FORM.on('submit', function () {
            var now = new Date();
            var localDateTime = new Date(now.getTime() - now.getTimezoneOffset() * 60 * 1000);
            var isoDateTimeStringNoTimeZone = localDateTime.toISOString().substr(0, 19);
            var dateTimeStringArray = isoDateTimeStringNoTimeZone.split('T');
            var dateTimeString = dateTimeStringArray[0] + ' ' + dateTimeStringArray[1];

            $(this).find('input[name="' + CONFIG.keys.dateTime + '"]').val(dateTimeString);
        });

        $WINDOW.focus(refreshData);
        $WINDOW.resize(resizeUI);

        $WINDOW.resize();
        refreshData();
    };

    return publicMethods;
}());
<commit_msg>Do not show grid lines in chart<commit_after>/*jslint browser, this, for*/
/*global window, $, Morris*/
var Main = (function () {
    'use strict';

    $.ajaxSetup({
        cache: false
    });

    var publicMethods = {};

    publicMethods.init = function (CONFIG) {
        var loadingIconClassName = 'loading-icon';
        var $WINDOW = $(window);
        var $CHART = $('#chart');
        var $TABLE = $('#table');
        var $FORM = $('form');

        var TEXT = {
            sys: 'SYS',
            dia: 'DIA',
            pulse: 'Pulse'
        };

        var RGB_COLORS = {
            sys: [234, 27, 19],
            dia: [230, 100, 19],
            pulse: [110, 110, 245]
        };

        var GOALS = [120, 80, 60];

        function getRgbaColor(rgbArray, opacity) {
            opacity = opacity || 1;
            return 'rgba(' + rgbArray[0] + ', ' + rgbArray[1] + ', ' + rgbArray[2] + ', ' + opacity + ')';
        }

        var GOAL_OPACITY = 0.6;

        var MORRIS_OPTIONS = {
            element: 'chart',
            data: [],
            xkey: CONFIG.keys.dateTime,
            ykeys: [CONFIG.keys.sys, CONFIG.keys.dia, CONFIG.keys.pulse],
            labels: [TEXT.sys, TEXT.dia, TEXT.pulse],
            lineColors: [getRgbaColor(RGB_COLORS.sys), getRgbaColor(RGB_COLORS.dia), getRgbaColor(RGB_COLORS.pulse)],
            lineWidth: 1,
            pointSize: 0,
            smooth: false,
            resize: true,
            continuousLine: true,
            grid: false,
            goals: GOALS,
            goalStrokeWidth: 1,
            goalLineColors: [
                getRgbaColor(RGB_COLORS.sys, GOAL_OPACITY),
                getRgbaColor(RGB_COLORS.dia, GOAL_OPACITY),
                getRgbaColor(RGB_COLORS.pulse, GOAL_OPACITY)
            ]
        };

        function showLoadingIcon(jElement) {
            jElement.css('position', 'relative');
            jElement.append($('
', {class: loadingIconClassName}));
        }

        function hideLoadingIcon(jElement) {
            jElement.find('.' + loadingIconClassName).remove();
            jElement.css('position', 'initial');
        }

        function setTableData(logData) {
            $TABLE.empty();

            var table = $('
');

            var firstRow = $('
');
            firstRow.append($(''));
            firstRow.append($('', {html: TEXT.sys + '
[mmHg]'}));
            firstRow.append($('', {html: TEXT.dia + '
[mmHg]'}));
            firstRow.append($('', {html: TEXT.pulse + '
[1/min]'}));
            table.append(firstRow);

            var bloodPressureCategories = {
                sys: [120, 130, 140, 160, 180],
                dia: [80, 85, 90, 100, 110]
            };
            var bloodPressureClasses = [
                'bp-optimal',
                'bp-normal',
                'bp-normalhigh',
                'bp-hypertension1',
                'bp-hypertension2',
                'bp-hypertension3'
            ];
            var getBloodPressureClass = function (categoryValues, value) {
                var idx, bloodPressureClass;
                for (idx = categoryValues.length - 1; idx >= 0; idx -= 1) {
                    if (value >= categoryValues[idx]) {
                        bloodPressureClass = bloodPressureClasses[idx + 1];
                        break;
                    }
                }
                return bloodPressureClass || bloodPressureClasses[0];
            };

            logData.forEach(function (entry) {
                var sysClass = getBloodPressureClass(bloodPressureCategories.sys, entry[CONFIG.keys.sys]);
                var diaClass = getBloodPressureClass(bloodPressureCategories.dia, entry[CONFIG.keys.dia]);

                var newRow = $('
');
                newRow.append($('', {text: entry[CONFIG.keys.dateTime]}));
                newRow.append($('', {text: entry[CONFIG.keys.sys], class: sysClass}));
                newRow.append($('', {text: entry[CONFIG.keys.dia], class: diaClass}));
                newRow.append($('', {text: entry[CONFIG.keys.pulse]}));
                table.append(newRow);
            });

            $TABLE.append(table);
        }

        function getExtremeOfObjectsArray(objectsArray, extremeFn, keys) {
            if (keys === undefined && objectsArray.length > 0) {
                keys = objectsArray[0].keys();
            }

            var currentExtreme;
            objectsArray.forEach(function (object) {
                var objectValues = [];
                keys.forEach(function (objectKey) {
                    objectValues.push(object[objectKey]);
                });

                var value = extremeFn.apply(null, objectValues);
                var isNewExtreme = (extremeFn === Math.min)
                    ? (value < currentExtreme)
                    : (value > currentExtreme);
                if (currentExtreme === undefined || isNewExtreme) {
                    currentExtreme = value;
                }
            });
            return currentExtreme;
        }

        function round(number, exponent, roundingFunction, base) {
            var factor = Math.pow(base || 10, exponent || 0);
            return roundingFunction(number * factor) / factor;
        }

        function getExtremeValueIncludingGoals(extremeFn, logData, keys, goals) {
            var extremeValue = getExtremeOfObjectsArray(logData, extremeFn, keys);
            var minValueIncludingGoals = extremeFn(extremeValue, extremeFn.apply(null, goals));
            return minValueIncludingGoals;
        }

        function refreshData() {
            showLoadingIcon($CHART);
            var keys = [CONFIG.keys.sys, CONFIG.keys.dia, CONFIG.keys.pulse];

            $.getJSON(CONFIG.api.endPoints.getLog).done(function (logData) {
                MORRIS_OPTIONS.ymin = round(getExtremeValueIncludingGoals(Math.min, logData, keys, GOALS), -1, Math.floor);
                MORRIS_OPTIONS.ymax = round(getExtremeValueIncludingGoals(Math.max, logData, keys, GOALS), -1, Math.ceil);
                $CHART.empty();
                Morris.Line(MORRIS_OPTIONS).setData(logData);

                setTableData(JSON.parse(JSON.stringify(logData)).reverse());
            }).always(function () {
                hideLoadingIcon($CHART);
            });
        }

        function resizeUI() {
            $CHART.height($WINDOW.innerHeight() - $FORM.innerHeight());
        }

        $FORM.on('submit', function () {
            var now = new Date();
            var localDateTime = new Date(now.getTime() - now.getTimezoneOffset() * 60 * 1000);
            var isoDateTimeStringNoTimeZone = localDateTime.toISOString().substr(0, 19);
            var dateTimeStringArray = isoDateTimeStringNoTimeZone.split('T');
            var dateTimeString = dateTimeStringArray[0] + ' ' + dateTimeStringArray[1];

            $(this).find('input[name="' + CONFIG.keys.dateTime + '"]').val(dateTimeString);
        });

        $WINDOW.focus(refreshData);
        $WINDOW.resize(resizeUI);

        $WINDOW.resize();
        refreshData();
    };

    return publicMethods;
}());
<|endoftext|>


<commit_before>// lajiapi module

const url = require('url');
const querystring = require('querystring');
const parallel = require('async/parallel');
const md5 = require('js-md5'); // REMOVE
const fs = require('fs');

const get = require('./get');
const telegram = require('./telegram');
//const keys = require('../keys.js');

let parameters = {};

// --------------------------------------------------------------------
// Routing, API queries

// Decides what to do with the query
function handleQuery(serverRequest, serverResponse) {
	parameters.response = serverResponse; // Makes this available to the whole module
	setUrlParameters(serverRequest.url);

	get.init(parameters); // More elegant way to do this?
	telegram.init(parameters); // More elegant way to do this?

//	console.log(parameters.urlParts);
//	console.log(parameters.queryParts);

	// Router - decides what to do based on URL
	if ("/vihkolatest" == parameters.urlParts.pathname) {

		pingCronitor("run");
		parameters.requestType = "getVihkolatest";

		parallel({
			latest: function(callback) {
				get.https(
					"api.laji.fi",
					("/v0/warehouse/query/list?selected=document.createdDate%2Cdocument.documentId%2Cdocument.editors%2Cdocument.loadDate%2Cgathering.biogeographicalProvince%2Cgathering.conversions.wgs84CenterPoint.lat%2Cgathering.conversions.wgs84CenterPoint.lon%2Cgathering.country%2Cgathering.eventDate.begin%2Cgathering.eventDate.end%2Cgathering.gatheringId%2Cgathering.interpretations.biogeographicalProvince%2Cgathering.interpretations.country%2Cgathering.interpretations.finnishMunicipality%2Cgathering.locality%2Cgathering.municipality%2Cgathering.notes%2Cgathering.province%2Cgathering.team%2Cunit.linkings.taxon.qname%2Cunit.linkings.taxon.scientificName%2Cunit.linkings.taxon.vernacularName%2Cunit.unitId&orderBy=document.documentId%20DESC&pageSize=100&page=1&collectionId=HR.1747" + "&access_token=" +  process.env.LAJI_TOKEN),
					callback
				);
			}
		},
		function(err, results) {
			getVihkolatest(results);
		});

	}

	else if ("/uploads" == parameters.urlParts.pathname) {

//		pingCronitor("run"); // Hasn't yet implemented the complete command
		parameters.requestType = "getUploads";
		parameters.sinceDate = getDateYesterday();

		parallel({
			uploads: function(callback) {
				get.https(
					"api.laji.fi",
					("/v0/warehouse/query/aggregate?aggregateBy=document.collectionId&geoJSON=false&pageSize=100&page=1&loadedLaterThan=" + parameters.sinceDate + "&access_token=" +  process.env.LAJI_TOKEN),
					callback
				);
			},
			collections: function(callback) {
				get.https(
					"api.laji.fi",
					("/v0/collections?lang=fi&langFallback=true&pageSize=1000" + "&access_token=" +  process.env.LAJI_TOKEN), // Currently ~360 (2017-01-11)
					callback
				);
			}
		},
		function(err, results) {
			getUploads(results);
		});
	}

	else {
		console.log(serverRequest.url + " not found");
		parameters.response.writeHead(404);
		parameters.response.end("Page not found 404");
	}
}

// --------------------------------------------------------------------
// Process data

function getUploads(data) {
	let collectionsQueryObj = getCollectionsQueryObject(data.collections);
	let plaintext = getUploadsPlaintext(data.uploads, collectionsQueryObj);

	let messageParts = {
		prefix: ("Päivitykset Lajitietokeskuksen tietovarastoon " + parameters.sinceDate + " jälkeen:\n"),
		text : plaintext,
		suffix: "\nLuvut sisältävät sekä uudet että päivitetyt havainnot."
	};
	let message = wrapToMessage(messageParts);

	telegram.sendToTelegram(message);
}

function getVihkolatest(data) {
//	console.log(JSON.stringify(data));
//	console.log(data.latest.results[0]);

	let documentsArray = data.latest.results;

//	debug(documentsArray);

	let latestDocumentId = "none";
	let documentsObj = {};
	let totalUnitCount = 0;
	let send = true;
	let filename = "local/vihkolatest.txt";

	// Goes through units, each of which repeats it's parent gathering and document data.
	for (let i = 0; i < documentsArray.length; i++) {
		if ("none" == latestDocumentId) {
			latestDocumentId = documentsArray[i].document.documentId; // const ?
			if (1 != parameters.queryParts.resend) {
				let previousDocumentId = fs.readFileSync(filename);
				if (previousDocumentId == latestDocumentId) {
					send = false;
					break;
				}
				else {
					fs.writeFileSync(filename, latestDocumentId);
				}
			}
		}

		if (documentsArray[i].document.documentId == latestDocumentId) {
			if (typeof documentsObj[latestDocumentId] == "undefined") {
				documentsObj[latestDocumentId] = {};
			}
			if (typeof documentsObj[latestDocumentId][documentsArray[i].gathering.gatheringId] == "undefined") {
				documentsObj[latestDocumentId][documentsArray[i].gathering.gatheringId] = {};
			}

//			debug(latestDocumentId);
			if (typeof documentsArray[i].gathering.locality !== "undefined") {
				documentsObj[latestDocumentId][documentsArray[i].gathering.gatheringId]["locality"] = documentsArray[i].gathering.locality;
			}
			else
			{
				documentsObj[latestDocumentId][documentsArray[i].gathering.gatheringId]["locality"] = "(tyhjä sijainti)";
			}
			if (typeof documentsArray[i].gathering.team !== "undefined") {
				documentsObj[latestDocumentId][documentsArray[i].gathering.gatheringId]["team"] = documentsArray[i].gathering.team.join(", ");
			}
			else
			{
				documentsObj[latestDocumentId][documentsArray[i].gathering.gatheringId]["team"] = "(tyhjä ryhmä)";
			}
			documentsObj[latestDocumentId][documentsArray[i].gathering.gatheringId]["unitCount"] = addOne(documentsObj[latestDocumentId][documentsArray[i].gathering.gatheringId]["unitCount"]); // ABBA
			totalUnitCount++;
		}
		else {
			break; // Break when first document ends
		}
	}

//	console.log(totalUnitCount);
//	debug(documentsObj);

	// TODO: better message handling

	if (send) {
		let messageParts = {
			prefix: (totalUnitCount + " uutta havista:\n"),
			text : JSON.stringify(documentsObj),
			suffix: ""
		};
		let message = wrapToMessage(messageParts);
		parameters.response.end(message);
		telegram.sendToTelegram(message);
	}
	else
	{
		parameters.response.end("No new documents, latest was " + latestDocumentId);
	}

	pingCronitor("complete");
}

// --------------------------------------------------------------------
// Format getUploads

// Prepares collection names into an object
function getCollectionsQueryObject(data) {
	let collectionsQueryObj = {};
	for (let i = 0; i < data.results.length; i++) {
		let item = data.results[i];
		let collectionId = "http://tun.fi/" + item.id; // Add missing domain name
		collectionsQueryObj[collectionId] = item.longName;
	}
	return collectionsQueryObj;
}

// Formats the object-data into a human-readable plaintext
function getUploadsPlaintext(data, collectionsQueryObj) {
	let plaintext = "";
	let suffix = " havaintoa";

	for (let i = 0; i < data.results.length; i++) {
		let item = data.results[i];
		let collectionId = item.aggregateBy["document.collectionId"];
		let collectionName = collectionsQueryObj[collectionId];
		let count = item.count;

		plaintext += (i+1) + ". " + collectionName + ": " + count + suffix + "\n";
		suffix = "";
	}
	return plaintext;
}

// --------------------------------------------------------------------
// Helpers

// Wraps the text into a message, with intro & footer.
function wrapToMessage(data) {
	let message = data.prefix + " " + data.text + " " + data.suffix;
	return message;
}

function getDateYesterday() {
	let date = new Date();
	let day = date.getDate() - 2; // -2 because of exclusive filter
	let month = date.getMonth() + 1;
	let year = date.getFullYear();
	return (year + "-" + month + "-" + day);
}

function setUrlParameters(urlString) {
	parameters.urlParts = url.parse(urlString);
	parameters.queryParts = querystring.parse(parameters.urlParts.query);
	if (1 == parameters.queryParts.telegram) {
		parameters.sendToTelegram = true;
	}
	else {
		parameters.sendToTelegram = false;
	}
	console.log(parameters.queryParts);
}

function addOne(nro) {
	if (typeof nro == 'undefined') {
		nro = 1;
	}
	else {
		nro++;
	}
	return nro;
}

function debug(data) {
	parameters.sendToTelegram = false;
	console.log("************************************");
	console.log("Debug:");
	console.log(data);
	console.log("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^");
	parameters.response.end("Debug");
	debugger;
}

function pingCronitor(endpoint) {
	if (1 == parameters.queryParts.cronitor) {
		get.https(
			"cronitor.link",
			("/" + process.env.CRONITOR_PING_ID + "/" + endpoint),
			function () {
				console.log("Pinged Cronitor " + endpoint);
			}
		);
	}
	else {
		console.log("Cronitor ping is off.");
	}
}

// --------------------------------------------------------------------
// Exports

module.exports = {
	handleQuery : handleQuery
};
<commit_msg>Style corrections 2<commit_after>// lajiapi module

const url = require("url");
const querystring = require("querystring");
const parallel = require("async/parallel");
const md5 = require("js-md5"); // REMOVE
const fs = require("fs");

const get = require("./get");
const telegram = require("./telegram");
//const keys = require("../keys.js");

let parameters = {};

// --------------------------------------------------------------------
// Routing, API queries

// Decides what to do with the query
function handleQuery(serverRequest, serverResponse) {
	parameters.response = serverResponse; // Makes this available to the whole module
	setUrlParameters(serverRequest.url);

	get.init(parameters); // More elegant way to do this?
	telegram.init(parameters); // More elegant way to do this?

//	console.log(parameters.urlParts);
//	console.log(parameters.queryParts);

	// Router - decides what to do based on URL
	if ("/vihkolatest" == parameters.urlParts.pathname) {

		pingCronitor("run");
		parameters.requestType = "getVihkolatest";

		parallel({
			latest: function(callback) {
				get.https(
					"api.laji.fi",
					("/v0/warehouse/query/list?selected=document.createdDate%2Cdocument.documentId%2Cdocument.editors%2Cdocument.loadDate%2Cgathering.biogeographicalProvince%2Cgathering.conversions.wgs84CenterPoint.lat%2Cgathering.conversions.wgs84CenterPoint.lon%2Cgathering.country%2Cgathering.eventDate.begin%2Cgathering.eventDate.end%2Cgathering.gatheringId%2Cgathering.interpretations.biogeographicalProvince%2Cgathering.interpretations.country%2Cgathering.interpretations.finnishMunicipality%2Cgathering.locality%2Cgathering.municipality%2Cgathering.notes%2Cgathering.province%2Cgathering.team%2Cunit.linkings.taxon.qname%2Cunit.linkings.taxon.scientificName%2Cunit.linkings.taxon.vernacularName%2Cunit.unitId&orderBy=document.documentId%20DESC&pageSize=100&page=1&collectionId=HR.1747" + "&access_token=" +  process.env.LAJI_TOKEN),
					callback
				);
			}
		},
		function(err, results) {
			getVihkolatest(results);
		});

	}

	else if ("/uploads" == parameters.urlParts.pathname) {

//		pingCronitor("run"); // Hasn't yet implemented the complete command
		parameters.requestType = "getUploads";
		parameters.sinceDate = getDateYesterday();

		parallel({
			uploads: function(callback) {
				get.https(
					"api.laji.fi",
					("/v0/warehouse/query/aggregate?aggregateBy=document.collectionId&geoJSON=false&pageSize=100&page=1&loadedLaterThan=" + parameters.sinceDate + "&access_token=" +  process.env.LAJI_TOKEN),
					callback
				);
			},
			collections: function(callback) {
				get.https(
					"api.laji.fi",
					("/v0/collections?lang=fi&langFallback=true&pageSize=1000" + "&access_token=" +  process.env.LAJI_TOKEN), // Currently ~360 (2017-01-11)
					callback
				);
			}
		},
		function(err, results) {
			getUploads(results);
		});
	}

	else {
		console.log(serverRequest.url + " not found");
		parameters.response.writeHead(404);
		parameters.response.end("Page not found 404");
	}
}

// --------------------------------------------------------------------
// Process data

function getUploads(data) {
	let collectionsQueryObj = getCollectionsQueryObject(data.collections);
	let plaintext = getUploadsPlaintext(data.uploads, collectionsQueryObj);

	let messageParts = {
		prefix: ("Päivitykset Lajitietokeskuksen tietovarastoon " + parameters.sinceDate + " jälkeen:\n"),
		text : plaintext,
		suffix: "\nLuvut sisältävät sekä uudet että päivitetyt havainnot."
	};
	let message = wrapToMessage(messageParts);

	telegram.sendToTelegram(message);
}

function getVihkolatest(data) {
//	console.log(JSON.stringify(data));
//	console.log(data.latest.results[0]);

	let documentsArray = data.latest.results;

//	debug(documentsArray);

	let latestDocumentId = "none";
	let documentsObj = {};
	let totalUnitCount = 0;
	let send = true;
	let filename = "local/vihkolatest.txt";

	// Goes through units, each of which repeats it's parent gathering and document data.
	for (let i = 0; i < documentsArray.length; i++) {
		if ("none" == latestDocumentId) {
			latestDocumentId = documentsArray[i].document.documentId; // const ?
			if (1 != parameters.queryParts.resend) {
				let previousDocumentId = fs.readFileSync(filename);
				if (previousDocumentId == latestDocumentId) {
					send = false;
					break;
				}
				else {
					fs.writeFileSync(filename, latestDocumentId);
				}
			}
		}

		if (documentsArray[i].document.documentId == latestDocumentId) {
			if (typeof documentsObj[latestDocumentId] == "undefined") {
				documentsObj[latestDocumentId] = {};
			}
			if (typeof documentsObj[latestDocumentId][documentsArray[i].gathering.gatheringId] == "undefined") {
				documentsObj[latestDocumentId][documentsArray[i].gathering.gatheringId] = {};
			}

//			debug(latestDocumentId);
			if (typeof documentsArray[i].gathering.locality !== "undefined") {
				documentsObj[latestDocumentId][documentsArray[i].gathering.gatheringId]["locality"] = documentsArray[i].gathering.locality;
			}
			else
			{
				documentsObj[latestDocumentId][documentsArray[i].gathering.gatheringId]["locality"] = "(tyhjä sijainti)";
			}
			if (typeof documentsArray[i].gathering.team !== "undefined") {
				documentsObj[latestDocumentId][documentsArray[i].gathering.gatheringId]["team"] = documentsArray[i].gathering.team.join(", ");
			}
			else
			{
				documentsObj[latestDocumentId][documentsArray[i].gathering.gatheringId]["team"] = "(tyhjä ryhmä)";
			}
			documentsObj[latestDocumentId][documentsArray[i].gathering.gatheringId]["unitCount"] = addOne(documentsObj[latestDocumentId][documentsArray[i].gathering.gatheringId]["unitCount"]); // ABBA
			totalUnitCount++;
		}
		else {
			break; // Break when first document ends
		}
	}

//	console.log(totalUnitCount);
//	debug(documentsObj);

	// TODO: better message handling

	if (send) {
		let messageParts = {
			prefix: (totalUnitCount + " uutta havista:\n"),
			text : JSON.stringify(documentsObj),
			suffix: ""
		};
		let message = wrapToMessage(messageParts);
		parameters.response.end(message);
		telegram.sendToTelegram(message);
	}
	else
	{
		parameters.response.end("No new documents, latest was " + latestDocumentId);
	}

	pingCronitor("complete");
}

// --------------------------------------------------------------------
// Format getUploads

// Prepares collection names into an object
function getCollectionsQueryObject(data) {
	let collectionsQueryObj = {};
	for (let i = 0; i < data.results.length; i++) {
		let item = data.results[i];
		let collectionId = "http://tun.fi/" + item.id; // Add missing domain name
		collectionsQueryObj[collectionId] = item.longName;
	}
	return collectionsQueryObj;
}

// Formats the object-data into a human-readable plaintext
function getUploadsPlaintext(data, collectionsQueryObj) {
	let plaintext = "";
	let suffix = " havaintoa";

	for (let i = 0; i < data.results.length; i++) {
		let item = data.results[i];
		let collectionId = item.aggregateBy["document.collectionId"];
		let collectionName = collectionsQueryObj[collectionId];
		let count = item.count;

		plaintext += (i+1) + ". " + collectionName + ": " + count + suffix + "\n";
		suffix = "";
	}
	return plaintext;
}

// --------------------------------------------------------------------
// Helpers

// Wraps the text into a message, with intro & footer.
function wrapToMessage(data) {
	let message = data.prefix + " " + data.text + " " + data.suffix;
	return message;
}

function getDateYesterday() {
	let date = new Date();
	let day = date.getDate() - 2; // -2 because of exclusive filter
	let month = date.getMonth() + 1;
	let year = date.getFullYear();
	return (year + "-" + month + "-" + day);
}

function setUrlParameters(urlString) {
	parameters.urlParts = url.parse(urlString);
	parameters.queryParts = querystring.parse(parameters.urlParts.query);
	if (1 == parameters.queryParts.telegram) {
		parameters.sendToTelegram = true;
	}
	else {
		parameters.sendToTelegram = false;
	}
	console.log(parameters.queryParts);
}

function addOne(nro) {
	if (typeof nro == "undefined") {
		nro = 1;
	}
	else {
		nro++;
	}
	return nro;
}

function debug(data) {
	parameters.sendToTelegram = false;
	console.log("************************************");
	console.log("Debug:");
	console.log(data);
	console.log("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^");
	parameters.response.end("Debug");
	debugger;
}

function pingCronitor(endpoint) {
	if (1 == parameters.queryParts.cronitor) {
		get.https(
			"cronitor.link",
			("/" + process.env.CRONITOR_PING_ID + "/" + endpoint),
			function () {
				console.log("Pinged Cronitor " + endpoint);
			}
		);
	}
	else {
		console.log("Cronitor ping is off.");
	}
}

// --------------------------------------------------------------------
// Exports

module.exports = {
	handleQuery : handleQuery
};
<|endoftext|>


<commit_before>var express = require( "express" );
var async = require( "async" );
var moment = require( "moment" );
var models = require( "./models" );

var handleSensorReading = function ( messageBroker, logger, app ) {
    var getSensorData = function ( done ) {
        messageBroker.publish( "sensor.get", { serialMessage: "A" }, function ( err, data ) {
            if ( err ) {
                return done( err );
            }
            return done( null, data );
        } );
    };

    var saveSensorData = function ( sensorData, done ) {
        models.SensorReading.create( sensorData, function ( err, data ) {
            if ( err ) {
                return done( err );
            }
            return done( null, data );
        } );
    };

    app.get( "/job/sensor-reading", function ( req, res ) {
        async.waterfall( [ getSensorData, saveSensorData ], function ( err, data ) {
            if ( err ) {
                logger.log( err );
                return res.status( 500 ).json();
            }
            return res.status( 200 ).json( data );
        } );
    } );
};

var handleSystemReading = function ( messageBroker, logger, app ) {
    var getSystemData = function ( done ) {
        messageBroker.publish( "system.get", {}, function ( err, data ) {
            if ( err ) {
                return done( err );
            }
            return done( null, data );
        } );
    };

    var saveSystemData = function ( systemData, done ) {
        models.SystemReading.create( systemData, function ( err, data ) {
            if ( err ) {
                return done( err );
            }
            return done( null, data );
        } );
    };

    app.get( "/job/system-reading", function ( req, res ) {
        async.waterfall( [ getSystemData, saveSystemData ], function ( err, data ) {
            if ( err ) {
                logger.log( err );
                return res.status( 500 ).json();
            }
            return res.status( 200 ).json( data );
        } );
    } );
};

var handleDailyStats = function ( options, logger, app ) {
    app.get( options.path, function ( req, res ) {
        var yesterdayStart = moment().subtract( 1, "day" ).startOf( "day" ).toDate();
        var yesterdayEnd = moment().subtract( 1, "day" ).endOf( "day" ).toDate();

        var statCalcOptions = {
            startDate: yesterdayStart,
            endDate: yesterdayEnd,
            date: yesterdayStart,
            type: "day"
        };

        options.model.calcStatsForDateRange( statCalcOptions, function ( err, data ) {
            if ( err ) {
                logger.log( err );
                return res.status( 500 ).json();
            }
            return res.status( 200 ).json( data );
        } );
    } );
};

var handleWeeklyStats = function ( options, logger, app ) {
    app.get( options.path, function ( req, res ) {
        var yesterdayStart = moment().subtract( 1, "day" ).startOf( "day" ).toDate();
        var yesterdayEnd = moment().subtract( 1, "day" ).endOf( "day" ).toDate();
        var weekAgoStart = moment().subtract( 7, "days").startOf( "day" ).toDate();

        var statCalcOptions = {
            startDate: weekAgoStart,
            endDate: yesterdayEnd,
            date: yesterdayStart,
            type: "week"
        };

        options.model.calcStatsForDateRange( statCalcOptions, function ( err, data ) {
            if ( err ) {
                logger.log( err );
                return res.status( 500 ).json();
            }
            return res.status( 200 ).json( data );
        } );
    } );
};

var handleMonthlyStats = function ( options, logger, app ) {
    app.get( options.path, function ( req, res ) {
        var monthStart = moment().subtract( 1, "day" ).startOf( "month" ).toDate();
        var monthEnd = moment().subtract( 1, "day" ).endOf( "month" ).toDate();

        var statCalcOptions = {
            startDate: monthStart,
            endDate: monthEnd,
            date: monthStart,
            type: "month"
        };

        options.model.calcStatsForDateRange( statCalcOptions, function ( err, data ) {
            if ( err ) {
                logger.log( err );
                return res.status( 500 ).json();
            }
            return res.status( 200 ).json( data );
        } );
    } );
};

module.exports = function ( messageBroker, logger ) {

    var app = express();

    messageBroker.create( "sensor.get" );
    messageBroker.create( "system.get" );

    handleSensorReading( messageBroker, logger, app );
    handleSystemReading( messageBroker, logger, app );

    handleDailyStats( {
        path: "/job/sensor-stats/day",
        model: models.SensorReading
    }, logger, app );

    handleDailyStats( {
        path: "/job/system-stats/day",
        model: models.SystemReading
    }, logger, app );

    handleWeeklyStats( {
        path: "/job/sensor-stats/week",
        model: models.SensorReading
    }, logger, app );

    handleWeeklyStats( {
        path: "/job/system-stats/week",
        model: models.SystemReading
    }, logger, app );

    handleMonthlyStats( {
        path: "/job/sensor-stats/month",
        model: models.SensorReading
    }, logger, app );

    handleMonthlyStats( {
        path: "/job/system-stats/month",
        model: models.SystemReading
    }, logger, app );

    return app;

};
<commit_msg>Be smarter about this<commit_after>var express = require( "express" );
var async = require( "async" );
var moment = require( "moment" );
var models = require( "./models" );

var handleSensorReading = function ( messageBroker, logger, app ) {
    var getSensorData = function ( done ) {
        messageBroker.publish( "sensor.get", { serialMessage: "A" }, function ( err, data ) {
            if ( err ) {
                return done( err );
            }
            return done( null, data );
        } );
    };

    var saveSensorData = function ( sensorData, done ) {
        models.SensorReading.create( sensorData, function ( err, data ) {
            if ( err ) {
                return done( err );
            }
            return done( null, data );
        } );
    };

    app.get( "/job/sensor-reading", function ( req, res ) {
        async.waterfall( [ getSensorData, saveSensorData ], function ( err, data ) {
            if ( err ) {
                logger.log( err );
                return res.status( 500 ).json();
            }
            return res.status( 200 ).json( data );
        } );
    } );
};

var handleSystemReading = function ( messageBroker, logger, app ) {
    var getSystemData = function ( done ) {
        messageBroker.publish( "system.get", {}, function ( err, data ) {
            if ( err ) {
                return done( err );
            }
            return done( null, data );
        } );
    };

    var saveSystemData = function ( systemData, done ) {
        models.SystemReading.create( systemData, function ( err, data ) {
            if ( err ) {
                return done( err );
            }
            return done( null, data );
        } );
    };

    app.get( "/job/system-reading", function ( req, res ) {
        async.waterfall( [ getSystemData, saveSystemData ], function ( err, data ) {
            if ( err ) {
                logger.log( err );
                return res.status( 500 ).json();
            }
            return res.status( 200 ).json( data );
        } );
    } );
};

var handleDailyStats = function ( options, logger, app ) {
    app.get( options.path, function ( req, res ) {
        var yesterdayStart = moment().subtract( 1, "day" ).startOf( "day" ).toDate();
        var yesterdayEnd = moment().subtract( 1, "day" ).endOf( "day" ).toDate();

        var statCalcOptions = {
            startDate: yesterdayStart,
            endDate: yesterdayEnd,
            date: yesterdayStart,
            type: "day"
        };

        options.model.calcStatsForDateRange( statCalcOptions, function ( err, data ) {
            if ( err ) {
                logger.log( err );
                return res.status( 500 ).json();
            }
            return res.status( 200 ).json( data );
        } );
    } );
};

var handleWeeklyStats = function ( options, logger, app ) {
    app.get( options.path, function ( req, res ) {
        var lastWeekStart = moment().subtract( 1, "week").startOf( "week" ).toDate();
        var lastWeekEnd = moment().subtract( 1, "week").endOf( "week" ).toDate();

        var statCalcOptions = {
            startDate: lastWeekStart,
            endDate: lastWeekEnd,
            date: lastWeekStart,
            type: "week"
        };

        options.model.calcStatsForDateRange( statCalcOptions, function ( err, data ) {
            if ( err ) {
                logger.log( err );
                return res.status( 500 ).json();
            }
            return res.status( 200 ).json( data );
        } );
    } );
};

var handleMonthlyStats = function ( options, logger, app ) {
    app.get( options.path, function ( req, res ) {
        var lastMonthStart = moment().subtract( 1, "month" ).startOf( "month" ).toDate();
        var lastMonthEnd = moment().subtract( 1, "month" ).startOf( "month" ).toDate();

        var statCalcOptions = {
            startDate: lastMonthStart,
            endDate: lastMonthEnd,
            date: lastMonthStart,
            type: "month"
        };

        options.model.calcStatsForDateRange( statCalcOptions, function ( err, data ) {
            if ( err ) {
                logger.log( err );
                return res.status( 500 ).json();
            }
            return res.status( 200 ).json( data );
        } );
    } );
};

module.exports = function ( messageBroker, logger ) {

    var app = express();

    messageBroker.create( "sensor.get" );
    messageBroker.create( "system.get" );

    handleSensorReading( messageBroker, logger, app );
    handleSystemReading( messageBroker, logger, app );

    handleDailyStats( {
        path: "/job/sensor-stats/day",
        model: models.SensorReading
    }, logger, app );

    handleDailyStats( {
        path: "/job/system-stats/day",
        model: models.SystemReading
    }, logger, app );

    handleWeeklyStats( {
        path: "/job/sensor-stats/week",
        model: models.SensorReading
    }, logger, app );

    handleWeeklyStats( {
        path: "/job/system-stats/week",
        model: models.SystemReading
    }, logger, app );

    handleMonthlyStats( {
        path: "/job/sensor-stats/month",
        model: models.SensorReading
    }, logger, app );

    handleMonthlyStats( {
        path: "/job/system-stats/month",
        model: models.SystemReading
    }, logger, app );

    return app;

};
<|endoftext|>


<commit_before>'use strict';

const Router = require('./index');
const errors = require('storj-service-error-types');
const inherits = require('util').inherits;

/**
 * Handles endpoints for all contact related endpoints
 * @constructor
 * @extends {Router}
 */
function ContactsRouter(options) {
  if (!(this instanceof ContactsRouter)) {
    return new ContactsRouter(options);
  }

  Router.apply(this, arguments);
}

inherits(ContactsRouter, Router);

ContactsRouter.DEFAULTS = {
  skip: 0,
  limit: 30
};

/**
 * Returns the correct skip and limit from the supplied page number
 * @private
 */
ContactsRouter.prototype._getSkipLimitFromPage = function(page) {
  page = page || 1;

  return {
    limit: ContactsRouter.DEFAULTS.limit,
    skip: (page - 1) * ContactsRouter.DEFAULTS.limit
  };
};

/**
 * Lists the contacts according the the supplied query
 * @param {http.IncomingMessage} req
 * @param {http.ServerResponse} res
 * @param {Function} next
 */
ContactsRouter.prototype.getContactList = function(req, res, next) {
  const Contact = this.storage.models.Contact;

  let allowedQueryParams = ['address', 'port', 'protocol', 'userAgent'];
  let opts = this._getSkipLimitFromPage(req.query.page);
  let skip = opts.skip;
  let limit = opts.limit;
  let query = {};

  for (let param in req.query) {
    if (allowedQueryParams.indexOf(param) !== -1) {
      query[param] = req.query[param];
    }
  }

  let cursor = Contact.find(query).skip(skip).limit(limit).sort({
    lastSeen: -1
  });

  cursor.exec(function(err, contacts) {
    if (err) {
      return next(new errors.InternalError(err.message));
    }

    res.status(200).send(contacts.sort(function(c1, c2) {
      return c2.lastSeen - c1.lastSeen;
    }).map(function(c) {
      return c.toObject();
    }));
  });
};

/**
 * Returns the contact information for the given nodeID
 * @param {http.IncomingMessage} req
 * @param {http.ServerResponse} res
 * @param {Function} next
 */
ContactsRouter.prototype.getContactByNodeID = function(req, res, next) {
  const Contact = this.storage.models.Contact;

  Contact.findOne({ _id: req.params.nodeID }, function(err, contact) {
    if (err) {
      return next(new errors.InternalError(err.message));
    }

    if (!contact) {
      return next(new errors.NotFoundError('Contact not found'));
    }

    res.status(200).send(contact.toObject());
  });
};

/**
 * Export definitions
 * @private
 */
ContactsRouter.prototype._definitions = function() {
  return [
    ['GET', '/contacts', this.getContactList],
    ['GET', '/contacts/:nodeID', this.getContactByNodeID]
  ];
};

module.exports = ContactsRouter;
<commit_msg>Add limiter to contacts<commit_after>'use strict';

const Router = require('./index');
const errors = require('storj-service-error-types');
const inherits = require('util').inherits;
const middleware = require('storj-service-middleware');

/**
 * Handles endpoints for all contact related endpoints
 * @constructor
 * @extends {Router}
 */
function ContactsRouter(options) {
  if (!(this instanceof ContactsRouter)) {
    return new ContactsRouter(options);
  }

  Router.apply(this, arguments);

  this._defaults = {
    lookup: function(req) {
      return [req.user, req.connection.remoteAddress]
    },
    onRateLimited: function(req, res, next) {
      log.info('user rate limited', req.user, req.connection.remoteAddress);
      return next(new errors.BadRequestError('Slow down, dude.'));
    },
    total: 100,
    expire: 1000 * 60 // 1 minutes
  };
  this._limiter = middleware.rateLimiter(options.client);
}

inherits(ContactsRouter, Router);

ContactsRouter.DEFAULTS = {
  skip: 0,
  limit: 30
};

/**
 * Returns the correct skip and limit from the supplied page number
 * @private
 */
ContactsRouter.prototype._getSkipLimitFromPage = function(page) {
  page = page || 1;

  return {
    limit: ContactsRouter.DEFAULTS.limit,
    skip: (page - 1) * ContactsRouter.DEFAULTS.limit
  };
};

/**
 * Lists the contacts according the the supplied query
 * @param {http.IncomingMessage} req
 * @param {http.ServerResponse} res
 * @param {Function} next
 */
ContactsRouter.prototype.getContactList = function(req, res, next) {
  const Contact = this.storage.models.Contact;

  let allowedQueryParams = ['address', 'port', 'protocol', 'userAgent'];
  let opts = this._getSkipLimitFromPage(req.query.page);
  let skip = opts.skip;
  let limit = opts.limit;
  let query = {};

  for (let param in req.query) {
    if (allowedQueryParams.indexOf(param) !== -1) {
      query[param] = req.query[param];
    }
  }

  let cursor = Contact.find(query).skip(skip).limit(limit).sort({
    lastSeen: -1
  });

  cursor.exec(function(err, contacts) {
    if (err) {
      return next(new errors.InternalError(err.message));
    }

    res.status(200).send(contacts.sort(function(c1, c2) {
      return c2.lastSeen - c1.lastSeen;
    }).map(function(c) {
      return c.toObject();
    }));
  });
};

/**
 * Returns the contact information for the given nodeID
 * @param {http.IncomingMessage} req
 * @param {http.ServerResponse} res
 * @param {Function} next
 */
ContactsRouter.prototype.getContactByNodeID = function(req, res, next) {
  const Contact = this.storage.models.Contact;

  Contact.findOne({ _id: req.params.nodeID }, function(err, contact) {
    if (err) {
      return next(new errors.InternalError(err.message));
    }

    if (!contact) {
      return next(new errors.NotFoundError('Contact not found'));
    }

    res.status(200).send(contact.toObject());
  });
};

/**
 * Export definitions
 * @private
 */
ContactsRouter.prototype._definitions = function() {
  return [
    ['GET', '/contacts', this._limiter(this._defaults), this.getContactList],
    ['GET', '/contacts/:nodeID', this._limiter(this._defaults), this.getContactByNodeID]
  ];
};

module.exports = ContactsRouter;
<|endoftext|>


<commit_before>require("requirish")._(module);

import subscription_service from "lib/services/subscription_service";
import { StatusCodes } from "lib/datamodel/opcua_status_code";
import { AttributeIds } from "lib/datamodel/attributeIds";
import assert from "better-assert";
import _ from "underscore";

function __validateDataChangeFilter(filter,itemToMonitor,node) {
  assert(itemToMonitor.attributeId  === AttributeIds.Value);
  assert(filter instanceof subscription_service.DataChangeFilter);

  const UAVariable = require("lib/address_space/ua_variable").UAVariable;
  const NodeId = require("lib/datamodel/nodeid").NodeId;

  if (!(node instanceof UAVariable)) {
    return StatusCodes.BadNodeIdInvalid;
  }

  assert(node instanceof UAVariable);

    // if node is not Numerical=> DataChangeFilter
  assert(node.dataType instanceof NodeId);
  const dataType = node.addressSpace.findDataType(node.dataType);

  const dataTypeNumber = node.addressSpace.findDataType("Number");
  if (!dataType.isSupertypeOf(dataTypeNumber)) {
    return StatusCodes.BadFilterNotAllowed;
  }

  if (filter.deadbandType === subscription_service.DeadbandType.Percent) {
    if (filter.deadbandValue < 0 || filter.deadbandValue > 100) {
      return StatusCodes.BadDeadbandFilterInvalid;
    }

        // node must also have a valid euRange
    if (!node.euRange) {
      console.log(` node has no euRange ! DeadbandPercent cannot be used on node ${node.nodeId.toString()}`);
      return StatusCodes.BadFilterNotAllowed;
    }
  }
  return StatusCodes.Good;
}

function __validateFilter(filter, itemToMonitor ,node) {
    // handle filter information
  if (filter && filter instanceof subscription_service.EventFilter && itemToMonitor.attributeId !== AttributeIds.EventNotifier) {
        // invalid filter on Event
    return StatusCodes.BadFilterNotAllowed;
  }

  if (filter && filter instanceof subscription_service.DataChangeFilter && itemToMonitor.attributeId !== AttributeIds.Value) {
        // invalid DataChange filter on non Value Attribute
    return StatusCodes.BadFilterNotAllowed;
  }

  if (filter && itemToMonitor.attributeId !== AttributeIds.EventNotifier && itemToMonitor.attributeId !== AttributeIds.Value) {
    return StatusCodes.BadFilterNotAllowed;
  }

  if (filter instanceof subscription_service.DataChangeFilter) {
    return __validateDataChangeFilter(filter,itemToMonitor,node);
  }

  return StatusCodes.Good;
}

export { __validateFilter as validateFilter };
<commit_msg>validate_filter.js<commit_after>import assert from "better-assert";
import _ from "underscore";

import subscription_service from "lib/services/subscription_service";
import { StatusCodes } from "lib/datamodel/opcua_status_code";
import { AttributeIds } from "lib/datamodel/attributeIds";

import { UAVariable } from "lib/address_space/ua_variable";
import { NodeId }  from "lib/datamodel/nodeid";

function __validateDataChangeFilter(filter,itemToMonitor,node) {
  assert(itemToMonitor.attributeId  === AttributeIds.Value);
  assert(filter instanceof subscription_service.DataChangeFilter);

  if (!(node instanceof UAVariable)) {
    return StatusCodes.BadNodeIdInvalid;
  }

  assert(node instanceof UAVariable);

    // if node is not Numerical=> DataChangeFilter
  assert(node.dataType instanceof NodeId);
  const dataType = node.addressSpace.findDataType(node.dataType);

  const dataTypeNumber = node.addressSpace.findDataType("Number");
  if (!dataType.isSupertypeOf(dataTypeNumber)) {
    return StatusCodes.BadFilterNotAllowed;
  }

  if (filter.deadbandType === subscription_service.DeadbandType.Percent) {
    if (filter.deadbandValue < 0 || filter.deadbandValue > 100) {
      return StatusCodes.BadDeadbandFilterInvalid;
    }

        // node must also have a valid euRange
    if (!node.euRange) {
      console.log(` node has no euRange ! DeadbandPercent cannot be used on node ${node.nodeId.toString()}`);
      return StatusCodes.BadFilterNotAllowed;
    }
  }
  return StatusCodes.Good;
}

function __validateFilter(filter, itemToMonitor ,node) {
    // handle filter information
  if (filter
    && filter instanceof subscription_service.EventFilter
    && itemToMonitor.attributeId !== AttributeIds.EventNotifier) {
        // invalid filter on Event
    return StatusCodes.BadFilterNotAllowed;
  }

  if (filter
    && filter instanceof subscription_service.DataChangeFilter
    && itemToMonitor.attributeId !== AttributeIds.Value) {
        // invalid DataChange filter on non Value Attribute
    return StatusCodes.BadFilterNotAllowed;
  }

  if (filter
    && itemToMonitor.attributeId !== AttributeIds.EventNotifier
    && itemToMonitor.attributeId !== AttributeIds.Value) {
    return StatusCodes.BadFilterNotAllowed;
  }

  if (filter instanceof subscription_service.DataChangeFilter) {
    return __validateDataChangeFilter(filter,itemToMonitor,node);
  }

  return StatusCodes.Good;
}

export { __validateFilter as validateFilter };
<|endoftext|>


<commit_before>/*!
 * a2 suite
 * Copyright(c) 2012 Matti Savolainen <matti@applifier.com>
 * MIT Licensed
 */

/**
 * Module Dependencies
 */
var store = require('../store')
  , serializer = require('serializer')
  , errors = require('../errors')
  , verify = require('../verify');

/**
 * constants
 */

/**
 * http://tools.ietf.org/html/rfc6749#section-4.1.2
 * A maximum authorization code lifetime of 10 minutes is RECOMMENDED
 */
var AUTHORIZATION_CODE_TTL = 600;

/**
 * Authorization
 *
 * @param server  `Server` instance
 * @param options
 *
 * Options:
 *  `scope`   Array of valid scopes. defaults to ['basic']
 *  `store`   The store to be used, see store dir.
 *
 */
function Authorization(server, options) {
  options = options || {};
  this.store = server.store.namespace("authorization");
  this.sserializer = server.serializer;
  this.scopes = options.scopes || {'': "Read your basic info","basic": "Read and write your information"};
  this.server = server;
  this.validScopes = Object.keys(this.scopes);
  this.secureSerializer = server.serializer;
  server.on('check_scope', this.checkScope.bind(this));
  server.on('authorize_form', this.renderAuthorizeForm.bind(this));
}

/**
 * revokeTokensForCode
 *
 * revokes refresh tokens and access tokens issued with the code.
 *
 * @param code
 * @param callback
 */
Authorization.prototype.revokeTokensForCode = function(grant, callback) {
  var self = this;
  delete grant.access_token;
  var refreshTokenKey = 'refresh_token:'+grant.refresh_token;
  delete grant.refresh_token;
  delete grant.code;
  delete grant.used;
  self.store.del(refreshTokenKey, function(err) {
    if(err) {
      return callback(err, null);
    }
    self.saveGrant(grant, callback);
  });
};

/**
 * refreshCode
 *
 * Each authorization request gets it own code, even if a grant exists a new one will be generated and .used removed.
 *
 * @param grant
 * @param callback
 */
Authorization.prototype.refreshCode = function(grant, callback) {
  var self = this;
  self.createCode(grant.user_id, grant.client_id, grant.redirect_uri, grant.scope,  function(err, code) {
    if(err) {
      return errors.code.server_error(req, res);
    }
    grant.used = false;
    grant.code = code;
    self.saveGrant(grant, callback);
  });
};

/**
 * Checks that the requested scopes are valid and if new scopes are provided permissions are asked.
 *
 * @param user
 * @param client
 * @param scope
 * @param callback
 * @return {*}
 */
Authorization.prototype.checkScope = function(req, res, user, client, scope, callback) {
  var self = this
    , unauthorized =  []
    , authorized = []
    , scopeObj = {};
  var a2 = req.a2;
  if(!scope) {
    scope = [''];
  }
  // get stored authorization for this client/user pair.
  if(!Array.isArray(scope)) {
    scope = scope.split(' ');
  }
  console.log("Loading existing grant");
  this.loadGrantForUser(user, client, function(error, grant) {
    if(error) {
      return errors.server_error(req, res);
    }
    console.log("Grant found", grant);
    if(grant && grant.scope != null) {
      authorized = grant.scope;
      scope.filter(function(s) {
        return self.scopes[s] === undefined;
      });

      scope.forEach(function(s) {
        if(authorized.indexOf(s) == -1) {
          unauthorized.push(s);
        }
      });

      for(var i in unauthorized) {
        if(unauthorized.hasOwnProperty(i)) {
          scopeObj[unauthorized[i]] = self.scopes[unauthorized[i]];
        }
      }
      return callback(null, scopeObj);
    } else {
      scope.forEach(function(s) {
        scopeObj[s] = self.scopes[s];
      });
      return callback(null, scopeObj);
    }
  });
};

/**
 * User supplied login form function. If not specified will use default one.
 * @param callback
 */
Authorization.prototype.authorizeForm = function(callback) {
  this.server.removeAllListeners('authorize_form');
  this.server.on('authorize_form', callback.bind(this));
};

/**
 * Default login form, throws as authorizeForm has to be implemented.
 */
Authorization.prototype.renderAuthorizeForm = function(req, res, scope) {
  res.render(this.server.options.authorize_template, {
    authorize_endpoint: this.server.options.authorize_endpoint,
    state:req.a2.state,
    scope:scope,
    redirect_uri: req.a2.client.redirect_uri,
    game: req.a2.client.name,
    response_type: req.query.response_type,
    client_id: req.a2.client.client_id
  });
};

/**
 * createCode
 *
 * Creates a signed & verifiable authorization code.
 *
 * @param user {Object/String} user with .id or string id
 * @param client {Object/String} client with .id or string id
 * @param redirectUri {String} the redirect_uri
 * @param scopes {Array/String} Array of scopes or string separated by space.
 * @param callback {Function} the callback function to be invoked on completion.
 * @return {String} the authorization code.
 */
Authorization.prototype.createCode = function(user, client, redirectUri, scope, callback) {
  var code, clientId, userId;
  if(typeof user == "object") {
    userId = user.id;
  } else {
    userId = user;
  }

  if(typeof client == "object") {
    clientId = client.client_id;
  } else {
    clientId = client;
  }

  if(Array.isArray(scope)) {
    scope = scope.join(' ');
  }

  var data = [clientId, userId, redirectUri, scope, Math.floor(Date.now()/1000)];
  code = this.sserializer.stringify(data);
  if(callback) {
    callback(null, code);
  }
  return code;
};

/**
 * loadCode
 *
 * Loads user_id, client_id, scopes and creation time from a provided authorization code.
 *
 * @param code {String} the encrypted authorization code.
 * @param callback {Function} callback function
 * @return {Object} object containing user_id, client_id, scopes. created_at or null if invalid code.
 */
Authorization.prototype.loadCode = function(code, callback) {
  var obj, info =  this.sserializer.parse(code);
  if(!info) {
    return callback(null, null);
  }
  console.log(code, info);
  obj = {
      client_id: info[0]
    , user_id: info[1]
    , redirect_uri: info[2]
    , scope: info[3].split(' ')
    , created_at: info[4]
  };

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

/**
 * loadGrant
 *
 * loads a grant from database with the authorization code and verifies the data matches the code.
 *
 * @param code
 * @param callback
 */
Authorization.prototype.loadGrant = function(code, callback) {
  var self = this;
  self.loadCode(code, function(err, codeGrant) {
    console.log("code",code,"loaded as ",codeGrant);
    self.store.get(code, function(err, key) {
      if(err || !key) {
        return callback(err, key);
      }
      self.store.get(key, function(err, grant) {
        if(err || !grant) {
          return callback(err, grant);
        }
        grant = JSON.parse(grant);
        console.log("Grant is:",grant);
        if(grant.client_id == codeGrant.client_id && grant.user_id == codeGrant.user_id) {
          return callback(null, grant);
        } else {
          console.warn("Grant code and database missmatch!",grant, codeGrant);
          return callback(null, null);
        }
      });
    });
  });
};

/**
 * Saves a grant to the database with user_id:client_id and authorization:code
 * @param grant {Object} grant object
 * @param callback
 */
Authorization.prototype.saveGrant = function(grant, callback) {
  var self = this;
  if(!grant.key) {
    grant.key = 'Grant:'+grant.client_id + ':' + grant.user_id;
  }
  this.store.set(grant.key, JSON.stringify(grant), function(err, gra) {
    self.store.set(grant.code, grant.key, function(err, gra) { // index the grant with code also.
      // RFC says RECOMMENDED max lifetime for authorization code is 10 mins.
      self.store.expire(grant.code, 10*60, function(err) {
        return callback(err, grant);
      });
    });
  });
};

/**
 * createGrant
 *
 * creates a new grant and an authorization_code for the grant.
 *
 * @param user {Object} user object with atleast .id
 * @param client {Object} user object with atleast .id
 * @param scopes {Array} array of scopes.
 * @param callback
 * @return {*}
 */
Authorization.prototype.createGrant = function(user, client, redirectUri, scope, callback) {
  if(typeof client == "object") {
    client = client.client_id
  }
  if(typeof user == "object") {
    user = user.id;
  }

  var grant = {
      user_id: user
    , client_id: client
    , scope: scope
    , redirect_uri: redirectUri
    , code: this.createCode(user, client, redirectUri, scope)
  };
  return callback(null, grant);
};

/**
 * loadGrantForUser
 *
 * looks for an existing grant for client_id user_id pair.
 *
 * @param user {Object|String} user object/id
 * @param client {Object|String} client object/id
 * @param callback
 */
Authorization.prototype.loadGrantForUser = function(user, client, callback) {
  var self = this;
  if(typeof client == "object") {
    client = client.client_id;
  }
  if(typeof user == "object") {
    user = user.id;
  }

  self.store.get('Grant:'+client+':'+user, function(err, grant) {
    if(grant) {
      grant = JSON.parse(grant);
    }
    return callback(err, grant);
  });
};

/**
 * authorize
 *
 * Authorizes client to user with scope.
 *
 *
 * @param user {Object|String} user object/id
 * @param client {Object|String} client object/id
 * @param redirectUri {String} redirect_uri
 * @param scope {Array|String} Array or space delimetered string of scopes.
 * @param callback
 */
Authorization.prototype.authorize = function(user, client, redirectUri, scope, callback) {
  var self = this;
  if(!Array.isArray(scope)) {
    if(typeof scope == "object") {
      scope = Object.keys(scope);
    } else if(typeof scope == "string") {
      scope = scope.join(' ');
    }
  }

  this.loadGrantForUser(user, client, function(err, grant) {
    if(err) {
      return callback(err, null);
    }
    if(!grant) {
      self.createGrant(user, client, redirectUri, scope, function(err, grant) {
        if(err) {
          return callback(err, null);
        }
        self.saveGrant(grant, function(err, grant) {
          return callback(err, grant);
        });
      });
    } else {
      if(typeof grant.scope == "string") {
        grant.scope = grant.scope.split(' ');
      }
      var allScopesAuthorized = scope.every(function(key) {
        return grant.scope.indexOf(key) != -1;
      });

      if(redirectUri != grant.redirect_uri) {
        grant.redirect_uri = redirectUri;
      }

      if(!allScopesAuthorized) {
        grant.scope = scope;
        self.saveGrant(grant, function(err, d) {
          return callback(err, grant);
        });
      } else {
        // Old grant but each authorization needs its own code as they are single use.
        self.refreshCode(grant, function(err, grant) {
          if(err) {
            return errors.code.server_error(req, res);
          }
          return callback(null, grant);
        });
      }
    }
  });
};

/**
 * validateCode
 *
 * verifies authorization_code, makes sure its not expired, that redirect_uri and client_id matches etc.
 *
 * @param code
 * @param user
 * @param client
 * @param redirectUri
 * @param callback
 */
Authorization.prototype.validateCode = function(code, client, redirectUri, user, callback) {
  var self = this;
  if(callback == undefined) {
    callback = user;
    user = undefined;
  }
  if(typeof client == "object") {
    client = client.client_id;
  }

  function validate(codeData) {
    var now, expiresAt;
    /**
     * The authorization code is bound to the client identifier and redirection URI.
     */
    if(redirectUri != codeData.redirect_uri) {
      console.error("redirect uri missmatch", redirectUri, codeData.redirect_uri);
      return callback(null, false);
    }
    if(client != codeData.client_id) {
      console.error("client_id  missmatch", client, codeData.client_id);
      return callback(null, false);
    }
    // optional check if user is not undefined
    if(user && user.id != codeData.user_id) {
      return callback(null, false);
    }

    now = Math.floor(Date.now()/1000);
    expiresAt = Number(codeData.created_at) + AUTHORIZATION_CODE_TTL;
    // is it expired.
    if(now > expiresAt) {
      return callback(null, false);
    }
    return callback(null, true);
  }

  if(typeof code == 'string') {
    self.loadCode(code, function(err, codeData) {
      if(err || !codeData) {
        return callback(err, codeData);
      }
      return validate(codeData);
    });
  } else {
    return validate(code);
  }
};

module.exports = Authorization;
<commit_msg>Update authorization.js<commit_after>/*!
 * a2 suite
 * Copyright(c) 2012 Matti Savolainen <matti@applifier.com>
 * MIT Licensed
 */

/**
 * Module Dependencies
 */
var store = require('../store')
  , serializer = require('serializer')
  , errors = require('../errors')
  , verify = require('../verify');

/**
 * constants
 */

/**
 * http://tools.ietf.org/html/rfc6749#section-4.1.2
 * A maximum authorization code lifetime of 10 minutes is RECOMMENDED
 */
var AUTHORIZATION_CODE_TTL = 600;

/**
 * Authorization
 *
 * @param server  `Server` instance
 * @param options
 *
 * Options:
 *  `scope`   Array of valid scopes. defaults to ['basic']
 *  `store`   The store to be used, see store dir.
 *
 */
function Authorization(server, options) {
  options = options || {};
  this.store = server.store.namespace("authorization");
  this.sserializer = server.serializer;
  this.scopes = options.scopes || {'': "Read your basic info","basic": "Read and write your information"};
  this.server = server;
  this.validScopes = Object.keys(this.scopes);
  this.secureSerializer = server.serializer;
  server.on('check_scope', this.checkScope.bind(this));
  server.on('authorize_form', this.renderAuthorizeForm.bind(this));
}

/**
 * revokeTokensForCode
 *
 * revokes refresh tokens and access tokens issued with the code.
 *
 * @param code
 * @param callback
 */
Authorization.prototype.revokeTokensForCode = function(grant, callback) {
  var self = this;
  delete grant.access_token;
  var refreshTokenKey = 'refresh_token:'+grant.refresh_token;
  delete grant.refresh_token;
  delete grant.code;
  delete grant.used;
  self.store.del(refreshTokenKey, function(err) {
    if(err) {
      return callback(err, null);
    }
    self.saveGrant(grant, callback);
  });
};

/**
 * refreshCode
 *
 * Each authorization request gets it own code, even if a grant exists a new one will be generated and .used removed.
 *
 * @param grant
 * @param callback
 */
Authorization.prototype.refreshCode = function(grant, callback) {
  var self = this;
  self.createCode(grant.user_id, grant.client_id, grant.redirect_uri, grant.scope,  function(err, code) {
    if(err) {
      return errors.code.server_error(req, res);
    }
    grant.used = false;
    grant.code = code;
    self.saveGrant(grant, callback);
  });
};

/**
 * Checks that the requested scopes are valid and if new scopes are provided permissions are asked.
 *
 * @param user
 * @param client
 * @param scope
 * @param callback
 * @return {*}
 */
Authorization.prototype.checkScope = function(req, res, user, client, scope, callback) {
  var self = this
    , unauthorized =  []
    , authorized = []
    , scopeObj = {};
  var a2 = req.a2;
  if(!scope) {
    scope = [''];
  }
  // get stored authorization for this client/user pair.
  if(!Array.isArray(scope)) {
    scope = scope.split(' ');
  }
  console.log("Loading existing grant");
  this.loadGrantForUser(user, client, function(error, grant) {
    if(error) {
      return errors.server_error(req, res);
    }
    console.log("Grant found", grant);
    if(grant && grant.scope != null) {
      authorized = grant.scope;
      scope.filter(function(s) {
        return self.scopes[s] === undefined;
      });

      scope.forEach(function(s) {
        if(authorized.indexOf(s) == -1) {
          unauthorized.push(s);
        }
      });

      for(var i in unauthorized) {
        if(unauthorized.hasOwnProperty(i)) {
          scopeObj[unauthorized[i]] = self.scopes[unauthorized[i]];
        }
      }
      return callback(null, scopeObj);
    } else {
      scope.forEach(function(s) {
        scopeObj[s] = self.scopes[s];
      });
      return callback(null, scopeObj);
    }
  });
};

/**
 * User supplied login form function. If not specified will use default one.
 * @param callback
 */
Authorization.prototype.authorizeForm = function(callback) {
  this.server.removeAllListeners('authorize_form');
  this.server.on('authorize_form', callback.bind(this));
};

/**
 * Default login form, throws as authorizeForm has to be implemented.
 */
Authorization.prototype.renderAuthorizeForm = function(req, res, scope) {
  res.render(this.server.options.authorize_template, {
    authorize_endpoint: this.server.options.authorize_endpoint,
    state:req.a2.state,
    scope:scope,
    redirect_uri: req.a2.client.redirect_uri,
    game: req.a2.client.name,
    response_type: req.query.response_type,
    client_id: req.a2.client.client_id
  });
};

/**
 * createCode
 *
 * Creates a signed & verifiable authorization code.
 *
 * @param user {Object/String} user with .id or string id
 * @param client {Object/String} client with .id or string id
 * @param redirectUri {String} the redirect_uri
 * @param scopes {Array/String} Array of scopes or string separated by space.
 * @param callback {Function} the callback function to be invoked on completion.
 * @return {String} the authorization code.
 */
Authorization.prototype.createCode = function(user, client, redirectUri, scope, callback) {
  var code, clientId, userId;
  if(typeof user == "object") {
    userId = user.id;
  } else {
    userId = user;
  }

  if(typeof client == "object") {
    clientId = client.client_id;
  } else {
    clientId = client;
  }

  if(Array.isArray(scope)) {
    scope = scope.join(' ');
  }

  var data = [clientId, userId, redirectUri, scope, Math.floor(Date.now()/1000)];
  code = this.sserializer.stringify(data);
  if(callback) {
    callback(null, code);
  }
  return code;
};

/**
 * loadCode
 *
 * Loads user_id, client_id, scopes and creation time from a provided authorization code.
 *
 * @param code {String} the encrypted authorization code.
 * @param callback {Function} callback function
 * @return {Object} object containing user_id, client_id, scopes. created_at or null if invalid code.
 */
Authorization.prototype.loadCode = function(code, callback) {
  var obj, info =  this.sserializer.parse(code);
  if(!info) {
    return callback(null, null);
  }
  console.log(code, info);
  obj = {
      client_id: info[0]
    , user_id: info[1]
    , redirect_uri: info[2]
    , scope: info[3].split(' ')
    , created_at: info[4]
  };

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

/**
 * loadGrant
 *
 * loads a grant from database with the authorization code and verifies the data matches the code.
 *
 * @param code
 * @param callback
 */
Authorization.prototype.loadGrant = function(code, callback) {
  var self = this;
  self.loadCode(code, function(err, codeGrant) {
    console.log("code",code,"loaded as ",codeGrant);
    self.store.get(code, function(err, key) {
      if(err || !key) {
        return callback(err, key);
      }
      self.store.get(key, function(err, grant) {
        if(err || !grant) {
          return callback(err, grant);
        }
        grant = JSON.parse(grant);
        console.log("Grant is:",grant);
        if(grant.client_id == codeGrant.client_id && grant.user_id == codeGrant.user_id) {
          return callback(null, grant);
        } else {
          console.warn("Grant code and database missmatch!",grant, codeGrant);
          return callback(null, null);
        }
      });
    });
  });
};

/**
 * Saves a grant to the database with user_id:client_id and authorization:code
 * @param grant {Object} grant object
 * @param callback
 */
Authorization.prototype.saveGrant = function(grant, callback) {
  var self = this;
  if(!grant.key) {
    grant.key = 'Grant:'+grant.client_id + ':' + grant.user_id;
  }
  this.store.set(grant.key, JSON.stringify(grant), function(err, gra) {
    self.store.set(grant.code, grant.key, function(err, gra) { // index the grant with code also.
      // RFC says RECOMMENDED max lifetime for authorization code is 10 mins.
      self.store.expire(grant.code, 10*60, function(err) {
        return callback(err, grant);
      });
    });
  });
};

/**
 * createGrant
 *
 * creates a new grant and an authorization_code for the grant.
 *
 * @param user {Object} user object with atleast .id
 * @param client {Object} user object with atleast .id
 * @param scopes {Array} array of scopes.
 * @param callback
 * @return {*}
 */
Authorization.prototype.createGrant = function(user, client, redirectUri, scope, callback) {
  if(typeof client == "object") {
    client = client.client_id
  }
  if(typeof user == "object") {
    user = user.id;
  }

  var grant = {
      user_id: user
    , client_id: client
    , scope: scope
    , redirect_uri: redirectUri
    , code: this.createCode(user, client, redirectUri, scope)
  };
  return callback(null, grant);
};

/**
 * loadGrantForUser
 *
 * looks for an existing grant for client_id user_id pair.
 *
 * @param user {Object|String} user object/id
 * @param client {Object|String} client object/id
 * @param callback
 */
Authorization.prototype.loadGrantForUser = function(user, client, callback) {
  var self = this;
  if(typeof client == "object") {
    client = client.client_id;
  }
  if(typeof user == "object") {
    user = user.id;
  }

  self.store.get('Grant:'+client+':'+user, function(err, grant) {
    if(grant) {
      grant = JSON.parse(grant);
    }
    return callback(err, grant);
  });
};

/**
 * authorize
 *
 * Authorizes client to user with scope.
 *
 *
 * @param user {Object|String} user object/id
 * @param client {Object|String} client object/id
 * @param redirectUri {String} redirect_uri
 * @param scope {Array|String} Array or space delimetered string of scopes.
 * @param callback
 */
Authorization.prototype.authorize = function(user, client, redirectUri, scope, callback) {
  var self = this;
  if(!Array.isArray(scope)) {
    if(typeof scope == "object") {
      scope = Object.keys(scope);
    } else if(typeof scope == "string") {
      scope = scope.join(' ');
    }
  }

  this.loadGrantForUser(user, client, function(err, grant) {
    if(err) {
      return callback(err, null);
    }
    if(!grant) {
      self.createGrant(user, client, redirectUri, scope, function(err, grant) {
        if(err) {
          return callback(err, null);
        }
        self.saveGrant(grant, function(err, grant) {
          return callback(err, grant);
        });
      });
    } else {
      if(typeof grant.scope == "string") {
        grant.scope = grant.scope.split(' ');
      } else if(!grant.scope) {
        grant.scope = ['basic'];
      }

      var allScopesAuthorized = scope.every(function(key) {
        return grant.scope.indexOf(key) != -1;
      });

      if(redirectUri != grant.redirect_uri) {
        grant.redirect_uri = redirectUri;
      }

      if(!allScopesAuthorized) {
        grant.scope = scope;
        self.saveGrant(grant, function(err, d) {
          return callback(err, grant);
        });
      } else {
        // Old grant but each authorization needs its own code as they are single use.
        self.refreshCode(grant, function(err, grant) {
          if(err) {
            return errors.code.server_error(req, res);
          }
          return callback(null, grant);
        });
      }
    }
  });
};

/**
 * validateCode
 *
 * verifies authorization_code, makes sure its not expired, that redirect_uri and client_id matches etc.
 *
 * @param code
 * @param user
 * @param client
 * @param redirectUri
 * @param callback
 */
Authorization.prototype.validateCode = function(code, client, redirectUri, user, callback) {
  var self = this;
  if(callback == undefined) {
    callback = user;
    user = undefined;
  }
  if(typeof client == "object") {
    client = client.client_id;
  }

  function validate(codeData) {
    var now, expiresAt;
    /**
     * The authorization code is bound to the client identifier and redirection URI.
     */
    if(redirectUri != codeData.redirect_uri) {
      console.error("redirect uri missmatch", redirectUri, codeData.redirect_uri);
      return callback(null, false);
    }
    if(client != codeData.client_id) {
      console.error("client_id  missmatch", client, codeData.client_id);
      return callback(null, false);
    }
    // optional check if user is not undefined
    if(user && user.id != codeData.user_id) {
      return callback(null, false);
    }

    now = Math.floor(Date.now()/1000);
    expiresAt = Number(codeData.created_at) + AUTHORIZATION_CODE_TTL;
    // is it expired.
    if(now > expiresAt) {
      return callback(null, false);
    }
    return callback(null, true);
  }

  if(typeof code == 'string') {
    self.loadCode(code, function(err, codeData) {
      if(err || !codeData) {
        return callback(err, codeData);
      }
      return validate(codeData);
    });
  } else {
    return validate(code);
  }
};

module.exports = Authorization;
<|endoftext|>