<commit_before>// Log view for the W433.
//
// Our model is a collection of Logs
//
// (c) 2014 Edouard Lafargue, ed@lafargue.name

define(function(require) {
    "use strict";

    var $       = require('jquery'),
        _       = require('underscore'),
        Backbone = require('backbone'),
        tpl     = require('text!tpl/instruments/W433LogView.html'),
                template = null;

        try {
            template =  _.template(tpl);
        } catch (e) {
            // Will happen if we are packaged in a Chrome app
            console.log('W433Settings View: using compiled version (chrome app)');
            template = require('js/tpl/instruments/W433LogView.js', function(){} , function(err) {
                            console.log("Compiled JS preloading error callback.");
                            });
        }

    // Load the flot library & flot time plugin:
    require('flot');
    require('flot_time');

    return  Backbone.View.extend({

        initialize:function () {
            var self = this;

            this.deviceLogs = this.collection;
            this.packedData = null;

            // Need this to make sure "render" will always be bound to our context.
            // -> required for the _.after below.
            _.bindAll(this,"render");

            // Now fetch all the contents, then render
            var renderGraph = _.after(this.deviceLogs.length, this.render);
            this.deviceLogs.each(function(log) {
                log.entries.fetch({success: renderGraph,
                                  xhr: function() {
                                        var xhr = $.ajaxSettings.xhr();
                                        xhr.onprogress = self.handleProgress;
                                        return xhr;
                                    }});
            });

            // TODO: save color palette in settings ?
            // My own nice color palette:
            this.palette = ["#e27c48", "#5a3037", "#f1ca4f", "#acbe80", "#77b1a7", "#858485", "#d9c7ad" ];

            this.plotOptions = {
                xaxis: { mode: "time", show:true,
                        timezone: settings.get("timezone"),
                       },
                grid: {
                    hoverable: true,
                    clickable: true
                },
                legend: { position: "ne" },
                selection: {
                    mode: "xy"
                },

                colors: this.palette,
            };

            this.overviewOptions = {
                legend: { show: false },
                xaxis: { mode: "time", show:false, ticks:4 },
                yaxis: { ticks:4 },
                selection: { mode: "xy" },
                colors: this.palette,
            };

            this.previousPoint = null;

        },

        handleProgress: function(e) {
                $('#loadtext').html("Loaded: " + e.loaded + " bytes");
        },

        events: {
            "click .resetZoom": "resetZoom",
        },

        resetZoom: function() {
            delete this.ranges;
            this.addPlot();
            return false;
        },

        render:function () {
            var self = this;
            console.log('Main render of Log management view');

            $(this.el).html(template());

            if (this.packedData == null || this.packedData.length==0)
                this.packedData = this.packData();

            if (this.packedData.length == 0)
                return;

            this.addPlot();

            return this;
        },

        onClose: function() {
            console.log("Log management view closing...");

        },

        // Generate a "blob:"  URL to download (all) the data;
        saveDataUrl: function() {
            return;
        },

        // We can only add the plot once the view has finished rendering and its el is
        // attached to the DOM, so this function has to be called from the home view.
        addPlot: function() {
            var self=this;

            if (this.deviceLogs == null || this.deviceLogs.length == 0)
                return;

            $('#log_size',this.el).html(this.deviceLogs.getOverallLength());
            $('#log_start',this.el).html(new Date(this.deviceLogs.getLogsStart()).toString());
            $('#log_end',this.el).html(new Date(this.deviceLogs.getLogsEnd()).toString());

            // Restore current zoom level if it exists:
            if (this.ranges) {
                this.plot = $.plot($(".locochart",this.el), this.packedData,
                    $.extend(true, {}, this.plotOptions, {
                        xaxis: { min: this.ranges.xaxis.from, max: this.ranges.xaxis.to },
                        yaxis: { min: this.ranges.yaxis.from, max: this.ranges.yaxis.to }
                    })
                 );

            } else {
                this.plot = $.plot($(".locochart", this.el), this.packedData, this.plotOptions);
            };

            $(".locochart", this.el).bind("plothover", function (event, pos, item) {
                if (item) {
                    if (this.previousPoint != item.dataIndex) {
                        this.previousPoint = item.dataIndex;

                        $("#tooltip").remove();
                        var x = item.datapoint[0],
                        y = item.datapoint[1];

                        self.showTooltip(item.pageX, item.pageY,
                            "" + ((settings.get('timezone') === 'UTC') ?
                                            new Date(x).toUTCString() :
                                            new Date(x).toString()) + "
" + item.series.label + ": " + y + "");
                    }
                } else {
                    $("#tooltip").remove();
                    this.previousPoint = null;
                }
            });

            // Create the overview chart:
            this.overview = $.plot($("#overview",this.el), this.packedData, this.overviewOptions);

            // Connect overview and main charts
            $(".locochart",this.el).bind("plotselected", function (event, ranges) {

                // clamp the zooming to prevent eternal zoom

                if (ranges.xaxis.to - ranges.xaxis.from < 0.00001) {
                    ranges.xaxis.to = ranges.xaxis.from + 0.00001;
                }

                if (ranges.yaxis.to - ranges.yaxis.from < 0.00001) {
                    ranges.yaxis.to = ranges.yaxis.from + 0.00001;
                }

                // Save the current range so that switching plot scale (log/linear)
                // can preserve the zoom level:
                self.ranges = ranges;

                // do the zooming
                this.plot = $.plot($(".locochart",this.el), self.packedData,
                    $.extend(true, {}, self.plotOptions, {
                        xaxis: { min: ranges.xaxis.from, max: ranges.xaxis.to },
                        yaxis: { min: ranges.yaxis.from, max: ranges.yaxis.to }
                    })
                 );

                // don't fire event on the overview to prevent eternal loop
                self.overview.setSelection(ranges, true);
            });

            $("#overview",this.el).bind("plotselected", function (event, ranges) {
                 self.plot.setSelection(ranges);
              });

            // Last, update the save data URL to point to the data we just displayed:
            this.saveDataUrl();

        },

        // Depending on log type, we need to pack our data differently...
        packData: function() {
            var self=this;
            // Create a table of Y values with the x values from our collection
            var sensorData = [];
            var sensors = [];
            var logs = this.deviceLogs;
            // At this stage we know the logs are already fetched, btw.
            for (var j=0; j<logs.length; j++) {
                var value = logs.at(j).entries;
                var type = logs.at(j).get('logtype');
                for (var i=0; i < value.length; i++) {
                    var data = value.at(i).get('data');
                    // Now add the current sensor
                    // TODO: upon release, just remove support for non-named sensors
                    var sensor = ((data.sensor_name == undefined) ? data.sensor_address : data.sensor_name) + " - " + data.reading_type;
                    if (sensors.indexOf(sensor) == -1) {
                        sensors.push(sensor);
                        sensorData.push([]);
                    }
                    var idx = sensors.indexOf(sensor);
                    sensorData[idx].push([new Date(value.at(i).get('timestamp')).getTime(), parseFloat(data.value)]);
                }
            }

            var plotData = [];
            // Now pack our live data:
            for (var i = 0; i < sensors.length; i++) {
                plotData.push( { data: sensorData[i],
                                                label: sensors[i]} );

            }
            return plotData;
        },

        // Ugly at this stage, just to make it work (from flotcharts.org examples)
        showTooltip: function (x, y, contents) {
                $("
" + contents + "
").css({
                    position: "absolute",
                    display: "none",
                    top: y + 5,
                    left: x + 5,
                    padding: "3px",
                    opacity: 0.90
                }).appendTo("body").fadeIn(200);
        },

    });
});<commit_msg>Add Flot selection plugin in W433, was preventing zooming<commit_after>// Log view for the W433.
//
// Our model is a collection of Logs
//
// (c) 2014 Edouard Lafargue, ed@lafargue.name

define(function(require) {
    "use strict";

    var $       = require('jquery'),
        _       = require('underscore'),
        Backbone = require('backbone'),
        tpl     = require('text!tpl/instruments/W433LogView.html'),
                template = null;

        try {
            template =  _.template(tpl);
        } catch (e) {
            // Will happen if we are packaged in a Chrome app
            console.log('W433Settings View: using compiled version (chrome app)');
            template = require('js/tpl/instruments/W433LogView.js', function(){} , function(err) {
                            console.log("Compiled JS preloading error callback.");
                            });
        }

    // Load the flot library & flot time plugin:
    require('flot');
    require('flot_time');
    require('flot_selection');

    return  Backbone.View.extend({

        initialize:function () {
            var self = this;

            this.deviceLogs = this.collection;
            this.packedData = null;

            // Need this to make sure "render" will always be bound to our context.
            // -> required for the _.after below.
            _.bindAll(this,"render");

            // Now fetch all the contents, then render
            var renderGraph = _.after(this.deviceLogs.length, this.render);
            this.deviceLogs.each(function(log) {
                log.entries.fetch({success: renderGraph,
                                  xhr: function() {
                                        var xhr = $.ajaxSettings.xhr();
                                        xhr.onprogress = self.handleProgress;
                                        return xhr;
                                    }});
            });

            // TODO: save color palette in settings ?
            // My own nice color palette:
            this.palette = ["#e27c48", "#5a3037", "#f1ca4f", "#acbe80", "#77b1a7", "#858485", "#d9c7ad" ];

            this.plotOptions = {
                xaxis: { mode: "time", show:true,
                        timezone: settings.get("timezone"),
                       },
                grid: {
                    hoverable: true,
                    clickable: true
                },
                legend: { position: "ne" },
                selection: {
                    mode: "xy"
                },

                colors: this.palette,
            };

            this.overviewOptions = {
                legend: { show: false },
                xaxis: { mode: "time", show:false, ticks:4 },
                yaxis: { ticks:4 },
                selection: { mode: "xy" },
                colors: this.palette,
            };

            this.previousPoint = null;

        },

        handleProgress: function(e) {
                $('#loadtext').html("Loaded: " + e.loaded + " bytes");
        },

        events: {
            "click .resetZoom": "resetZoom",
        },

        resetZoom: function() {
            delete this.ranges;
            this.addPlot();
            return false;
        },

        render:function () {
            var self = this;
            console.log('Main render of Log management view');

            $(this.el).html(template());

            if (this.packedData == null || this.packedData.length==0)
                this.packedData = this.packData();

            if (this.packedData.length == 0)
                return;

            this.addPlot();

            return this;
        },

        onClose: function() {
            console.log("Log management view closing...");

        },

        // Generate a "blob:"  URL to download (all) the data;
        saveDataUrl: function() {
            return;
        },

        // We can only add the plot once the view has finished rendering and its el is
        // attached to the DOM, so this function has to be called from the home view.
        addPlot: function() {
            var self=this;

            if (this.deviceLogs == null || this.deviceLogs.length == 0)
                return;

            $('#log_size',this.el).html(this.deviceLogs.getOverallLength());
            $('#log_start',this.el).html(new Date(this.deviceLogs.getLogsStart()).toString());
            $('#log_end',this.el).html(new Date(this.deviceLogs.getLogsEnd()).toString());

            // Restore current zoom level if it exists:
            if (this.ranges) {
                this.plot = $.plot($(".locochart",this.el), this.packedData,
                    $.extend(true, {}, this.plotOptions, {
                        xaxis: { min: this.ranges.xaxis.from, max: this.ranges.xaxis.to },
                        yaxis: { min: this.ranges.yaxis.from, max: this.ranges.yaxis.to }
                    })
                 );

            } else {
                this.plot = $.plot($(".locochart", this.el), this.packedData, this.plotOptions);
            };

            $(".locochart", this.el).bind("plothover", function (event, pos, item) {
                if (item) {
                    if (this.previousPoint != item.dataIndex) {
                        this.previousPoint = item.dataIndex;

                        $("#tooltip").remove();
                        var x = item.datapoint[0],
                        y = item.datapoint[1];

                        self.showTooltip(item.pageX, item.pageY,
                            "" + ((settings.get('timezone') === 'UTC') ?
                                            new Date(x).toUTCString() :
                                            new Date(x).toString()) + "
" + item.series.label + ": " + y + "");
                    }
                } else {
                    $("#tooltip").remove();
                    this.previousPoint = null;
                }
            });

            // Create the overview chart:
            this.overview = $.plot($("#overview",this.el), this.packedData, this.overviewOptions);

            // Connect overview and main charts
            $(".locochart",this.el).bind("plotselected", function (event, ranges) {

                // clamp the zooming to prevent eternal zoom

                if (ranges.xaxis.to - ranges.xaxis.from < 0.00001) {
                    ranges.xaxis.to = ranges.xaxis.from + 0.00001;
                }

                if (ranges.yaxis.to - ranges.yaxis.from < 0.00001) {
                    ranges.yaxis.to = ranges.yaxis.from + 0.00001;
                }

                // Save the current range so that switching plot scale (log/linear)
                // can preserve the zoom level:
                self.ranges = ranges;

                // do the zooming
                this.plot = $.plot($(".locochart",this.el), self.packedData,
                    $.extend(true, {}, self.plotOptions, {
                        xaxis: { min: ranges.xaxis.from, max: ranges.xaxis.to },
                        yaxis: { min: ranges.yaxis.from, max: ranges.yaxis.to }
                    })
                 );

                // don't fire event on the overview to prevent eternal loop
                self.overview.setSelection(ranges, true);
            });

            $("#overview",this.el).bind("plotselected", function (event, ranges) {
                 self.plot.setSelection(ranges);
              });

            // Last, update the save data URL to point to the data we just displayed:
            this.saveDataUrl();

        },

        // Depending on log type, we need to pack our data differently...
        packData: function() {
            var self=this;
            // Create a table of Y values with the x values from our collection
            var sensorData = [];
            var sensors = [];
            var logs = this.deviceLogs;
            // At this stage we know the logs are already fetched, btw.
            for (var j=0; j<logs.length; j++) {
                var value = logs.at(j).entries;
                var type = logs.at(j).get('logtype');
                for (var i=0; i < value.length; i++) {
                    var data = value.at(i).get('data');
                    // Now add the current sensor
                    // TODO: upon release, just remove support for non-named sensors
                    var sensor = ((data.sensor_name == undefined) ? data.sensor_address : data.sensor_name) + " - " + data.reading_type;
                    if (sensors.indexOf(sensor) == -1) {
                        sensors.push(sensor);
                        sensorData.push([]);
                    }
                    var idx = sensors.indexOf(sensor);
                    sensorData[idx].push([new Date(value.at(i).get('timestamp')).getTime(), parseFloat(data.value)]);
                }
            }

            var plotData = [];
            // Now pack our live data:
            for (var i = 0; i < sensors.length; i++) {
                plotData.push( { data: sensorData[i],
                                                label: sensors[i]} );

            }
            return plotData;
        },

        // Ugly at this stage, just to make it work (from flotcharts.org examples)
        showTooltip: function (x, y, contents) {
                $("
" + contents + "
").css({
                    position: "absolute",
                    display: "none",
                    top: y + 5,
                    left: x + 5,
                    padding: "3px",
                    opacity: 0.90
                }).appendTo("body").fadeIn(200);
        },

    });
});<|endoftext|>


<commit_before>const mongoose = require('mongoose');
const Schema = mongoose.Schema

var userSchema = new Schema({
    name: {type: String, required: true},
    photo: {type: String, required: true},
    email: {type: String, required: true, unique: true},
    fbId: {type: String},
    fbAccessToken: {type: String},
    googleId: {type: String},
    createdPins: [{type: Schema.ObjectId, ref:'locations'}],
    savedPins: [{type: Schema.ObjectId, ref: 'locations'}],

});

var User = mongoose.model('User', userSchema)

module.exports = User
<commit_msg>Adjust user model to populate locations correctly<commit_after>const mongoose = require('mongoose');
const Schema = mongoose.Schema

var userSchema = new Schema({
    name: {type: String, required: true},
    photo: {type: String, required: true},
    email: {type: String, required: true, unique: true},
    fbId: {type: String},
    fbAccessToken: {type: String},
    googleId: {type: String},
    createdPins: [{type: Schema.ObjectId, ref:'Location'}],
    savedPins: [{type: Schema.ObjectId, ref: 'Location'}],

});

var User = mongoose.model('User', userSchema)

module.exports = User
<|endoftext|>


<commit_before>ManageIQ.angular.app.controller('scheduleFormController', ['$http', '$scope', 'scheduleFormId', 'oneMonthAgo', 'miqService', 'timerOptionService', function($http, $scope, scheduleFormId, oneMonthAgo, miqService, timerOptionService) {
  var init = function() {

    $scope.scheduleModel = {
      action_typ: '',
      depot_name: '',
      filter_typ: '',
      log_userid: '',
      log_protocol: '',
      description: '',
      enabled: '',
      name: '',
      timer_typ: '',
      timer_value: '',
      start_date: null,
      start_hour: '',
      start_min: '',
      time_zone: '',
      uri: '',
      uri_prefix: '',
      filter_value: ''
    };
    $scope.date_from = null;
    $scope.formId = scheduleFormId;
    $scope.afterGet = false;
    $scope.validateClicked = miqService.validateWithAjax;
    $scope.modelCopy = angular.copy( $scope.scheduleModel );

    ManageIQ.angular.scope = $scope;

    if (scheduleFormId == 'new') {
      $scope.newRecord                = true;
      $scope.scheduleModel.action_typ = 'vm';
      $scope.scheduleModel.filter_typ = 'all';
      $scope.scheduleModel.enabled    = true;
      $scope.filterValuesEmpty        = true;
      $scope.scheduleModel.start_date = moment(moment.utc().toDate()).format('MM/DD/YYYY');
      $scope.scheduleModel.timer_typ  = 'Once';
      $scope.scheduleModel.time_zone  = 'UTC';
      $scope.scheduleModel.start_hour = '0';
      $scope.scheduleModel.start_min  = '0';
      $scope.afterGet                 = true;
      $scope.modelCopy                = angular.copy( $scope.scheduleModel );
      $scope.setTimerType();
      $scope.date_from = new Date();
    } else {
      $scope.newRecord = false;

      miqService.sparkleOn();

      $http.get('/ops/schedule_form_fields/' + scheduleFormId).success(function(data) {
        $scope.scheduleModel.action_typ   = data.action_type;
        $scope.scheduleModel.depot_name   = data.depot_name;
        $scope.scheduleModel.filter_typ   = data.filter_type;
        $scope.scheduleModel.log_userid   = data.log_userid;
        $scope.scheduleModel.log_protocol = data.protocol;
        $scope.scheduleModel.description  = data.schedule_description;
        $scope.scheduleModel.enabled      = data.schedule_enabled == "1" ? true : false;
        $scope.scheduleModel.name         = data.schedule_name;
        $scope.scheduleModel.timer_typ    = data.schedule_timer_type;
        $scope.scheduleModel.timer_value  = data.schedule_timer_value;
        $scope.scheduleModel.start_date   = data.schedule_start_date;
        $scope.scheduleModel.start_hour   = data.schedule_start_hour.toString();
        $scope.scheduleModel.start_min    = data.schedule_start_min.toString();
        $scope.scheduleModel.time_zone    = data.schedule_time_zone;
        $scope.scheduleModel.uri          = data.uri;
        $scope.scheduleModel.uri_prefix   = data.uri_prefix;

        $scope.setTimerType();

        $scope.timer_items        = timerOptionService.getOptions($scope.scheduleModel.timer_typ);

        if (data.filter_type === 'all' || (data.protocol !== undefined && data.protocol !== null)) {
          $scope.filterValuesEmpty = true;
        } else {
          buildFilterList(data);

          $scope.filterValuesEmpty = false;
          $scope.scheduleModel.filter_value     = data.filter_value;
        }

        if(data.filter_type == null &&
          (data.protocol !== undefined && data.protocol !== null && data.protocol != 'Samba'))
          $scope.scheduleModel.filter_typ = 'all';

        $scope.scheduleModel.log_password = $scope.scheduleModel.log_verify = "";
        if($scope.scheduleModel.log_userid != '') {
          $scope.scheduleModel.log_password = $scope.scheduleModel.log_verify = miqService.storedPasswordPlaceholder;
        }

        $scope.afterGet = true;
        $scope.modelCopy = angular.copy( $scope.scheduleModel );

        miqService.sparkleOff();
      });
    }

    miqService.buildCalendar(oneMonthAgo.year, parseInt(oneMonthAgo.month) + 1, oneMonthAgo.date);

    $scope.$watch("scheduleModel.name", function() {
      $scope.form = $scope.angularForm;
      $scope.model = "scheduleModel";
    });
  };

  var buildFilterList = function(data) {
    $scope.filterList = [];
    angular.forEach(data.filtered_item_list, function(filteredItem) {
      var tempObj = {};

      if (Object.prototype.toString.call(filteredItem) === '[object Array]') {
        tempObj.text = filteredItem[1];
        tempObj.value = filteredItem[0];
      } else {
        tempObj.text = filteredItem;
        tempObj.value = filteredItem;
      }

      $scope.filterList.push(tempObj);
    });
  };

  var testType = function(type) {
    return type.test($scope.scheduleModel.action_typ);
  };

  var isVmType = function() {
    return testType(/^vm/);
  };

  var isHostType = function() {
    return testType(/^host/);
  };

  var scheduleEditButtonClicked = function(buttonName, serializeFields) {
    miqService.sparkleOn();
    var url = '/ops/schedule_edit/' + scheduleFormId + '?button=' + buttonName;
    if (serializeFields === undefined) {
      miqService.miqAjaxButton(url);
    } else {
      miqService.miqAjaxButton(url, serializeFields);
    }
  };

  $scope.buildLegend = function() {
    var type;

    if (isVmType()) {
      type = __('VM Selection');
    } else if (isHostType()) {
      type = __('Host Selection');
    } else if ($scope.scheduleModel.action_typ === 'miq_template') {
      type = __('Template Selection');
    } else if ($scope.scheduleModel.action_typ === 'emscluster') {
      type = __('Cluster Selection');
    } else if ($scope.scheduleModel.action_typ === 'storage') {
      type = __('Datastore Selection');
    } else if ($scope.scheduleModel.action_typ === 'db_backup') {
      type = __('Database Backup Selection');
    }

    return type;
  };

  $scope.determineActionType = function() {
    if (isVmType()) {
      return 'vm';
    } else if (isHostType()) {
      return 'host';
    } else {
      return $scope.scheduleModel.action_typ;
    }
  };

  $scope.dbBackup = function() {
    return $scope.scheduleModel.action_typ === 'db_backup';
  };

  $scope.sambaBackup = function() {
    return $scope.dbBackup() && $scope.scheduleModel.log_protocol === 'Samba';
  };

  $scope.actionTypeChanged = function() {
    if ($scope.dbBackup()) {
      $scope.scheduleModel.log_protocol = 'Network File System';
      $scope.scheduleModel.uri_prefix = 'nfs';
      $scope.scheduleModel.filter_typ = null;
    } else {
      $scope.scheduleModel.filter_typ = 'all';
    }
    $scope.scheduleModel.filter_value = '';

    $scope.filterValuesEmpty = true;
  };

  $scope.filterTypeChanged = function() {
    if ($scope.scheduleModel.filter_typ != 'all') {
      miqService.sparkleOn();
      $http.post('/ops/schedule_form_filter_type_field_changed/' + scheduleFormId,
        {filter_type: $scope.scheduleModel.filter_typ,
         action_type: $scope.scheduleModel.action_typ}).success(function(data) {
        buildFilterList(data);
        $scope.filterValuesEmpty = false;
        miqService.sparkleOff();
      });
    } else {
      $scope.scheduleModel.filter_value = '';
      $scope.filterValuesEmpty = true;
    }
  };

  $scope.logProtocolChanged = function() {
    if ($scope.scheduleModel.log_protocol === "Samba") {
      $scope.scheduleModel.uri_prefix = "smb";
    }

    if ($scope.scheduleModel.log_protocol === "Network File System") {
      $scope.scheduleModel.uri_prefix = "nfs";
      $scope.$broadcast('resetClicked');
      $scope.scheduleModel.log_userid = $scope.modelCopy.log_userid;
      $scope.scheduleModel.log_password = $scope.scheduleModel.log_verify = $scope.modelCopy.log_password;
    }
  };

  $scope.filterValueChanged = function() {
  };

  $scope.scheduleTimerTypeChanged = function() {
    $scope.setTimerType();

    $scope.timer_items = timerOptionService.getOptions($scope.scheduleModel.timer_typ);

    if ($scope.timerNotOnce()) {
      $scope.scheduleModel.timer_value = 1;
    } else {
      $scope.scheduleModel.timer_value = 0;
    }
  };

  $scope.timerNotOnce = function() {
    return $scope.scheduleModel.timer_typ !== 'Once';
  };

  $scope.cancelClicked = function() {
    scheduleEditButtonClicked('cancel');
    $scope.angularForm.$setPristine(true);
  };

  $scope.resetClicked = function() {
    $scope.$broadcast('resetClicked');
    $scope.scheduleModel = angular.copy( $scope.modelCopy );
    if($scope.dbBackup())
        $scope.filterValuesEmpty = true;

      if(!$scope.dbBackup() && $scope.scheduleModel.filter_typ &&
          ($scope.form.action_typ.$untouched && $scope.form.filter_typ.$untouched)) {
        //AJAX-less Reset
        $scope.toggleValueForWatch('filterValuesEmpty', false);
      }

    if(!$scope.dbBackup() && $scope.scheduleModel.filter_typ &&
      ($scope.form.action_typ.$touched || $scope.form.filter_typ.$touched)) {
      $scope.filterTypeChanged();
    }
    if($scope.scheduleModel.timer_typ && $scope.form.timer_typ.$touched) {
      $scope.setTimerType();
      $scope.timer_items = timerOptionService.getOptions($scope.scheduleModel.timer_typ);
    }
    $scope.angularForm.$setUntouched(true);
    $scope.angularForm.$setPristine(true);
    miqService.miqFlash("warn", __("All changes have been reset"));
  };

  $scope.saveClicked = function() {
    scheduleEditButtonClicked('save', true);
    $scope.angularForm.$setPristine(true);
  };

  $scope.addClicked = function() {
    $scope.saveClicked();
  };

  $scope.filterValueRequired = function(value) {
    return !$scope.filterValuesEmpty &&
      ($scope.isModelValueNil(value));
  };

  $scope.dbRequired = function(value) {
    return $scope.dbBackup() &&
      ($scope.isModelValueNil(value));
  };

  $scope.sambaRequired = function(value) {
    return $scope.sambaBackup() &&
      ($scope.isModelValueNil(value));
  };

  $scope.isModelValueNil = function(value) {
    return value == null || value == '';
  };

  $scope.isBasicInfoValid = function() {
    if($scope.angularForm.depot_name.$valid &&
      $scope.angularForm.uri.$valid &&
      $scope.angularForm.log_userid.$valid &&
      $scope.angularForm.log_password.$valid &&
      $scope.angularForm.log_verify.$valid)
      return true;
    else
      return false;
  };

  $scope.setTimerType = function() {
    if ($scope.scheduleModel.timer_typ == "Once")
      $scope.timerTypeOnce = true;
    else {
      $scope.timerTypeOnce = false;
    }
  };

  $scope.toggleValueForWatch =   function(watchValue, initialValue) {
    if($scope[watchValue] == initialValue)
      $scope[watchValue] = "NO-OP";
    else if($scope[watchValue] == "NO-OP")
      $scope[watchValue] = initialValue;
  };

  $scope.canValidate = function () {
    if ($scope.isBasicInfoValid() && $scope.validateFieldsDirty())
      return true;
    else
      return false;
  }

  $scope.canValidateBasicInfo = function () {
    if ($scope.isBasicInfoValid())
      return true;
    else
      return false;
  }

  $scope.validateFieldsDirty = function () {
    if ($scope.angularForm.depot_name.$dirty ||
        $scope.angularForm.uri.$dirty ||
        $scope.angularForm.log_userid.$dirty ||
        $scope.angularForm.log_password.$dirty ||
        $scope.angularForm.log_verify.$dirty)
      return true;
    else
      return false;
  }

  init();
}]);

<commit_msg>Removed $watch from Schedule controller<commit_after>ManageIQ.angular.app.controller('scheduleFormController', ['$http', '$scope', 'scheduleFormId', 'oneMonthAgo', 'miqService', 'timerOptionService', function($http, $scope, scheduleFormId, oneMonthAgo, miqService, timerOptionService) {
  var init = function() {

    $scope.scheduleModel = {
      action_typ: '',
      depot_name: '',
      filter_typ: '',
      log_userid: '',
      log_protocol: '',
      description: '',
      enabled: '',
      name: '',
      timer_typ: '',
      timer_value: '',
      start_date: null,
      start_hour: '',
      start_min: '',
      time_zone: '',
      uri: '',
      uri_prefix: '',
      filter_value: ''
    };
    $scope.date_from = null;
    $scope.formId = scheduleFormId;
    $scope.afterGet = false;
    $scope.validateClicked = miqService.validateWithAjax;
    $scope.modelCopy = angular.copy( $scope.scheduleModel );
    $scope.model = "scheduleModel";

    ManageIQ.angular.scope = $scope;

    if (scheduleFormId == 'new') {
      $scope.newRecord                = true;
      $scope.scheduleModel.action_typ = 'vm';
      $scope.scheduleModel.filter_typ = 'all';
      $scope.scheduleModel.enabled    = true;
      $scope.filterValuesEmpty        = true;
      $scope.scheduleModel.start_date = moment(moment.utc().toDate()).format('MM/DD/YYYY');
      $scope.scheduleModel.timer_typ  = 'Once';
      $scope.scheduleModel.time_zone  = 'UTC';
      $scope.scheduleModel.start_hour = '0';
      $scope.scheduleModel.start_min  = '0';
      $scope.afterGet                 = true;
      $scope.modelCopy                = angular.copy( $scope.scheduleModel );
      $scope.setTimerType();
      $scope.date_from = new Date();
    } else {
      $scope.newRecord = false;

      miqService.sparkleOn();

      $http.get('/ops/schedule_form_fields/' + scheduleFormId).success(function(data) {
        $scope.scheduleModel.action_typ   = data.action_type;
        $scope.scheduleModel.depot_name   = data.depot_name;
        $scope.scheduleModel.filter_typ   = data.filter_type;
        $scope.scheduleModel.log_userid   = data.log_userid;
        $scope.scheduleModel.log_protocol = data.protocol;
        $scope.scheduleModel.description  = data.schedule_description;
        $scope.scheduleModel.enabled      = data.schedule_enabled == "1" ? true : false;
        $scope.scheduleModel.name         = data.schedule_name;
        $scope.scheduleModel.timer_typ    = data.schedule_timer_type;
        $scope.scheduleModel.timer_value  = data.schedule_timer_value;
        $scope.scheduleModel.start_date   = data.schedule_start_date;
        $scope.scheduleModel.start_hour   = data.schedule_start_hour.toString();
        $scope.scheduleModel.start_min    = data.schedule_start_min.toString();
        $scope.scheduleModel.time_zone    = data.schedule_time_zone;
        $scope.scheduleModel.uri          = data.uri;
        $scope.scheduleModel.uri_prefix   = data.uri_prefix;

        $scope.setTimerType();

        $scope.timer_items        = timerOptionService.getOptions($scope.scheduleModel.timer_typ);

        if (data.filter_type === 'all' || (data.protocol !== undefined && data.protocol !== null)) {
          $scope.filterValuesEmpty = true;
        } else {
          buildFilterList(data);

          $scope.filterValuesEmpty = false;
          $scope.scheduleModel.filter_value     = data.filter_value;
        }

        if(data.filter_type == null &&
          (data.protocol !== undefined && data.protocol !== null && data.protocol != 'Samba'))
          $scope.scheduleModel.filter_typ = 'all';

        $scope.scheduleModel.log_password = $scope.scheduleModel.log_verify = "";
        if($scope.scheduleModel.log_userid != '') {
          $scope.scheduleModel.log_password = $scope.scheduleModel.log_verify = miqService.storedPasswordPlaceholder;
        }

        $scope.afterGet = true;
        $scope.modelCopy = angular.copy( $scope.scheduleModel );

        miqService.sparkleOff();
      });
    }

    miqService.buildCalendar(oneMonthAgo.year, parseInt(oneMonthAgo.month) + 1, oneMonthAgo.date);
  };

  var buildFilterList = function(data) {
    $scope.filterList = [];
    angular.forEach(data.filtered_item_list, function(filteredItem) {
      var tempObj = {};

      if (Object.prototype.toString.call(filteredItem) === '[object Array]') {
        tempObj.text = filteredItem[1];
        tempObj.value = filteredItem[0];
      } else {
        tempObj.text = filteredItem;
        tempObj.value = filteredItem;
      }

      $scope.filterList.push(tempObj);
    });
  };

  var testType = function(type) {
    return type.test($scope.scheduleModel.action_typ);
  };

  var isVmType = function() {
    return testType(/^vm/);
  };

  var isHostType = function() {
    return testType(/^host/);
  };

  var scheduleEditButtonClicked = function(buttonName, serializeFields) {
    miqService.sparkleOn();
    var url = '/ops/schedule_edit/' + scheduleFormId + '?button=' + buttonName;
    if (serializeFields === undefined) {
      miqService.miqAjaxButton(url);
    } else {
      miqService.miqAjaxButton(url, serializeFields);
    }
  };

  $scope.buildLegend = function() {
    var type;

    if (isVmType()) {
      type = __('VM Selection');
    } else if (isHostType()) {
      type = __('Host Selection');
    } else if ($scope.scheduleModel.action_typ === 'miq_template') {
      type = __('Template Selection');
    } else if ($scope.scheduleModel.action_typ === 'emscluster') {
      type = __('Cluster Selection');
    } else if ($scope.scheduleModel.action_typ === 'storage') {
      type = __('Datastore Selection');
    } else if ($scope.scheduleModel.action_typ === 'db_backup') {
      type = __('Database Backup Selection');
    }

    return type;
  };

  $scope.determineActionType = function() {
    if (isVmType()) {
      return 'vm';
    } else if (isHostType()) {
      return 'host';
    } else {
      return $scope.scheduleModel.action_typ;
    }
  };

  $scope.dbBackup = function() {
    return $scope.scheduleModel.action_typ === 'db_backup';
  };

  $scope.sambaBackup = function() {
    return $scope.dbBackup() && $scope.scheduleModel.log_protocol === 'Samba';
  };

  $scope.actionTypeChanged = function() {
    if ($scope.dbBackup()) {
      $scope.scheduleModel.log_protocol = 'Network File System';
      $scope.scheduleModel.uri_prefix = 'nfs';
      $scope.scheduleModel.filter_typ = null;
    } else {
      $scope.scheduleModel.filter_typ = 'all';
    }
    $scope.scheduleModel.filter_value = '';

    $scope.filterValuesEmpty = true;
  };

  $scope.filterTypeChanged = function() {
    if ($scope.scheduleModel.filter_typ != 'all') {
      miqService.sparkleOn();
      $http.post('/ops/schedule_form_filter_type_field_changed/' + scheduleFormId,
        {filter_type: $scope.scheduleModel.filter_typ,
         action_type: $scope.scheduleModel.action_typ}).success(function(data) {
        buildFilterList(data);
        $scope.filterValuesEmpty = false;
        miqService.sparkleOff();
      });
    } else {
      $scope.scheduleModel.filter_value = '';
      $scope.filterValuesEmpty = true;
    }
  };

  $scope.logProtocolChanged = function() {
    if ($scope.scheduleModel.log_protocol === "Samba") {
      $scope.scheduleModel.uri_prefix = "smb";
    }

    if ($scope.scheduleModel.log_protocol === "Network File System") {
      $scope.scheduleModel.uri_prefix = "nfs";
      $scope.$broadcast('resetClicked');
      $scope.scheduleModel.log_userid = $scope.modelCopy.log_userid;
      $scope.scheduleModel.log_password = $scope.scheduleModel.log_verify = $scope.modelCopy.log_password;
    }
  };

  $scope.filterValueChanged = function() {
  };

  $scope.scheduleTimerTypeChanged = function() {
    $scope.setTimerType();

    $scope.timer_items = timerOptionService.getOptions($scope.scheduleModel.timer_typ);

    if ($scope.timerNotOnce()) {
      $scope.scheduleModel.timer_value = 1;
    } else {
      $scope.scheduleModel.timer_value = 0;
    }
  };

  $scope.timerNotOnce = function() {
    return $scope.scheduleModel.timer_typ !== 'Once';
  };

  $scope.cancelClicked = function() {
    scheduleEditButtonClicked('cancel');
    $scope.angularForm.$setPristine(true);
  };

  $scope.resetClicked = function() {
    $scope.$broadcast('resetClicked');
    $scope.scheduleModel = angular.copy( $scope.modelCopy );
    if($scope.dbBackup())
        $scope.filterValuesEmpty = true;

      if(!$scope.dbBackup() && $scope.scheduleModel.filter_typ &&
          ($scope.form.action_typ.$untouched && $scope.form.filter_typ.$untouched)) {
        //AJAX-less Reset
        $scope.toggleValueForWatch('filterValuesEmpty', false);
      }

    if(!$scope.dbBackup() && $scope.scheduleModel.filter_typ &&
      ($scope.form.action_typ.$touched || $scope.form.filter_typ.$touched)) {
      $scope.filterTypeChanged();
    }
    if($scope.scheduleModel.timer_typ && $scope.form.timer_typ.$touched) {
      $scope.setTimerType();
      $scope.timer_items = timerOptionService.getOptions($scope.scheduleModel.timer_typ);
    }
    $scope.angularForm.$setUntouched(true);
    $scope.angularForm.$setPristine(true);
    miqService.miqFlash("warn", __("All changes have been reset"));
  };

  $scope.saveClicked = function() {
    scheduleEditButtonClicked('save', true);
    $scope.angularForm.$setPristine(true);
  };

  $scope.addClicked = function() {
    $scope.saveClicked();
  };

  $scope.filterValueRequired = function(value) {
    return !$scope.filterValuesEmpty &&
      ($scope.isModelValueNil(value));
  };

  $scope.dbRequired = function(value) {
    return $scope.dbBackup() &&
      ($scope.isModelValueNil(value));
  };

  $scope.sambaRequired = function(value) {
    return $scope.sambaBackup() &&
      ($scope.isModelValueNil(value));
  };

  $scope.isModelValueNil = function(value) {
    return value == null || value == '';
  };

  $scope.isBasicInfoValid = function() {
    if($scope.angularForm.depot_name.$valid &&
      $scope.angularForm.uri.$valid &&
      $scope.angularForm.log_userid.$valid &&
      $scope.angularForm.log_password.$valid &&
      $scope.angularForm.log_verify.$valid)
      return true;
    else
      return false;
  };

  $scope.setTimerType = function() {
    if ($scope.scheduleModel.timer_typ == "Once")
      $scope.timerTypeOnce = true;
    else {
      $scope.timerTypeOnce = false;
    }
  };

  $scope.toggleValueForWatch =   function(watchValue, initialValue) {
    if($scope[watchValue] == initialValue)
      $scope[watchValue] = "NO-OP";
    else if($scope[watchValue] == "NO-OP")
      $scope[watchValue] = initialValue;
  };

  $scope.canValidate = function () {
    if ($scope.isBasicInfoValid() && $scope.validateFieldsDirty())
      return true;
    else
      return false;
  }

  $scope.canValidateBasicInfo = function () {
    if ($scope.isBasicInfoValid())
      return true;
    else
      return false;
  }

  $scope.validateFieldsDirty = function () {
    if ($scope.angularForm.depot_name.$dirty ||
        $scope.angularForm.uri.$dirty ||
        $scope.angularForm.log_userid.$dirty ||
        $scope.angularForm.log_password.$dirty ||
        $scope.angularForm.log_verify.$dirty)
      return true;
    else
      return false;
  }

  init();
}]);

<|endoftext|>


<commit_before>const chalk = require('chalk');
const logger = require('../logger');

const DIALECT_MYSQL = 'mysql';
const DIALECT_POSTGRES = 'postgres';

const typeMatch = (type, value) => (type.match(value) || {}).input;
const typeStartsWith = (type, value) => typeMatch(type, new RegExp(`^${value}.*`, 'i'));
const typeContains = (type, value) => typeMatch(type, new RegExp(`${value}.*`, 'i'));

function ColumnTypeGetter(databaseConnection, schema, allowWarning = true) {
  const queryInterface = databaseConnection.getQueryInterface();

  function isDialect(dialect) {
    return queryInterface.sequelize.options.dialect === dialect;
  }

  function isColumnTypeEnum(columnName) {
    const type = queryInterface.sequelize.QueryTypes.SELECT;
    const query = `
      SELECT i.udt_name
      FROM pg_catalog.pg_type t
      JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
      JOIN pg_catalog.pg_enum e ON t.oid = e.enumtypid
      LEFT JOIN INFORMATION_SCHEMA.columns i ON t.typname = i.udt_name
      WHERE i.column_name = :columnName OR t.typname = :columnName
      GROUP BY i.udt_name;
    `;
    const replacements = { columnName };

    return queryInterface.sequelize
      .query(query, { replacements, type })
      .then((result) => !!result.length);
  }

  function getTypeOfArrayForPostgres(table, columnName) {
    const type = queryInterface.sequelize.QueryTypes.SELECT;
    const query = `
      SELECT e.udt_name as "udtName",
        (CASE WHEN e.udt_name = 'hstore'
            THEN e.udt_name ELSE e.data_type END)
          || (CASE WHEN e.character_maximum_length IS NOT NULL
            THEN '(' || e.character_maximum_length || ')' ELSE '' END) as "type",
        (SELECT array_agg(en.enumlabel) FROM pg_catalog.pg_type t
          JOIN pg_catalog.pg_enum en
          ON t.oid = en.enumtypid
          WHERE t.typname = e.udt_name) AS "special"
      FROM INFORMATION_SCHEMA.columns c
      LEFT JOIN INFORMATION_SCHEMA.element_types e
      ON ((c.table_catalog, c.table_schema, c.table_name, 'TABLE', c.dtd_identifier) = (e.object_catalog, e.object_schema, e.object_name, e.object_type, e.collection_type_identifier))
      WHERE table_schema = :schema
        AND table_name = :table AND c.column_name = :columnName
    `;
    const replacements = { schema, table, columnName };

    return queryInterface.sequelize
      .query(query, { replacements, type })
      .then((result) => result[0])
      .then((info) => ({
        ...info,
        special: info.special ? info.special.slice(1, -1).split(',') : [],
      }));
  }

  async function getTypeForUserDefined(columnName, columnInfo) {
    const { special } = columnInfo;
    if (isDialect(DIALECT_POSTGRES) && await isColumnTypeEnum(columnName)) {
      return `ENUM(\n        '${special.join('\',\n        \'')}',\n      )`;
    }
    return 'STRING';
  }

  this.getTypeForArray = async (tableName, columnName) => {
    if (!isDialect(DIALECT_POSTGRES)) { return null; }
    const innerColumnInfo = await getTypeOfArrayForPostgres(tableName, columnName);
    return `ARRAY(DataTypes.${await this.perform(innerColumnInfo, innerColumnInfo.udtName, tableName)})`;
  };

  this.perform = async (columnInfo, columnName, tableName) => {
    const { type } = columnInfo;

    switch (type) {
      case 'JSON':
        return 'JSON';
      case (type === 'BIT(1)' && isDialect(DIALECT_MYSQL) && 'BIT(1)'): // NOTICE: MySQL boolean type.
      case 'BIT': // NOTICE: MSSQL type.
      case 'BOOLEAN':
        return 'BOOLEAN';
      case 'CHARACTER VARYING':
      case 'TEXT':
      case 'NTEXT': // MSSQL type
      case typeContains(type, 'TEXT'):
      case typeContains(type, 'VARCHAR'):
      case typeContains(type, 'CHAR'):
      case 'NVARCHAR': // NOTICE: MSSQL type.
        return 'STRING';
      case 'USER-DEFINED':
        return getTypeForUserDefined(columnName, columnInfo);
      case typeMatch(type, /ENUM\((.*)\)/i):
        return type;
      case 'UNIQUEIDENTIFIER':
      case 'UUID':
        return 'UUID';
      case 'JSONB':
        return 'JSONB';
      case 'INTEGER':
      case 'SERIAL':
      case 'BIGSERIAL':
      case typeStartsWith(type, 'INT'):
      case typeStartsWith(type, 'SMALLINT'):
      case typeStartsWith(type, 'TINYINT'):
        return 'INTEGER';
      case typeStartsWith(type, 'BIGINT'):
        return 'BIGINT';
      case typeContains(type, 'FLOAT'):
        return 'FLOAT';
      case 'NUMERIC':
      case 'DECIMAL':
      case 'REAL':
      case 'DOUBLE':
      case 'DOUBLE PRECISION':
      case typeContains(type, 'DECIMAL'):
      case 'MONEY': // MSSQL type
        return 'DOUBLE';
      case 'DATE':
        return 'DATEONLY';
      case 'DATETIME':
      case typeStartsWith(type, 'TIMESTAMP'):
        return 'DATE';
      case 'TIME':
      case 'TIME WITHOUT TIME ZONE':
        return 'TIME';
      case 'ARRAY':
        return this.getTypeForArray(tableName, columnName);
      case 'INET':
        return 'INET';
      default:
        if (allowWarning) {
          logger.warn(`Type ${chalk.bold(type)} is not handled: The column ${chalk.bold(columnName)} won't be generated by lumber. If you need it please create it manually.`);
        }
        return null;
    }
  };
}

module.exports = ColumnTypeGetter;
<commit_msg>fix: handle medium integer type (#506)<commit_after>const chalk = require('chalk');
const logger = require('../logger');

const DIALECT_MYSQL = 'mysql';
const DIALECT_POSTGRES = 'postgres';

const typeMatch = (type, value) => (type.match(value) || {}).input;
const typeStartsWith = (type, value) => typeMatch(type, new RegExp(`^${value}.*`, 'i'));
const typeContains = (type, value) => typeMatch(type, new RegExp(`${value}.*`, 'i'));

function ColumnTypeGetter(databaseConnection, schema, allowWarning = true) {
  const queryInterface = databaseConnection.getQueryInterface();

  function isDialect(dialect) {
    return queryInterface.sequelize.options.dialect === dialect;
  }

  function isColumnTypeEnum(columnName) {
    const type = queryInterface.sequelize.QueryTypes.SELECT;
    const query = `
      SELECT i.udt_name
      FROM pg_catalog.pg_type t
      JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
      JOIN pg_catalog.pg_enum e ON t.oid = e.enumtypid
      LEFT JOIN INFORMATION_SCHEMA.columns i ON t.typname = i.udt_name
      WHERE i.column_name = :columnName OR t.typname = :columnName
      GROUP BY i.udt_name;
    `;
    const replacements = { columnName };

    return queryInterface.sequelize
      .query(query, { replacements, type })
      .then((result) => !!result.length);
  }

  function getTypeOfArrayForPostgres(table, columnName) {
    const type = queryInterface.sequelize.QueryTypes.SELECT;
    const query = `
      SELECT e.udt_name as "udtName",
        (CASE WHEN e.udt_name = 'hstore'
            THEN e.udt_name ELSE e.data_type END)
          || (CASE WHEN e.character_maximum_length IS NOT NULL
            THEN '(' || e.character_maximum_length || ')' ELSE '' END) as "type",
        (SELECT array_agg(en.enumlabel) FROM pg_catalog.pg_type t
          JOIN pg_catalog.pg_enum en
          ON t.oid = en.enumtypid
          WHERE t.typname = e.udt_name) AS "special"
      FROM INFORMATION_SCHEMA.columns c
      LEFT JOIN INFORMATION_SCHEMA.element_types e
      ON ((c.table_catalog, c.table_schema, c.table_name, 'TABLE', c.dtd_identifier) = (e.object_catalog, e.object_schema, e.object_name, e.object_type, e.collection_type_identifier))
      WHERE table_schema = :schema
        AND table_name = :table AND c.column_name = :columnName
    `;
    const replacements = { schema, table, columnName };

    return queryInterface.sequelize
      .query(query, { replacements, type })
      .then((result) => result[0])
      .then((info) => ({
        ...info,
        special: info.special ? info.special.slice(1, -1).split(',') : [],
      }));
  }

  async function getTypeForUserDefined(columnName, columnInfo) {
    const { special } = columnInfo;
    if (isDialect(DIALECT_POSTGRES) && await isColumnTypeEnum(columnName)) {
      return `ENUM(\n        '${special.join('\',\n        \'')}',\n      )`;
    }
    return 'STRING';
  }

  this.getTypeForArray = async (tableName, columnName) => {
    if (!isDialect(DIALECT_POSTGRES)) { return null; }
    const innerColumnInfo = await getTypeOfArrayForPostgres(tableName, columnName);
    return `ARRAY(DataTypes.${await this.perform(innerColumnInfo, innerColumnInfo.udtName, tableName)})`;
  };

  this.perform = async (columnInfo, columnName, tableName) => {
    const { type } = columnInfo;

    switch (type) {
      case 'JSON':
        return 'JSON';
      case (type === 'BIT(1)' && isDialect(DIALECT_MYSQL) && 'BIT(1)'): // NOTICE: MySQL boolean type.
      case 'BIT': // NOTICE: MSSQL type.
      case 'BOOLEAN':
        return 'BOOLEAN';
      case 'CHARACTER VARYING':
      case 'TEXT':
      case 'NTEXT': // MSSQL type
      case typeContains(type, 'TEXT'):
      case typeContains(type, 'VARCHAR'):
      case typeContains(type, 'CHAR'):
      case 'NVARCHAR': // NOTICE: MSSQL type.
        return 'STRING';
      case 'USER-DEFINED':
        return getTypeForUserDefined(columnName, columnInfo);
      case typeMatch(type, /ENUM\((.*)\)/i):
        return type;
      case 'UNIQUEIDENTIFIER':
      case 'UUID':
        return 'UUID';
      case 'JSONB':
        return 'JSONB';
      case 'INTEGER':
      case 'SERIAL':
      case 'BIGSERIAL':
      case typeStartsWith(type, 'INT'):
      case typeStartsWith(type, 'SMALLINT'):
      case typeStartsWith(type, 'TINYINT'):
      case typeStartsWith(type, 'MEDIUMINT'):
        return 'INTEGER';
      case typeStartsWith(type, 'BIGINT'):
        return 'BIGINT';
      case typeContains(type, 'FLOAT'):
        return 'FLOAT';
      case 'NUMERIC':
      case 'DECIMAL':
      case 'REAL':
      case 'DOUBLE':
      case 'DOUBLE PRECISION':
      case typeContains(type, 'DECIMAL'):
      case 'MONEY': // MSSQL type
        return 'DOUBLE';
      case 'DATE':
        return 'DATEONLY';
      case 'DATETIME':
      case typeStartsWith(type, 'TIMESTAMP'):
        return 'DATE';
      case 'TIME':
      case 'TIME WITHOUT TIME ZONE':
        return 'TIME';
      case 'ARRAY':
        return this.getTypeForArray(tableName, columnName);
      case 'INET':
        return 'INET';
      default:
        if (allowWarning) {
          logger.warn(`Type ${chalk.bold(type)} is not handled: The column ${chalk.bold(columnName)} won't be generated by lumber. If you need it please create it manually.`);
        }
        return null;
    }
  };
}

module.exports = ColumnTypeGetter;
<|endoftext|>


<commit_before>/*
 * fis
 * http://fis.baidu.com/
 */

'use strict';

/**
 * Config默认设置
 * @type {Object}
 */
var DEFAULT_SETTINGS = {

  project: {
    charset: 'utf8',
    md5Length: 7,
    md5Connector: '_',
    files: ['**'],
    ignore: ['node_modules/**', 'output/**', 'fis-conf.js'],
    watch: {
      exclude: /^\/(?:output|node_modules|fis\-conf\.js).*$/i
    }
  },

  component: {
    skipRoadmapCheck: true,
    protocol: 'github',
    author: 'fis-components'
  },

  modules: {
    plugin: 'module, components',
    packager: 'map',
    deploy: 'default'
  },

  options: {}
};

var groups = {};
var rVariable = /\$\{([^\}]+)\}/g

//You can't use merge in util.js
function merge(source, target) {
  if (typeof source === 'object' && typeof target === 'object') {
    for (var key in target) {
      if (target.hasOwnProperty(key)) {
        source[key] = merge(source[key], target[key]);
      }
    }
  } else {
    source = target;
  }
  return source;
}

function sortByWeight(a, b) {
  return a.weight - b.weight;
}

/**
 * Config构造方法
 * @constructor constructor
 *         @see init
 * @method init     Config初始化函数，初始化成员变量
 *         @param {Object}  将n(n >= 0)个对象参数与成员变量data合并
 * @method get      向上查找模块path，若没有返回def
 *         @param {String} path 模块路径
 *         @param {String} def 默认值
 *         @see _get
 * @method _get     遍历data中是否存在模块
 *         @param {String} path 模块路径
 *         @example
 *           get('obj')
 *           get('obj.a.b.c')
 * @method set      设置对应模块索引和值
 *         @param {String} path 模块索引
 *         @param {String/Object} value 对应模块的值
 *         @example
 *           set('obj', {a: b: {c: 12}}) === set('obj.a.b.c', 12)
 * @method del      清除对应path的模块的值
 *         @param {String} path 模块路径
 * @method merge    合并对象
 * @method require  加载对应路径的node模块
 * @method media    获得groups上挂载的模块树(分支)，或创建新的分支
 *         @param {String} group 分支名称，若不存创建新的分支，病返回；若缺省则返回当前使用分支
 *         @return {Object} group分支
 * @method env      @see media
 * @method match    处理匹配文件名时的正则，支持glob语法和分组功能
 *         @param {String} pattern glob语法的字符串
 *         @param {Object} properties 配置属性
 *         @param {Number} weight 优先级权重，缺省为0
 *         @return {Object} 返回Config对象自己，方便链式使用
 * @method getMatches  返回由global分支和当前使用分支下的全部match对象
 * @method getSortedMatches 返回weight由大到小的match序列
 */
var Config = Object.derive({
  constructor: function() {
    this.init.apply(this, arguments);
  },
  init: function() {
    this.data = {};
    this._matches = [];
    this._sortedMatches = null;
    this._media = 'GLOBAL';
    if (arguments.length > 0) {
      this.merge.apply(this, arguments);
    }
    return this;
  },
  get: function(path, def) {
    var ret = this._get(path);

    if (typeof ret === 'undefined' && this.parent) {
      ret = this.parent._get(path);
    }

    return typeof ret === 'undefined' ? def : this._filter(ret);
  },
  _get: function(path) {
    var result = this.data;
    (path || '').split('.').forEach(function(key) {
      if (key && (typeof result !== 'undefined')) {
        result = result[key];
      }
    });
    return result;
  },

  _filter: function(string) {
    // 可能有 Maximum call stack size exceeded
    // 不过，这就应该报错。
    if ('string' === typeof string && rVariable.test(string)) {
      rVariable.lastIndex = 0;
      var self = this;

      return string.replace(rVariable, function(all, key) {
        var val = self.get(key);

        if(typeof val === 'undefined'){
            fis.log.error('undefined property [' + key + '].');
        } else {
            return val || '';
        }
      });
    }

    return string;
  },
  set: function(path, value) {
    if (typeof value === 'undefined') {
      this.data = path;
    } else {
      path = String(path || '').trim();
      if (path) {
        var paths = path.split('.'),
          last = paths.pop(),
          data = this.data || {};
        paths.forEach(function(key) {
          var type = typeof data[key];
          if (type === 'object') {
            data = data[key];
          } else if (type === 'undefined') {
            data = data[key] = {};
          } else {
            fis.log.error('forbidden to set property[' + key + '] of [' + type + '] data');
          }
        });
        data[last] = value;
      }
    }
    return this;
  },
  del: function(path) {
    path = String(path || '').trim();
    if (path) {
      var paths = path.split('.'),
        data = this.data,
        last = paths.pop(),
        key;
      for (var i = 0, len = paths.length; i < len; i++) {
        key = paths[i];
        if (typeof data[key] === 'object') {
          data = data[key];
        } else {
          return this;
        }
      }
      if (typeof data[last] !== 'undefined') {
        delete data[last];
      }
    }
    return this;
  },
  merge: function() {
    var self = this;
    [].slice.call(arguments).forEach(function(arg) {
      if (typeof arg === 'object') {
        merge(self.data, arg);
      } else {
        fis.log.warning('unable to merge data[' + arg + '].');
      }
    });
    return this;
  },
  require: function(name) {
    fis.require('config', name);
    return this;
  },
  media: function(group) {
    group = group || fis.project.currentMedia() || 'dev';

    if (!groups[group]) {
      var forked = groups[group] = new Config();
      forked._media = group;
      forked.parent = module.exports;
    }

    return groups[group];
  },
  env: function() {
    return this.media.apply(this, arguments);
  },
  match: function(pattern, properties, weight) {
    var matches = this._matches;
    var negate = false;

    if (typeof pattern === 'string') {
      if (pattern[0] === '!') {
        negate = true;
        pattern = pattern.substring(1);
      }

      pattern = fis.util.glob(pattern);
    }

    var match = {
      reg: pattern,
      negate: negate,
      properties: properties,
      media: this._media,
      weight: weight === true ? 1 : (parseInt(weight, 10) || 0)
    };

    matches.push(match);
    this._sortedMatches = null;
    if (this.parent) {
      this.parent._matches.push(match);
      this.parent._sortedMatches = null;
    }

    return this;
  },

  getMatches: function() {
    if (this.parent) {
      var that = this;
      return this.parent._matches.filter(function (match) {
        if ((match.media !== 'GLOBAL') && (match.media !== that._media)) {
          return false;
        }

        return true;
      });
    } else {
      return this._matches;
    }
  },

  getSortedMatches: function() {
    if (!this._sortedMatches || this.parent && !this.parent._sortedMatches) {
      this._sortedMatches = this.getMatches().sort(sortByWeight);
    }

    return this._sortedMatches;
  }
});

module.exports = (new Config).init(DEFAULT_SETTINGS);
module.exports.Config = Config;
module.exports.DEFALUT_SETTINGS = DEFAULT_SETTINGS;
<commit_msg>update<commit_after>/*
 * fis
 * http://fis.baidu.com/
 */

'use strict';

/**
 * Config默认设置
 * @type {Object}
 */
var DEFAULT_SETTINGS = {

  project: {
    charset: 'utf8',
    md5Length: 7,
    md5Connector: '_',
    files: ['**'],
    ignore: ['node_modules/**', 'output/**', 'fis-conf.js'],
    watch: {
      exclude: /^\/(?:output|node_modules|fis\-conf\.js).*$/i
    }
  },

  component: {
    skipRoadmapCheck: true,
    protocol: 'github',
    author: 'fis-components'
  },

  modules: {
    plugin: 'module, components',
    packager: 'map',
    deploy: 'default'
  },

  options: {}
};

var groups = {};
var rVariable = /\$\{([^\}]+)\}/g

//You can't use merge in util.js
function merge(source, target) {
  if (typeof source === 'object' && typeof target === 'object') {
    for (var key in target) {
      if (target.hasOwnProperty(key)) {
        source[key] = merge(source[key], target[key]);
      }
    }
  } else {
    source = target;
  }
  return source;
}

function sortByWeight(a, b) {
  return a.weight - b.weight;
}

/**
 * Config构造方法
 * @constructor constructor
 *         @see init
 * @method init     Config初始化函数，初始化成员变量
 *         @param {Object}  将n(n >= 0)个对象参数与成员变量data合并
 * @method get      向上查找模块path，若没有返回def
 *         @param {String} path 模块路径
 *         @param {String} def 默认值
 *         @see _get
 * @method _get     遍历data中是否存在模块
 *         @param {String} path 模块路径
 *         @example
 *           get('obj')
 *           get('obj.a.b.c')
 * @method set      设置对应模块索引和值
 *         @param {String} path 模块索引
 *         @param {String/Object} value 对应模块的值
 *         @example
 *           set('obj', {a: b: {c: 12}}) === set('obj.a.b.c', 12)
 * @method del      清除对应path的模块的值
 *         @param {String} path 模块路径
 * @method merge    合并对象
 * @method require  加载对应路径的node模块
 * @method media    获得groups上挂载的模块树(分支)，或创建新的分支
 *         @param {String} group 分支名称，若不存创建新的分支，病返回；若缺省则返回当前使用分支
 *         @return {Object} group分支
 * @method env      @see media
 * @method match    处理匹配文件名时的正则，支持glob语法和分组功能
 *         @param {String} pattern glob语法的字符串
 *         @param {Object} properties 配置属性
 *         @param {Number} weight 优先级权重，缺省为0
 *         @return {Object} 返回Config对象自己，方便链式使用
 * @method getMatches  返回由global分支和当前使用分支下的全部match对象
 * @method getSortedMatches 返回weight由大到小的match序列
 */
var Config = Object.derive({
  constructor: function() {
    this.init.apply(this, arguments);
  },
  init: function() {
    this.data = {};
    this._matches = [];
    this._forks = [];
    this._sortedMatches = null;
    this._media = 'GLOBAL';
    if (arguments.length > 0) {
      this.merge.apply(this, arguments);
    }
    return this;
  },
  get: function(path, def) {
    var ret = this._get(path);

    if (typeof ret === 'undefined' && this.parent) {
      ret = this.parent._get(path);
    }

    return typeof ret === 'undefined' ? def : this._filter(ret);
  },
  _get: function(path) {
    var result = this.data;
    (path || '').split('.').forEach(function(key) {
      if (key && (typeof result !== 'undefined')) {
        result = result[key];
      }
    });
    return result;
  },

  _filter: function(string) {
    // 可能有 Maximum call stack size exceeded
    // 不过，这就应该报错。
    if ('string' === typeof string && rVariable.test(string)) {
      rVariable.lastIndex = 0;
      var self = this;

      return string.replace(rVariable, function(all, key) {
        var val = self.get(key);

        if(typeof val === 'undefined'){
            fis.log.error('undefined property [' + key + '].');
        } else {
            return val || '';
        }
      });
    }

    return string;
  },
  set: function(path, value) {
    if (typeof value === 'undefined') {
      this.data = path;
    } else {
      path = String(path || '').trim();
      if (path) {
        var paths = path.split('.'),
          last = paths.pop(),
          data = this.data || {};
        paths.forEach(function(key) {
          var type = typeof data[key];
          if (type === 'object') {
            data = data[key];
          } else if (type === 'undefined') {
            data = data[key] = {};
          } else {
            fis.log.error('forbidden to set property[' + key + '] of [' + type + '] data');
          }
        });
        data[last] = value;
      }
    }
    return this;
  },
  del: function(path) {
    path = String(path || '').trim();
    if (path) {
      var paths = path.split('.'),
        data = this.data,
        last = paths.pop(),
        key;
      for (var i = 0, len = paths.length; i < len; i++) {
        key = paths[i];
        if (typeof data[key] === 'object') {
          data = data[key];
        } else {
          return this;
        }
      }
      if (typeof data[last] !== 'undefined') {
        delete data[last];
      }
    }
    return this;
  },
  merge: function() {
    var self = this;
    [].slice.call(arguments).forEach(function(arg) {
      if (typeof arg === 'object') {
        merge(self.data, arg);
      } else {
        fis.log.warning('unable to merge data[' + arg + '].');
      }
    });
    return this;
  },
  require: function(name) {
    fis.require('config', name);
    return this;
  },
  media: function(group) {
    group = group || fis.project.currentMedia() || 'dev';

    if (!groups[group]) {
      var forked = groups[group] = new Config();
      forked._media = group;
      forked.parent = module.exports;
      this._forks.push(forked);
    }

    return groups[group];
  },
  env: function() {
    return this.media.apply(this, arguments);
  },
  _addMatch: function(match) {
    this._matches.push(match);
    this._sortedMatches = null;

    this._forks.forEach(function(forked) {
      forked._sortedMatches = null;
    });
  },
  match: function(pattern, properties, weight) {
    var negate = false;

    if (typeof pattern === 'string') {
      if (pattern[0] === '!') {
        negate = true;
        pattern = pattern.substring(1);
      }

      pattern = fis.util.glob(pattern);
    }

    var match = {
      reg: pattern,
      negate: negate,
      properties: properties,
      media: this._media,
      weight: weight === true ? 1 : (parseInt(weight, 10) || 0)
    };

    this._addMatch(match);
    this.parent && this.parent._addMatch(match);

    return this;
  },

  getMatches: function() {
    if (this.parent) {
      var that = this;
      return this.parent._matches.filter(function (match) {
        if ((match.media !== 'GLOBAL') && (match.media !== that._media)) {
          return false;
        }

        return true;
      });
    } else {
      return this._matches;
    }
  },

  getSortedMatches: function() {
    if (!this._sortedMatches) {
      this._sortedMatches = this.getMatches().sort(sortByWeight);
    }

    return this._sortedMatches;
  }
});

module.exports = (new Config).init(DEFAULT_SETTINGS);
module.exports.Config = Config;
module.exports.DEFALUT_SETTINGS = DEFAULT_SETTINGS;
<|endoftext|>


<commit_before>/**
 * Rich Text Editor
 */
'use strict';

(function (document, app, CKEDITOR) {
    document.addEventListener('DOMContentLoaded', function () {
        Array.prototype.forEach.call(document.querySelectorAll('textarea[data-type=rte]'), function (item) {
            CKEDITOR.replace(item, {
                blockApi: id => typeof id === 'string' && id ? '/block/api/' + id : null,
                blockBrowser: '/block/browser',
                contentsCss: ['/gui/base.css', '/gui/rte/rte.css'],
                customConfig: '',
                disableNativeSpellChecker: true,
                fillEmptyBlocks: false,
                format_tags: 'p;h2;h3',
                height: '30rem',
                language: document.documentElement.getAttribute('lang') || 'en',
                mediaBrowser: '/file_media/browser',
                removeDialogTabs: 'link:advanced;link:target',
                section: {
                    'block-content': app.i18n('Content Block')
                },
                stylesSet: false,
                toolbar: [
                    {
                        name: 'all',
                        items: [
                            'Undo',
                            'Redo',
                            'Maximize',
                            'Bold',
                            'Italic',
                            'Link',
                            'Unlink',
                            'Format',
                            'BulletedList',
                            'NumberedList',
                            'Quote',
                            'Media',
                            'Table',
                            'Detail',
                            'Section',
                            'Block',
                            'Grid'
                        ]
                    }
                ]
            });
        });
    });
})(document, app, CKEDITOR);
<commit_msg>gallery widget<commit_after>/**
 * Rich Text Editor
 */
'use strict';

(function (document, app, CKEDITOR) {
    document.addEventListener('DOMContentLoaded', function () {
        Array.prototype.forEach.call(document.querySelectorAll('textarea[data-type=rte]'), function (item) {
            CKEDITOR.replace(item, {
                blockApi: id => typeof id === 'string' && id ? '/block/api/' + id : null,
                blockBrowser: '/block/browser',
                contentsCss: ['/gui/base.css', '/gui/rte/rte.css'],
                customConfig: '',
                disableNativeSpellChecker: true,
                fillEmptyBlocks: false,
                format_tags: 'p;h2;h3',
                height: '30rem',
                language: document.documentElement.getAttribute('lang') || 'en',
                mediaBrowser: '/file_media/browser',
                removeDialogTabs: 'link:advanced;link:target',
                section: {
                    'block-content': app.i18n('Content Block')
                },
                stylesSet: false,
                toolbar: [
                    {
                        name: 'all',
                        items: [
                            'Undo',
                            'Redo',
                            'Maximize',
                            'Bold',
                            'Italic',
                            'Link',
                            'Unlink',
                            'Format',
                            'BulletedList',
                            'NumberedList',
                            'Quote',
                            'Media',
                            'Table',
                            'Detail',
                            'Section',
                            'Block',
                            'Grid',
                            'Gallery'
                        ]
                    }
                ]
            });
        });
    });
})(document, app, CKEDITOR);
<|endoftext|>


<commit_before>const webpack = require('webpack');
const path = require('path');

const { argv } = require('yargs');
const packageName = argv.package;

const sourcePath = path.join(
  __dirname,
  '..',
  '..',
  'benchmarks',
  packageName,
  './client'
);
const staticsPath = path.join(
  __dirname,
  '..',
  '..',
  'benchmarks',
  packageName,
  './static'
);

const isProd = process.NODE_ENV === 'production';

const plugins = [
  new webpack.optimize.CommonsChunkPlugin({
    name: 'vendor',
    minChunks: Infinity,
    filename: 'vendor.bundle.js',
  }),
  new webpack.EnvironmentPlugin({
    NODE_ENV: process.NODE_ENV,
  }),
  new webpack.NamedModulesPlugin(),
];

if (isProd) {
  plugins.push(
    new webpack.LoaderOptionsPlugin({
      minimize: true,
      debug: false,
    }),
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        warnings: false,
        screw_ie8: true,
        conditionals: true,
        unused: true,
        comparisons: true,
        sequences: true,
        dead_code: true,
        evaluate: true,
        if_return: true,
        join_vars: true,
      },
      output: {
        comments: false,
      },
    })
  );
} else {
  plugins.push(new webpack.HotModuleReplacementPlugin());
}

module.exports = {
  devtool: isProd ? 'source-map' : 'eval',
  context: sourcePath,
  entry: {
    js: './index.js',
    vendor: ['react'],
  },
  output: {
    path: staticsPath,
    filename: '[name].bundle.js',
  },
  module: {
    rules: [
      {
        test: /\.html$/,
        exclude: /node_modules/,
        use: {
          loader: 'file-loader',
          query: {
            name: '[name].[ext]',
          },
        },
      },
      {
        test: /\.(js|jsx)$/,
        include: sourcePath,
        exclude: /node_modules/,
        use: ['babel-loader'],
      },
      {
        test: /\.css$/,
        exclude: /node_modules/,
        use: ['style-loader', 'css-loader'],
      },
    ],
  },
  resolve: {
    extensions: [
      '.webpack-loader.js',
      '.web-loader.js',
      '.loader.js',
      '.js',
      '.jsx',
    ],
    modules: [
      // path.join(__dirname, '..', '..', packageName, 'node_modules')
      'node_modules',
      path.join(
        __dirname,
        '..',
        '..',
        'benchmarks',
        packageName,
        'node_modules'
      ),
      path.join(__dirname, '..', '..', '..', 'node_modules'),
    ],
  },

  plugins,

  performance: isProd && {
    maxAssetSize: 100,
    maxEntrypointSize: 300,
    hints: 'warning',
  },

  stats: {
    colors: {
      green: '\u001b[32m',
    },
  },

  devServer: {
    contentBase: './client',
    historyApiFallback: true,
    port: 3000,
    compress: isProd,
    inline: !isProd,
    hot: !isProd,
    stats: {
      assets: true,
      children: false,
      chunks: false,
      hash: false,
      modules: false,
      publicPath: false,
      timings: true,
      version: false,
      warnings: true,
      colors: {
        green: '\u001b[32m',
      },
    },
  },
};
<commit_msg>fix webpack config to support npm-link<commit_after>const webpack = require('webpack');
const path = require('path');

const { argv } = require('yargs');
const packageName = argv.package;

const sourcePath = path.join(
  __dirname,
  '..',
  '..',
  'benchmarks',
  packageName,
  './client'
);
const staticsPath = path.join(
  __dirname,
  '..',
  '..',
  'benchmarks',
  packageName,
  './static'
);

const isProd = process.NODE_ENV === 'production';

const plugins = [
  new webpack.optimize.CommonsChunkPlugin({
    name: 'vendor',
    minChunks: Infinity,
    filename: 'vendor.bundle.js',
  }),
  new webpack.EnvironmentPlugin({
    NODE_ENV: process.NODE_ENV,
  }),
  new webpack.NamedModulesPlugin(),
];

if (isProd) {
  plugins.push(
    new webpack.LoaderOptionsPlugin({
      minimize: true,
      debug: false,
    }),
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        warnings: false,
        screw_ie8: true,
        conditionals: true,
        unused: true,
        comparisons: true,
        sequences: true,
        dead_code: true,
        evaluate: true,
        if_return: true,
        join_vars: true,
      },
      output: {
        comments: false,
      },
    })
  );
} else {
  plugins.push(new webpack.HotModuleReplacementPlugin());
}

module.exports = {
  devtool: isProd ? 'source-map' : 'eval',
  context: sourcePath,
  entry: {
    js: './index.js',
    vendor: ['react'],
  },
  output: {
    path: staticsPath,
    filename: '[name].bundle.js',
  },
  module: {
    rules: [
      {
        test: /\.html$/,
        exclude: /node_modules/,
        use: {
          loader: 'file-loader',
          query: {
            name: '[name].[ext]',
          },
        },
      },
      {
        test: /\.(js|jsx)$/,
        include: sourcePath,
        exclude: /node_modules/,
        use: ['babel-loader'],
      },
      {
        test: /\.css$/,
        exclude: /node_modules/,
        use: ['style-loader', 'css-loader'],
      },
    ],
  },
  resolve: {
    extensions: [
      '.webpack-loader.js',
      '.web-loader.js',
      '.loader.js',
      '.js',
      '.jsx',
    ],
    modules: [
      // path.join(__dirname, '..', '..', packageName, 'node_modules')
      // 'node_modules',
      path.join(
        __dirname,
        '..',
        '..',
        'benchmarks',
        packageName,
        'node_modules'
      ),
      path.join(__dirname, '..', '..', '..', 'node_modules'),
    ],
  },

  plugins,

  performance: isProd && {
    maxAssetSize: 100,
    maxEntrypointSize: 300,
    hints: 'warning',
  },

  stats: {
    colors: {
      green: '\u001b[32m',
    },
  },

  devServer: {
    contentBase: './client',
    historyApiFallback: true,
    port: 3000,
    compress: isProd,
    inline: !isProd,
    hot: !isProd,
    stats: {
      assets: true,
      children: false,
      chunks: false,
      hash: false,
      modules: false,
      publicPath: false,
      timings: true,
      version: false,
      warnings: true,
      colors: {
        green: '\u001b[32m',
      },
    },
  },
};
<|endoftext|>


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

'use strict';

var _ = require('underscore');
var async = require('async');
var es = require('event-stream');
var stream = require('readable-stream');
var util = require('util');

var Transform = stream.Transform;

var encoding = require('./token-cache-encoding');
var credStore = require('./win-credstore');

// Credential store has size limits, and tokens can
// go beyond them. 640k should be enough for everyone,
// right?
//
// As such, we have to be prepared to split a single
// credential across multiple credstore entries. This
// value controls the size of each chunk.

var MAX_CREDENTIAL_BYTES = 2048;

//
// Takes a single entry and credential, returns an array of
// entry/credential pairs ready to go to the credential store.
// @param{string} targetName target name that will be stored
// @param{string} tokens     string that will be stored as the
//                           actual credential.
//
// @returns {array} The possibly split set of entries for the
//                  credential store.
//
function splitEntry(targetName, tokens) {

  // Tokens are ascii, so # of bytes = # of characters.
  var numBytes = tokens.length;

  if (numBytes <= MAX_CREDENTIAL_BYTES) {
    return [ [targetName, tokens] ];
  }

  var numBlocks = Math.floor(numBytes / MAX_CREDENTIAL_BYTES);
  if (numBlocks % MAX_CREDENTIAL_BYTES != 0) {
    ++numBlocks;
  }

  var blocks = [];

  for(var i = 0; i < numBlocks; ++i) {
    blocks.push([util.format('%s--%d-%d', targetName, i, numBlocks),
      tokens.substr(i * MAX_CREDENTIAL_BYTES, MAX_CREDENTIAL_BYTES)]);
  }

  return blocks;
}

//
// entry joiner. This is written as a transform stream since
// the logic for reading the cred store is also stream based.
//

function JoinerStream() {
  this.entries = {};
  this.splitRe = /^(.*)--(\d+)-(\d+)$/;
  Transform.call(this, {objectMode: true});
}

util.inherits(JoinerStream, Transform);

_.extend(JoinerStream.prototype, {
  _transform: function (chunk, encoding, callback) {
    // object mode, chunk is actually an object of the form
    // { targetName: n, credential: c }
    var targetName = chunk.targetName;
    var credential = chunk.credential;

    var match = targetName.match(this.splitRe);
    if (match != null) {
      var mainTarget = match[1];
      var blockNum = +match[2];
      var numBlocks = +match[3];

      if (!_.has(this.entries, mainTarget)) {
        this.entries[mainTarget] = [];
      }

      this.entries[mainTarget][blockNum] = credential;

      if (_.compact(this.entries[mainTarget]).length === numBlocks) {
        this.push({
          targetName: mainTarget,
          credential: this.entries[mainTarget].join("")
        });
        delete this.entries[mainTarget];
      }
    } else {
      this.push(chunk);
    }
    callback();
  }
});

/**
 * Constructs a new token cache that stores credentials
 * in the Windows credential store.
 * @constructor
 */
 function CredStoreTokenCache() {
  this._entries = null;
 }

 //
// Internal helper functions for serializing and
// deserializing cache entries.
//

function deserializeBool(str) {
  return str.toLowerCase() === 'true';
}

function deserializeDate(str) {
  return new Date(str);
}

function deserializeNum(str) {
  return +str;
}

// Mapping of field names that need special handling
// on deserialization (typically type conversions)
// to the appropriate deserializer functions.
var fieldsToConvert = {
  expiresIn: deserializeNum,
  expiresOn: deserializeDate,
  isUserIdDisplayable: deserializeBool,
  isMRRT: deserializeBool
};

function deserializeEntry(entry) {
  return _.chain(entry)
    .pairs()
    .map(function (pair) {
      var key = pair[0], value = pair[1];
      if (_.has(fieldsToConvert, key)) {
        return [key, fieldsToConvert[key](value)];
      }
      return pair;
    })
    .object()
    .value();
}

_.extend(CredStoreTokenCache.prototype, {
  /**
  * Load the cache entries. Does a lazy load,
  * loads from OS on first request, otherwise
  * returns in-memory copy.
  *
  * @param {function(err, Array)} callback callback
  *                               receiving cache entries.
  */
  _loadEntries: function (callback) {
    var self = this;
    if (self._entries !== null) {
      return callback(null, self._entries);
    }

    var joiner = new JoinerStream();
    var entries = [];
    credStore.list()
      .pipe(joiner)
      .pipe(es.mapSync(function (entry) {
        // Most fields are stored in targetName,
        // access token and refresh token are stored in
        // credentials.
        var authResult = encoding.decodeObject(entry.targetName);
        var credential = encoding.decodeObject(new Buffer(entry.credential, 'hex').toString('utf8'));
        authResult.accessToken = credential.a;
        authResult.refreshToken = credential.r;

        return deserializeEntry(authResult);
      }))
      .on('data', function (entry) {
        entries.push(entry);
      })
      .on('end', function (err) {
        if (!err) {
          self._entries = entries;
        }
        callback(err, entries);
      });
  },

  isSecureCache: true,

  /**
   * Removes a collection of entries from the cache in a single batch operation.
   * @param  {Array}   entries  An array of cache entries to remove.
   * @param  {Function} callback This function is called when the operation is complete.  Any error is provided as the
   *                             first parameter.
   */
  remove: function remove(entries, callback) {
    var self = this;

    function shouldKeep(entry) {
      if (_.findWhere(entries, entry)) {
        return false;
      }
      return true;
    }

    self._loadEntries(function (err, _entries) {
      if (err) { return callback(err); }

      var grouped = _.groupBy(_entries, shouldKeep);
      var entriesToRemove = grouped[false] || [];
      var entriesToKeep = grouped[true] || [];

      function removeEntry(entry, removeCb) {
        var targetName = encoding.encodeObject(_.omit(entry, ['accessToken', 'refreshToken']));
        async.series([
          function (done) {
            credStore.remove(targetName, function () { done(); });
          },
          function (done) {
            credStore.remove(targetName + '--*', function () { done(); });
          }
        ], removeCb);
      }

      async.eachSeries(entriesToRemove, removeEntry, function (err) {
        if (err) { return callback(err); }
        self._entries = entriesToKeep;
        callback();
      });
    });
  },

  /**
   * Adds a collection of entries to the cache in a single batch operation.
   * @param {Array}   entries  An array of entries to add to the cache.
   * @param  {Function} callback This function is called when the operation is complete.  Any error is provided as the
   *                             first parameter.
   */
  add: function add(entries, callback) {
    var self = this;

    self._loadEntries(function (err, _entries) {
      if (err) { return callback(err); }

      // Remove any entries that are duplicates of the existing
      // cache elements.
      _.each(_entries, function(element) {
        _.each(entries, function(addElement, index) {
          if (_.isEqual(element, addElement)) {
            entries[index] = null;
          }
        });
      });

      // Add the new entries to the end of the cache.
      entries = _.compact(entries);

      // Add the new entries to the credstore
      function addToCredStore(entry, callback) {
        var targetName = encoding.encodeObject(_.omit(entry, ['accessToken', 'refreshToken']));
        var credential = encoding.encodeObject({a: entry.accessToken, r: entry.refreshToken});
        var entryParts = splitEntry(targetName, credential);

        async.eachSeries(entryParts,
          function (entry, entrycb) {
            credStore.set(entry[0], entry[1], entrycb);
          },
          function (err) {
            if (!err) {
              self._entries.push(entry);
              callback(err);
            }
          });
        }

      async.eachSeries(entries, addToCredStore, callback);
    });
  },

  /**
   * Finds all entries in the cache that match all of the passed in values.
   * @param  {object}   query    This object will be compared to each entry in the cache.  Any entries that
   *                             match all of the values in this object will be returned.  All the values
   *                             in the passed in object must match values in a potentialy returned object
   *                             exactly.  The returned object may have more values than the passed in query
   *                             object.
   * @param  {TokenCacheFindCallback} callback
   */
  find: function find(query, callback) {
    var self = this;

    self._loadEntries(function (err, _entries) {
      if (err) { return callback(err); }

      var results = _.where(_entries, query);
      callback(null, results);
    });
  }
});

module.exports = CredStoreTokenCache;
<commit_msg>jshint fixes<commit_after>/**
* Copyright (c) Microsoft.  All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*   http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

'use strict';

var _ = require('underscore');
var async = require('async');
var es = require('event-stream');
var stream = require('readable-stream');
var util = require('util');

var Transform = stream.Transform;

var encoding = require('./token-cache-encoding');
var credStore = require('./win-credstore');

// Credential store has size limits, and tokens can
// go beyond them. 640k should be enough for everyone,
// right?
//
// As such, we have to be prepared to split a single
// credential across multiple credstore entries. This
// value controls the size of each chunk.

var MAX_CREDENTIAL_BYTES = 2048;

//
// Takes a single entry and credential, returns an array of
// entry/credential pairs ready to go to the credential store.
// @param{string} targetName target name that will be stored
// @param{string} tokens     string that will be stored as the
//                           actual credential.
//
// @returns {array} The possibly split set of entries for the
//                  credential store.
//
function splitEntry(targetName, tokens) {

  // Tokens are ascii, so # of bytes = # of characters.
  var numBytes = tokens.length;

  if (numBytes <= MAX_CREDENTIAL_BYTES) {
    return [ [targetName, tokens] ];
  }

  var numBlocks = Math.floor(numBytes / MAX_CREDENTIAL_BYTES);
  if (numBlocks % MAX_CREDENTIAL_BYTES !== 0) {
    ++numBlocks;
  }

  var blocks = [];

  for(var i = 0; i < numBlocks; ++i) {
    blocks.push([util.format('%s--%d-%d', targetName, i, numBlocks),
      tokens.substr(i * MAX_CREDENTIAL_BYTES, MAX_CREDENTIAL_BYTES)]);
  }

  return blocks;
}

//
// entry joiner. This is written as a transform stream since
// the logic for reading the cred store is also stream based.
//

function JoinerStream() {
  this.entries = {};
  this.splitRe = /^(.*)--(\d+)-(\d+)$/;
  Transform.call(this, {objectMode: true});
}

util.inherits(JoinerStream, Transform);

_.extend(JoinerStream.prototype, {
  _transform: function (chunk, encoding, callback) {
    // object mode, chunk is actually an object of the form
    // { targetName: n, credential: c }
    var targetName = chunk.targetName;
    var credential = chunk.credential;

    var match = targetName.match(this.splitRe);
    if (match !== null) {
      var mainTarget = match[1];
      var blockNum = +match[2];
      var numBlocks = +match[3];

      if (!_.has(this.entries, mainTarget)) {
        this.entries[mainTarget] = [];
      }

      this.entries[mainTarget][blockNum] = credential;

      if (_.compact(this.entries[mainTarget]).length === numBlocks) {
        this.push({
          targetName: mainTarget,
          credential: this.entries[mainTarget].join('')
        });
        delete this.entries[mainTarget];
      }
    } else {
      this.push(chunk);
    }
    callback();
  }
});

/**
 * Constructs a new token cache that stores credentials
 * in the Windows credential store.
 * @constructor
 */
 function CredStoreTokenCache() {
  this._entries = null;
 }

 //
// Internal helper functions for serializing and
// deserializing cache entries.
//

function deserializeBool(str) {
  return str.toLowerCase() === 'true';
}

function deserializeDate(str) {
  return new Date(str);
}

function deserializeNum(str) {
  return +str;
}

// Mapping of field names that need special handling
// on deserialization (typically type conversions)
// to the appropriate deserializer functions.
var fieldsToConvert = {
  expiresIn: deserializeNum,
  expiresOn: deserializeDate,
  isUserIdDisplayable: deserializeBool,
  isMRRT: deserializeBool
};

function deserializeEntry(entry) {
  return _.chain(entry)
    .pairs()
    .map(function (pair) {
      var key = pair[0], value = pair[1];
      if (_.has(fieldsToConvert, key)) {
        return [key, fieldsToConvert[key](value)];
      }
      return pair;
    })
    .object()
    .value();
}

_.extend(CredStoreTokenCache.prototype, {
  /**
  * Load the cache entries. Does a lazy load,
  * loads from OS on first request, otherwise
  * returns in-memory copy.
  *
  * @param {function(err, Array)} callback callback
  *                               receiving cache entries.
  */
  _loadEntries: function (callback) {
    var self = this;
    if (self._entries !== null) {
      return callback(null, self._entries);
    }

    var joiner = new JoinerStream();
    var entries = [];
    credStore.list()
      .pipe(joiner)
      .pipe(es.mapSync(function (entry) {
        // Most fields are stored in targetName,
        // access token and refresh token are stored in
        // credentials.
        var authResult = encoding.decodeObject(entry.targetName);
        var credential = encoding.decodeObject(new Buffer(entry.credential, 'hex').toString('utf8'));
        authResult.accessToken = credential.a;
        authResult.refreshToken = credential.r;

        return deserializeEntry(authResult);
      }))
      .on('data', function (entry) {
        entries.push(entry);
      })
      .on('end', function (err) {
        if (!err) {
          self._entries = entries;
        }
        callback(err, entries);
      });
  },

  isSecureCache: true,

  /**
   * Removes a collection of entries from the cache in a single batch operation.
   * @param  {Array}   entries  An array of cache entries to remove.
   * @param  {Function} callback This function is called when the operation is complete.  Any error is provided as the
   *                             first parameter.
   */
  remove: function remove(entries, callback) {
    var self = this;

    function shouldKeep(entry) {
      if (_.findWhere(entries, entry)) {
        return false;
      }
      return true;
    }

    self._loadEntries(function (err, _entries) {
      if (err) { return callback(err); }

      var grouped = _.groupBy(_entries, shouldKeep);
      var entriesToRemove = grouped[false] || [];
      var entriesToKeep = grouped[true] || [];

      function removeEntry(entry, removeCb) {
        var targetName = encoding.encodeObject(_.omit(entry, ['accessToken', 'refreshToken']));
        async.series([
          function (done) {
            credStore.remove(targetName, function () { done(); });
          },
          function (done) {
            credStore.remove(targetName + '--*', function () { done(); });
          }
        ], removeCb);
      }

      async.eachSeries(entriesToRemove, removeEntry, function (err) {
        if (err) { return callback(err); }
        self._entries = entriesToKeep;
        callback();
      });
    });
  },

  /**
   * Adds a collection of entries to the cache in a single batch operation.
   * @param {Array}   entries  An array of entries to add to the cache.
   * @param  {Function} callback This function is called when the operation is complete.  Any error is provided as the
   *                             first parameter.
   */
  add: function add(entries, callback) {
    var self = this;

    self._loadEntries(function (err, _entries) {
      if (err) { return callback(err); }

      // Remove any entries that are duplicates of the existing
      // cache elements.
      _.each(_entries, function(element) {
        _.each(entries, function(addElement, index) {
          if (_.isEqual(element, addElement)) {
            entries[index] = null;
          }
        });
      });

      // Add the new entries to the end of the cache.
      entries = _.compact(entries);

      // Add the new entries to the credstore
      function addToCredStore(entry, callback) {
        var targetName = encoding.encodeObject(_.omit(entry, ['accessToken', 'refreshToken']));
        var credential = encoding.encodeObject({a: entry.accessToken, r: entry.refreshToken});
        var entryParts = splitEntry(targetName, credential);

        async.eachSeries(entryParts,
          function (entry, entrycb) {
            credStore.set(entry[0], entry[1], entrycb);
          },
          function (err) {
            if (!err) {
              self._entries.push(entry);
              callback(err);
            }
          });
        }

      async.eachSeries(entries, addToCredStore, callback);
    });
  },

  /**
   * Finds all entries in the cache that match all of the passed in values.
   * @param  {object}   query    This object will be compared to each entry in the cache.  Any entries that
   *                             match all of the values in this object will be returned.  All the values
   *                             in the passed in object must match values in a potentialy returned object
   *                             exactly.  The returned object may have more values than the passed in query
   *                             object.
   * @param  {TokenCacheFindCallback} callback
   */
  find: function find(query, callback) {
    var self = this;

    self._loadEntries(function (err, _entries) {
      if (err) { return callback(err); }

      var results = _.where(_entries, query);
      callback(null, results);
    });
  }
});

module.exports = CredStoreTokenCache;
<|endoftext|>