<commit_before>// ==UserScript==
// @name         InnerCircle Enhancement Suite
// @namespace    http://jeltelagendijk.nl
// @version      0.2.1
// @description  Adds functionalities to InnerCircle
// @author       j3lte
// @match        https://www.theinnercircle.co/*
// @include      https://www.theinnercircle.co/*
// @grant        none
// ==/UserScript==

function ic_FunctionWrapper() {

    // Define global version
    var version = "0.2.1";

    // Variables
    var win = window;
    var $ic;

    if (typeof unsafeWindow != 'undefined') win = unsafeWindow;
    if (typeof unsafeWindow == 'undefined' ) $ic = jQuery;
    else $ic = unsafeWindow.jQuery || jQuery;

    if (!win.console) {
        win.console = {
            log : function(){}
        };
    }

    var defaults = {
        version : version,
        hideQuestionBlock : false,
        hideEmptyMatches : false,
        loadMemberOnHover : false,
        openInNew : true,
        nightMode : false
    };
    var opts = defaults;

    var optionLabels = [
        { identifier: 'option_hide_question', optionKey: 'hideQuestionBlock', label: 'Hide top question block' },
        { identifier: 'option_hide_empty', optionKey: 'hideEmptyMatches', label: 'Hide matches widget when there are no matches' },
        { identifier: 'option_load_hover', optionKey: 'loadMemberOnHover', label: 'Load members on hover' },
        { identifier: 'option_open_in_new', optionKey: 'openInNew', label: 'Open member links in a new window/tab' },
        { identifier: 'option_night_mode', optionKey: 'nightMode', label: 'Night mode (not finished)' }
    ];

    if ($ic.cookie) {
        if ($ic.cookie('ic_enhance_options')) {
            try {
                opts = JSON.parse($ic.cookie('ic_enhance_options'));
                if (opts.version !== defaults.version) {
                    var json = JSON.stringify(defaults);
                    $ic.cookie('ic_enhance_options', json, { path: '/' });
                }
            } catch (e) {}
        } else {
            var stringJSON = JSON.stringify(opts);
            $ic.cookie('ic_enhance_options', stringJSON, { path: '/' });
        }
    }

    // ADD CSS
    var css = [
        '#userBox { width: 158px; z-index:10000; padding: 2px; border: 1px solid #0099B0;position: fixed;left: 10px;top: 111px;font-size: 10px;background: #fff; }',
        '#userBox .profile_field { border: 0px solid #000; padding: 0px; line-height: 11px; clear: both; }',
        '#userBox .job_title { clear: both; color: #666; padding-bottom: 0; font-size: 10px; line-height: 11px; white-space: nowrap; overflow: hidden; border-top: 1px solid #CCC; border-bottom: 1px solid #CCC; }',
        '#userBox #user_photos { display: block; width: 158px; min-width: 1px; margin: 3px 0 3px 2px; }',
        '#userBox .rsTmb { width: 50px; margin: 0 2px 2px 0; }',
        '.close-user-box { cursor: pointer; }',
        '.google-link { position: absolute;background: rgba(255,255,255,0.3);left: 0;bottom: 0;width: 100%;text-align: center;color: #000;font-size: 10px;text-decoration: none; }',
        '#saveAndReloadOptions.disabled { opacity: 0.1; }',
        '.navigation a { padding: 8px 7px; }',
        '#enhance_tabs label { color:#FFF; }'
    ].join('\n');

    // NIGHT MODE
    css += [
        'body.night, .night .widget.white { background-color: #302E31; color: #fff; }',
        'body.night h1 { background-image: url("/images/h1_white.png"); }',
        'body.night h1, body.night h1 a, body.night .job_title, body.night .profile_field b { color:#fff; }',
        'body.night .rsDefault, body.night .rsDefault .rsOverflow, body.night .rsDefault .rsSlide, body.night .rsDefault .rsVideoFrameHolder, body.night .rsDefault .rsThumbs { background-color: #323232; }',
        'body.night .member_event_date, body.night .event_date, body.night .invites_count, body.night .my_trip_date { background-color: #0099B0; }',
        'body.night .grey, .message_accent { background-color: #151415; }',
        'body.night .featured_box, body.night .interest_box, body.night .ui-tooltip { color: #151415; }',
        'body.night .nearby-block { border-top:5px solid #151415; }',
        'body.night .widget, body.night .hotposts_container { color: #302E31; }'
    ].join('\n');

    if (typeof GM_addStyle != "undefined") {
        GM_addStyle(css);
    } else if (typeof PRO_addStyle != "undefined") {
        PRO_addStyle(css);
    } else if (typeof addStyle != "undefined") {
        addStyle(css);
    } else {
        var node = document.createElement("style");
        node.type = "text/css";
        node.appendChild(document.createTextNode(css));
        var heads = document.getElementsByTagName("head");
        if (heads.length > 0) {
            heads[0].appendChild(node);
        } else {
            // no head yet, stick it whereever
            document.documentElement.appendChild(node);
        }
    }

    // Body element
    var $body = $ic('body');

    if (opts.nightMode) {
        $body.addClass('night');
    };

    // $membox is used for the downloaded content
    var $memBox = $ic('
');

    // Userbox is the box that is shown when a member is loaded
    var $userBox = $ic('
');
    $body.append($userBox);

    // Loader that is shown at the bottom right of the window
    var $loader = $ic('

');
    $body.append($loader);

    function showLoader(message) {
        $loader.text(message);
        if ($loader.is(':hidden')) {
            $loader.show();
        }
    }

    function hideLoader() {
        if (!$loader.is(':hidden')) {
            $loader.hide();
        }
    }

    // Regex to determine whether a link contains a member link
    var memLinkRegEx = /(http|https):\/\/www\.theinnercircle\.co\/member\/\d+/;
    var timer, ev, link, userBoxes  = {};

    // Loads the member page
    function loadMember(link) {
        showLoader('Loading member...');
        $memBox.load(link, function(){
            //callback after your data is in loaded into body.
            var userName = $memBox.find('.username').parent();
            var jobTitle = $memBox.find('.job_title');
            var thumbs = $memBox.find('#user_photos');
            var profileFields = $memBox.find('.profile_field');

            $userBox.html('');
            $userBox.append(userName);
            $userBox.append(jobTitle);
            if (thumbs.find('.rsImg').length) {

                var h = Math.round(thumbs.find('.rsImg').length / 3) * 50;
                thumbs.css('height', h + 'px');

                thumbs.find('.rsImg').each(function (i){
                    if ($ic(this).data('rsbigimg')) {
                        $ic(this).attr('href', $ic(this).data('rsbigimg'));
                    }
                    $ic(this).attr('target',"_blank");
                });

                $userBox.append(thumbs);
            }
            $userBox.append(profileFields);
            $userBox.append('');

            userBoxes[link] = $userBox.html();
            link = null;

            hideLoader();
            $userBox.fadeIn(500);

            win.console.log('[IC Enhancement Suite] :: User shown');
        });
    }

    // Load member on hover
    if (opts.loadMemberOnHover){
        $body.on('mouseover', 'a', function(event) {
            ev = event;
            // Only set timer if it has a target link, matches a member and is not equal to current member
            if (ev.currentTarget && ev.currentTarget.href && memLinkRegEx.test(ev.currentTarget.href) && (ev.currentTarget.href !== win.location.href) && ev.currentTarget.href.indexOf('#') === -1) {

                link = ev.currentTarget.href;

                if (typeof(userBoxes[link]) !== 'undefined' && userBoxes[link] !== null) {

                    $userBox.html(userBoxes[link]);
                    $userBox.show();

                } else {
                    showLoader('Loading member in 7s...');
                    timer = win.setTimeout(function() {
                        // Execute when timer reached
                        win.console.log('[IC Enhancement Suite] :: Loading user : ' + link);
                        loadMember(link);

                        timer = null;
                        ev = null;

                    }, 7000);

                }
            }
        });

        $body.on('mouseout', 'a', function(event) {
            if (timer) {
                win.clearTimeout(timer);
                ev = null;
            }
            hideLoader();
        });

        // Userbox clicks
        $body.on('click', '#userBox .username', open_new);
        $body.on('click','.close-user-box', function() { $userBox.hide(); });
    }

    function open_new() {
        $ic(this).target = "_blank";
        if ($ic(this).prop('href')) {
            win.open($ic(this).prop('href'));
        } else if ($ic(this).prop('src')) {
            win.open($ic(this).prop('src'));
        }
        if (timer) {
            win.clearTimeout(timer);
            ev = null;
        }
        return false;
    }

    if (opts.openInNew) {
        // Open online members in new tab/window
        $body.on('click', '.online_box a', open_new);

        // Open featured in new tab/window
        $body.on('click','.featured_box a', open_new);

        // Open interest in new tab/window
        $body.on('click','.interest_box a', open_new);
    }

    // Remove match box if there are no matches
    if (opts.hideEmptyMatches && $ic('.potential_match .match-last').length) {
        console.log('[IC Enhancement Suite] :: No matches, hiding box');
        $ic('.potential_match').hide();
    }

    // Remove nearby question block
    if (opts.hideQuestionBlock) {
        $ic('.nearby-block.question').css('display', 'none');
    }

    // Open fancybox images originals
    $body.on('click','.fancybox-image', open_new);

    // Add 'Search with google' link to popup images
    $body.on('mouseover', '.fancybox-image', function(event) {
        ev = event;
        if ($ic(this).attr('src') && $ic(this).parent().find('.google-link').length === 0) {
            var href = $ic(this).attr('src');
            var link = $ic('Search with google');
            $ic(this).parent().append(link);
        }
    });

    // Enhance Dialog (Options screen)
    $ic('.navigation').append('Enhance');
    var $enhDialog = $ic('
');
    function createEnhanceDialog() {
        var dialogContent =  '
\n';
        for (var i = 0; i < optionLabels.length; i++) {
            var opt = optionLabels[i];
            dialogContent += '    <input type="checkbox" class="option_check" data-option="' + opt.optionKey + '" id="' + opt.identifier + '" name="' + opt.identifier + '" ' + (opts[opt.optionKey] ? 'checked="checked"' : '') + '>\n';
            dialogContent += '    ' + opt.label + '\n';
            dialogContent += '

\n';
        }
        // SUBMIT
        dialogContent += '

\n';
        dialogContent += '    Save and reload\n';
        dialogContent += '
';
        $enhDialog.html(dialogContent);
    }

    $body.append($enhDialog);
    $enhDialog.dialog({
        closeOnEscape: true,
        modal: true,
        autoOpen: false,
        width: 400
    });

    $ic('a.ic_enhance_suite').on('click', function (e){
        e.preventDefault();
        createEnhanceDialog();
        $enhDialog.dialog('open');
    });

    $body.on('click', '.option_check',function (e){
        $ic('#saveAndReloadOptions').removeClass('disabled');
    });

    $body.on('click', '#saveAndReloadOptions',function (e){
        e.preventDefault();
        if ($ic(this).hasClass('disabled')) {
            return false;
        }
        opts = { version : version };
        $ic('.option_check').each(function () {
            var key = $ic(this).data('option');
            opts[key] = $ic(this).is(':checked');
        });
        var json = JSON.stringify(opts);
        $.cookie('ic_enhance_options', json, { path: '/' });
        win.location.reload();
    });

    // Fix the inplace editor
    $('.inplace_view').each(function (){
        var $this = $(this);
        var html = $this.html();
        $this.html(html.replace('\n','

'));
    });
    //$($('.inplace_view')[10]).html($($('.inplace_view')[10]).html().replace('\n','

'))

    // DEBUG
    win.console.log("[IC Enhancement Suite] :: Succesfully loaded Suite");
}

var ic_ScriptObject = document.createElement("script");
ic_ScriptObject.textContent = "(" + ic_FunctionWrapper.toString() + ")();";
document.body.appendChild(ic_ScriptObject);<commit_msg>Fixing typo<commit_after>// ==UserScript==
// @name         InnerCircle Enhancement Suite
// @namespace    http://jeltelagendijk.nl
// @version      0.2.2
// @description  Adds functionalities to InnerCircle
// @author       j3lte
// @match        https://www.theinnercircle.co/*
// @include      https://www.theinnercircle.co/*
// @grant        none
// ==/UserScript==

function ic_FunctionWrapper() {

    // Define global version
    var version = "0.2.2";

    // Variables
    var win = window;
    var $ic;

    if (typeof unsafeWindow != 'undefined') win = unsafeWindow;
    if (typeof unsafeWindow == 'undefined' ) $ic = jQuery;
    else $ic = unsafeWindow.jQuery || jQuery;

    if (!win.console) {
        win.console = {
            log : function(){}
        };
    }

    var defaults = {
        version : version,
        hideQuestionBlock : false,
        hideEmptyMatches : false,
        loadMemberOnHover : false,
        openInNew : true,
        nightMode : false
    };
    var opts = defaults;

    var optionLabels = [
        { identifier: 'option_hide_question', optionKey: 'hideQuestionBlock', label: 'Hide top question block' },
        { identifier: 'option_hide_empty', optionKey: 'hideEmptyMatches', label: 'Hide matches widget when there are no matches' },
        { identifier: 'option_load_hover', optionKey: 'loadMemberOnHover', label: 'Load members on hover' },
        { identifier: 'option_open_in_new', optionKey: 'openInNew', label: 'Open member links in a new window/tab' },
        { identifier: 'option_night_mode', optionKey: 'nightMode', label: 'Night mode (not finished)' }
    ];

    if ($ic.cookie) {
        if ($ic.cookie('ic_enhance_options')) {
            try {
                opts = JSON.parse($ic.cookie('ic_enhance_options'));
                if (opts.version !== defaults.version) {
                    var json = JSON.stringify(defaults);
                    $ic.cookie('ic_enhance_options', json, { path: '/' });
                }
            } catch (e) {}
        } else {
            var stringJSON = JSON.stringify(opts);
            $ic.cookie('ic_enhance_options', stringJSON, { path: '/' });
        }
    }

    // ADD CSS
    var css = [
        '#userBox { width: 158px; z-index:10000; padding: 2px; border: 1px solid #0099B0;position: fixed;left: 10px;top: 111px;font-size: 10px;background: #fff; }',
        '#userBox .profile_field { border: 0px solid #000; padding: 0px; line-height: 11px; clear: both; }',
        '#userBox .job_title { clear: both; color: #666; padding-bottom: 0; font-size: 10px; line-height: 11px; white-space: nowrap; overflow: hidden; border-top: 1px solid #CCC; border-bottom: 1px solid #CCC; }',
        '#userBox #user_photos { display: block; width: 158px; min-width: 1px; margin: 3px 0 3px 2px; }',
        '#userBox .rsTmb { width: 50px; margin: 0 2px 2px 0; }',
        '.close-user-box { cursor: pointer; }',
        '.google-link { position: absolute;background: rgba(255,255,255,0.3);left: 0;bottom: 0;width: 100%;text-align: center;color: #000;font-size: 10px;text-decoration: none; }',
        '#saveAndReloadOptions.disabled { opacity: 0.1; }',
        '.navigation a { padding: 8px 7px; }',
        '#enhance_tabs label { color:#FFF; }'
    ].join('\n');

    // NIGHT MODE
    css += [
        'body.night, .night .widget.white { background-color: #302E31; color: #fff; }',
        'body.night h1 { background-image: url("/images/h1_white.png"); }',
        'body.night h1, body.night h1 a, body.night .job_title, body.night .profile_field b { color:#fff; }',
        'body.night .rsDefault, body.night .rsDefault .rsOverflow, body.night .rsDefault .rsSlide, body.night .rsDefault .rsVideoFrameHolder, body.night .rsDefault .rsThumbs { background-color: #323232; }',
        'body.night .member_event_date, body.night .event_date, body.night .invites_count, body.night .my_trip_date { background-color: #0099B0; }',
        'body.night .grey, body.night .message_accent { background-color: #151415; }',
        'body.night .featured_box, body.night .interest_box, body.night .ui-tooltip { color: #151415; }',
        'body.night .nearby-block { border-top:5px solid #151415; }',
        'body.night .widget, body.night .hotposts_container { color: #302E31; }'
    ].join('\n');

    if (typeof GM_addStyle != "undefined") {
        GM_addStyle(css);
    } else if (typeof PRO_addStyle != "undefined") {
        PRO_addStyle(css);
    } else if (typeof addStyle != "undefined") {
        addStyle(css);
    } else {
        var node = document.createElement("style");
        node.type = "text/css";
        node.appendChild(document.createTextNode(css));
        var heads = document.getElementsByTagName("head");
        if (heads.length > 0) {
            heads[0].appendChild(node);
        } else {
            // no head yet, stick it whereever
            document.documentElement.appendChild(node);
        }
    }

    // Body element
    var $body = $ic('body');

    if (opts.nightMode) {
        $body.addClass('night');
    };

    // $membox is used for the downloaded content
    var $memBox = $ic('
');

    // Userbox is the box that is shown when a member is loaded
    var $userBox = $ic('
');
    $body.append($userBox);

    // Loader that is shown at the bottom right of the window
    var $loader = $ic('

');
    $body.append($loader);

    function showLoader(message) {
        $loader.text(message);
        if ($loader.is(':hidden')) {
            $loader.show();
        }
    }

    function hideLoader() {
        if (!$loader.is(':hidden')) {
            $loader.hide();
        }
    }

    // Regex to determine whether a link contains a member link
    var memLinkRegEx = /(http|https):\/\/www\.theinnercircle\.co\/member\/\d+/;
    var timer, ev, link, userBoxes  = {};

    // Loads the member page
    function loadMember(link) {
        showLoader('Loading member...');
        $memBox.load(link, function(){
            //callback after your data is in loaded into body.
            var userName = $memBox.find('.username').parent();
            var jobTitle = $memBox.find('.job_title');
            var thumbs = $memBox.find('#user_photos');
            var profileFields = $memBox.find('.profile_field');

            $userBox.html('');
            $userBox.append(userName);
            $userBox.append(jobTitle);
            if (thumbs.find('.rsImg').length) {

                var h = Math.round(thumbs.find('.rsImg').length / 3) * 50;
                thumbs.css('height', h + 'px');

                thumbs.find('.rsImg').each(function (i){
                    if ($ic(this).data('rsbigimg')) {
                        $ic(this).attr('href', $ic(this).data('rsbigimg'));
                    }
                    $ic(this).attr('target',"_blank");
                });

                $userBox.append(thumbs);
            }
            $userBox.append(profileFields);
            $userBox.append('');

            userBoxes[link] = $userBox.html();
            link = null;

            hideLoader();
            $userBox.fadeIn(500);

            win.console.log('[IC Enhancement Suite] :: User shown');
        });
    }

    // Load member on hover
    if (opts.loadMemberOnHover){
        $body.on('mouseover', 'a', function(event) {
            ev = event;
            // Only set timer if it has a target link, matches a member and is not equal to current member
            if (ev.currentTarget && ev.currentTarget.href && memLinkRegEx.test(ev.currentTarget.href) && (ev.currentTarget.href !== win.location.href) && ev.currentTarget.href.indexOf('#') === -1) {

                link = ev.currentTarget.href;

                if (typeof(userBoxes[link]) !== 'undefined' && userBoxes[link] !== null) {

                    $userBox.html(userBoxes[link]);
                    $userBox.show();

                } else {
                    showLoader('Loading member in 7s...');
                    timer = win.setTimeout(function() {
                        // Execute when timer reached
                        win.console.log('[IC Enhancement Suite] :: Loading user : ' + link);
                        loadMember(link);

                        timer = null;
                        ev = null;

                    }, 7000);

                }
            }
        });

        $body.on('mouseout', 'a', function(event) {
            if (timer) {
                win.clearTimeout(timer);
                ev = null;
            }
            hideLoader();
        });

        // Userbox clicks
        $body.on('click', '#userBox .username', open_new);
        $body.on('click','.close-user-box', function() { $userBox.hide(); });
    }

    function open_new() {
        $ic(this).target = "_blank";
        if ($ic(this).prop('href')) {
            win.open($ic(this).prop('href'));
        } else if ($ic(this).prop('src')) {
            win.open($ic(this).prop('src'));
        }
        if (timer) {
            win.clearTimeout(timer);
            ev = null;
        }
        return false;
    }

    if (opts.openInNew) {
        // Open online members in new tab/window
        $body.on('click', '.online_box a', open_new);

        // Open featured in new tab/window
        $body.on('click','.featured_box a', open_new);

        // Open interest in new tab/window
        $body.on('click','.interest_box a', open_new);
    }

    // Remove match box if there are no matches
    if (opts.hideEmptyMatches && $ic('.potential_match .match-last').length) {
        console.log('[IC Enhancement Suite] :: No matches, hiding box');
        $ic('.potential_match').hide();
    }

    // Remove nearby question block
    if (opts.hideQuestionBlock) {
        $ic('.nearby-block.question').css('display', 'none');
    }

    // Open fancybox images originals
    $body.on('click','.fancybox-image', open_new);

    // Add 'Search with google' link to popup images
    $body.on('mouseover', '.fancybox-image', function(event) {
        ev = event;
        if ($ic(this).attr('src') && $ic(this).parent().find('.google-link').length === 0) {
            var href = $ic(this).attr('src');
            var link = $ic('Search with google');
            $ic(this).parent().append(link);
        }
    });

    // Enhance Dialog (Options screen)
    $ic('.navigation').append('Enhance');
    var $enhDialog = $ic('
');
    function createEnhanceDialog() {
        var dialogContent =  '
\n';
        for (var i = 0; i < optionLabels.length; i++) {
            var opt = optionLabels[i];
            dialogContent += '    <input type="checkbox" class="option_check" data-option="' + opt.optionKey + '" id="' + opt.identifier + '" name="' + opt.identifier + '" ' + (opts[opt.optionKey] ? 'checked="checked"' : '') + '>\n';
            dialogContent += '    ' + opt.label + '\n';
            dialogContent += '

\n';
        }
        // SUBMIT
        dialogContent += '

\n';
        dialogContent += '    Save and reload\n';
        dialogContent += '
';
        $enhDialog.html(dialogContent);
    }

    $body.append($enhDialog);
    $enhDialog.dialog({
        closeOnEscape: true,
        modal: true,
        autoOpen: false,
        width: 400
    });

    $ic('a.ic_enhance_suite').on('click', function (e){
        e.preventDefault();
        createEnhanceDialog();
        $enhDialog.dialog('open');
    });

    $body.on('click', '.option_check',function (e){
        $ic('#saveAndReloadOptions').removeClass('disabled');
    });

    $body.on('click', '#saveAndReloadOptions',function (e){
        e.preventDefault();
        if ($ic(this).hasClass('disabled')) {
            return false;
        }
        opts = { version : version };
        $ic('.option_check').each(function () {
            var key = $ic(this).data('option');
            opts[key] = $ic(this).is(':checked');
        });
        var json = JSON.stringify(opts);
        $.cookie('ic_enhance_options', json, { path: '/' });
        win.location.reload();
    });

    // Fix the inplace editor
    $('.inplace_view').each(function (){
        var $this = $(this);
        var html = $this.html();
        $this.html(html.replace('\n','

'));
    });
    //$($('.inplace_view')[10]).html($($('.inplace_view')[10]).html().replace('\n','

'))

    // DEBUG
    win.console.log("[IC Enhancement Suite] :: Succesfully loaded Suite");
}

var ic_ScriptObject = document.createElement("script");
ic_ScriptObject.textContent = "(" + ic_FunctionWrapper.toString() + ")();";
document.body.appendChild(ic_ScriptObject);<|endoftext|>


<commit_before>Org = new Mongo.Collection('org');

/**
 * A Organization in Wekan. A Enterprise in Trello.
 */
Org.attachSchema(
  new SimpleSchema({
    orgDisplayName: {
      /**
       * the name to display for the organization
       */
      type: String,
      optional: true,
    },
    orgDesc: {
      /**
       * the description the organization
       */
      type: String,
      optional: true,
      max: 190,
    },
    orgShortName: {
      /**
       * short name of the organization
       */
      type: String,
      optional: true,
      max: 255,
    },
    orgWebsite: {
      /**
       * website of the organization
       */
      type: String,
      optional: true,
      max: 255,
    },
    orgIsActive: {
      /**
       * status of the organization
       */
      type: Boolean,
      optional: true,
    },
    createdAt: {
      /**
       * creation date of the organization
       */
      type: Date,
      denyUpdate: false,
      // eslint-disable-next-line consistent-return
      autoValue() {
        if (this.isInsert) {
          return new Date();
        } else if (this.isUpsert) {
          return { $setOnInsert: new Date() };
        } else {
          this.unset();
        }
      },
    },
    modifiedAt: {
      type: Date,
      denyUpdate: false,
      // eslint-disable-next-line consistent-return
      autoValue() {
        if (this.isInsert || this.isUpsert || this.isUpdate) {
          return new Date();
        } else {
          this.unset();
        }
      },
    },
  }),
);

if (Meteor.isServer) {
  Org.allow({
    insert(userId, doc) {
      const user = Users.findOne({
        _id: userId,
      });
      if ((user && user.isAdmin) || (Meteor.user() && Meteor.user().isAdmin))
        return true;
      if (!user) {
        return false;
      }
      return doc._id === userId;
    },
    update(userId, doc) {
      const user = Users.findOne({
        _id: userId,
      });
      if ((user && user.isAdmin) || (Meteor.user() && Meteor.user().isAdmin))
        return true;
      if (!user) {
        return false;
      }
      return doc._id === userId;
    },
    remove(userId, doc) {
      const user = Users.findOne({
        _id: userId,
      });
      if ((user && user.isAdmin) || (Meteor.user() && Meteor.user().isAdmin))
        return true;
      if (!user) {
        return false;
      }
      return doc._id === userId;
    },
    fetch: [],
  });

  Meteor.methods({
    setCreateOrg(
      orgDisplayName,
      orgDesc,
      orgShortName,
      orgWebsite,
      orgIsActive,
    ) {
      if (Meteor.user() && Meteor.user().isAdmin) {
        check(orgDisplayName, String);
        check(orgDesc, String);
        check(orgShortName, String);
        check(orgWebsite, String);
        check(orgIsActive, Boolean);

        const nOrgNames = Org.find({ orgShortName }).count();
        if (nOrgNames > 0) {
          throw new Meteor.Error('orgname-already-taken');
        } else {
          Org.insert({
            orgDisplayName,
            orgDesc,
            orgShortName,
            orgWebsite,
            orgIsActive,
          });
        }
      }
    },

    setOrgDisplayName(org, orgDisplayName) {
      if (Meteor.user() && Meteor.user().isAdmin) {
        check(org, Object);
        check(orgDisplayName, String);
        Org.update(org, {
          $set: { orgDisplayName: orgDisplayNameorgShortName },
        });
      }
    },

    setOrgDesc(org, orgDesc) {
      if (Meteor.user() && Meteor.user().isAdmin) {
        check(org, Object);
        check(orgDesc, String);
        Org.update(org, {
          $set: { orgDesc: orgDesc },
        });
      }
    },

    setOrgShortName(org, orgShortName) {
      if (Meteor.user() && Meteor.user().isAdmin) {
        check(org, Object);
        check(orgShortName, String);
        Org.update(org, {
          $set: { orgShortName: orgShortName },
        });
      }
    },

    setOrgIsActive(org, orgIsActive) {
      if (Meteor.user() && Meteor.user().isAdmin) {
        check(org, Object);
        check(orgIsActive, Boolean);
        Org.update(org, {
          $set: { orgIsActive: orgIsActive },
        });
      }
    },

    setOrgAllFields(
      org,
      orgDisplayName,
      orgDesc,
      orgShortName,
      orgWebsite,
      orgIsActive,
    ) {
      if (Meteor.user() && Meteor.user().isAdmin) {
        check(org, Object);
        check(orgDisplayName, String);
        check(orgDesc, String);
        check(orgShortName, String);
        check(orgWebsite, String);
        check(orgIsActive, Boolean);
        Org.update(org, {
          $set: {
            orgDisplayName: orgDisplayName,
            orgDesc: orgDesc,
            orgShortName: orgShortName,
            orgWebsite: orgWebsite,
            orgIsActive: orgIsActive,
          },
        });
      }
    },
  });
}

if (Meteor.isServer) {
  // Index for Organization name.
  Meteor.startup(() => {
    // Org._collection._ensureIndex({ name: -1 });
    Org._collection._ensureIndex({ orgDisplayName: -1 });
  });
}

export default Org;
<commit_msg>Added some controls and warning messages when user try to delete an organization or team that has at least one user belongs to it<commit_after>Org = new Mongo.Collection('org');

/**
 * A Organization in Wekan. A Enterprise in Trello.
 */
Org.attachSchema(
  new SimpleSchema({
    orgDisplayName: {
      /**
       * the name to display for the organization
       */
      type: String,
      optional: true,
    },
    orgDesc: {
      /**
       * the description the organization
       */
      type: String,
      optional: true,
      max: 190,
    },
    orgShortName: {
      /**
       * short name of the organization
       */
      type: String,
      optional: true,
      max: 255,
    },
    orgWebsite: {
      /**
       * website of the organization
       */
      type: String,
      optional: true,
      max: 255,
    },
    orgIsActive: {
      /**
       * status of the organization
       */
      type: Boolean,
      optional: true,
    },
    createdAt: {
      /**
       * creation date of the organization
       */
      type: Date,
      denyUpdate: false,
      // eslint-disable-next-line consistent-return
      autoValue() {
        if (this.isInsert) {
          return new Date();
        } else if (this.isUpsert) {
          return { $setOnInsert: new Date() };
        } else {
          this.unset();
        }
      },
    },
    modifiedAt: {
      type: Date,
      denyUpdate: false,
      // eslint-disable-next-line consistent-return
      autoValue() {
        if (this.isInsert || this.isUpsert || this.isUpdate) {
          return new Date();
        } else {
          this.unset();
        }
      },
    },
  }),
);

if (Meteor.isServer) {
  Org.allow({
    insert(userId, doc) {
      const user = Users.findOne({
        _id: userId,
      });
      if ((user && user.isAdmin) || (Meteor.user() && Meteor.user().isAdmin))
        return true;
      if (!user) {
        return false;
      }
      return doc._id === userId;
    },
    update(userId, doc) {
      const user = Users.findOne({
        _id: userId,
      });
      if ((user && user.isAdmin) || (Meteor.user() && Meteor.user().isAdmin))
        return true;
      if (!user) {
        return false;
      }
      return doc._id === userId;
    },
    remove(userId, doc) {
      const user = Users.findOne({
        _id: userId,
      });
      if ((user && user.isAdmin) || (Meteor.user() && Meteor.user().isAdmin))
        return true;
      if (!user) {
        return false;
      }
      return doc._id === userId;
    },
    fetch: [],
  });

  Meteor.methods({
    setCreateOrg(
      orgDisplayName,
      orgDesc,
      orgShortName,
      orgWebsite,
      orgIsActive,
    ) {
      if (Meteor.user() && Meteor.user().isAdmin) {
        check(orgDisplayName, String);
        check(orgDesc, String);
        check(orgShortName, String);
        check(orgWebsite, String);
        check(orgIsActive, Boolean);

        const nOrgNames = Org.find({ orgShortName }).count();
        if (nOrgNames > 0) {
          throw new Meteor.Error('orgname-already-taken');
        } else {
          Org.insert({
            orgDisplayName,
            orgDesc,
            orgShortName,
            orgWebsite,
            orgIsActive,
          });
        }
      }
    },

    setOrgDisplayName(org, orgDisplayName) {
      if (Meteor.user() && Meteor.user().isAdmin) {
        check(org, Object);
        check(orgDisplayName, String);
        Org.update(org, {
          $set: { orgDisplayName: orgDisplayNameorgShortName },
        });
      }
    },

    setOrgDesc(org, orgDesc) {
      if (Meteor.user() && Meteor.user().isAdmin) {
        check(org, Object);
        check(orgDesc, String);
        Org.update(org, {
          $set: { orgDesc: orgDesc },
        });
      }
    },

    setOrgShortName(org, orgShortName) {
      if (Meteor.user() && Meteor.user().isAdmin) {
        check(org, Object);
        check(orgShortName, String);
        Org.update(org, {
          $set: { orgShortName: orgShortName },
        });
      }
    },

    setOrgIsActive(org, orgIsActive) {
      if (Meteor.user() && Meteor.user().isAdmin) {
        check(org, Object);
        check(orgIsActive, Boolean);
        Org.update(org, {
          $set: { orgIsActive: orgIsActive },
        });
      }
    },

    setOrgAllFields(
      org,
      orgDisplayName,
      orgDesc,
      orgShortName,
      orgWebsite,
      orgIsActive,
    ) {
      if (Meteor.user() && Meteor.user().isAdmin) {
        check(org, Object);
        check(orgDisplayName, String);
        check(orgDesc, String);
        check(orgShortName, String);
        check(orgWebsite, String);
        check(orgIsActive, Boolean);
        Org.update(org, {
          $set: {
            orgDisplayName: orgDisplayName,
            orgDesc: orgDesc,
            orgShortName: orgShortName,
            orgWebsite: orgWebsite,
            orgIsActive: orgIsActive,
          },
        });
      }
    },
  });
}

if (Meteor.isServer) {
  // Index for Organization name.
  Meteor.startup(() => {
    // Org._collection._ensureIndex({ name: -1 });
    Org._collection._ensureIndex({ orgDisplayName: -1 });
  });
}

export default Org;
<|endoftext|>


<commit_before>/**
 * moveSelect jQuery plugin
 * -----------------+
 * v 0.1 08/08/2013
 *
 * Move options from one multi-select box to another
 *
 */
(function( $ ) {

	$.fn.moveSelect = function( options ) {
		var El = this;

		// Extend our default options with those provided.
		var opts = $.extend( {}, $.fn.moveSelect.defaults, options );

		/**
		 * Box class
		 *
		 * by converting the jquery elements to their ids and making use of native
		 * javascript speeds things up a bit.
		 *
		 * @type {Object}
		 */
		var BOX = {
			cache: new Object(),
			init: function(element) {
				var id = element.attr('id');
				var select = document.getElementById(id);
				var node;
				BOX.cache[id] = new Array();

				var cache = BOX.cache[id];
				element.find('option').each(function(){
					cache.push({value: $(this).val(), text: $(this).text(), displayed: 1});
				});
			},
			redisplay: function(element) {
				var id = element.attr('id');
				// Repopulate HTML select box from cache
				var select = document.getElementById(id);

				select.options.length = 0; // clear all options

				for (var i = 0, j = BOX.cache[id].length; i < j; i++) {
					var node = BOX.cache[id][i];
					if (node.displayed) {
						select.options[select.options.length] = new Option(node.text, node.value, false, false);
					}
				}
			},
			filter: function(element, text) {
				var id = element.attr('id');
				// Redisplay the HTML select box, displaying only the choices containing ALL
				// the words in text. (It's an AND search.)
				var tokens = text.toLowerCase().split(/\s+/);
				var node, token;
				for (var i = 0; (node = BOX.cache[id][i]); i++) {
					node.displayed = 1;
					for (var j = 0; (token = tokens[j]); j++) {
						if (node.text.toLowerCase().indexOf(token) == -1) {
							node.displayed = 0;
						}
					}
				}
				BOX.redisplay(id);
			},
			delete_from_cache: function(element, value) {
				var id = element.attr('id');
				var node, delete_index = null;
				for (var i = 0; (node = BOX.cache[id][i]); i++) {
					if (node.value == value) {
						delete_index = i;
						break;
					}
				}
				var j = BOX.cache[id].length - 1;
				for (var i = delete_index; i < j; i++) {
					BOX.cache[id][i] = BOX.cache[id][i+1];
				}
				BOX.cache[id].length--;
			},
			add_to_cache: function(element, option) {
				var id = element.attr('id');
				BOX.cache[id].push({value: option.value, text: option.text, displayed: 1});
			},
			cache_contains: function(element, value) {
				var id = element.attr('id');
				// Check if an item is contained in the cache
				var node;
				for (var i = 0; (node = BOX.cache[id][i]); i++) {
					if (node.value == value) {
						return true;
					}
				}
				return false;
			},
			move: function(from, to) {
				var from_box = document.getElementById(from.attr('id'));
				var to_box = document.getElementById(to.attr('id'));
				var option;
				for (var i = 0; (option = from_box.options[i]); i++) {
					if (option.selected && BOX.cache_contains(from, option.value)) {
						BOX.add_to_cache(to, {value: option.value, text: option.text, displayed: 1});
						BOX.delete_from_cache(from, option.value);
					}
				}
				BOX.sort(from).redisplay(from);
				BOX.sort(to).redisplay(to);
			},
			move_all: function(from, to) {
				var from_box = document.getElementById(from.attr('id'));
				var to_box = document.getElementById(to.attr('id'));
				var option;
				for (var i = 0; (option = from_box.options[i]); i++) {
					if (BOX.cache_contains(from, option.value)) {
						BOX.add_to_cache(to, {value: option.value, text: option.text, displayed: 1});
						BOX.delete_from_cache(from, option.value);
					}
				}
				BOX.sort(from).redisplay(from);
				BOX.sort(to).redisplay(to);
			},
			sort: function(element) {
				var id = element.attr('id');
				BOX.cache[id].sort( function(a, b) {
					a = a.text.toLowerCase();
					b = b.text.toLowerCase();
					try {
						if (a > b) return 1;
						if (a < b) return -1;
					}
					catch (e) {
						// silently fail on IE 'unknown' exception
					}
					return 0;
				} );
				return this;
			}
		};

		//check base and container, initialise them
		var base = (opts.base != '') ? opts.base : El.find('#move-select-base');
		BOX.init(base);

		var container = (opts.container != '') ? opts.container : El.find('#move-select-container');
		BOX.init(container);

		//check save btn and attach save handler
		var btn_save = (opts.save != '') ? opts.btn_save : El.find('#move-select-save');

		btn_save.click(function(e){
			//unset any selected options in the base element, just in case
			base.find('option').each(function(){
				$(this).attr('selected', false);
			});

			//select all options in the container element
			container.find('option').each(function(){
				$(this).attr('selected', true);
			});
		});

		//check btn_in and add handler
		var btn_in = (opts.btn_in != '') ? opts.btn_in : El.find('#move-select-in');

		btn_in.click(function(e) {
			e.preventDefault();
			base.find('option:selected').each(function(){
				//remove the role from the select list
				BOX.move(base, container);
			});
		});

		//check btn_out and add handler
		var btn_out = (opts.btn_out != '') ? opts.btn_out : El.find('#move-select-out');

		btn_out.click(function(e) {
			e.preventDefault();
			container.find('option:selected').each(function(){
				//add the role from the select list
				BOX.move(container, base);
			});
		});

		//check btn_empty and add handler (empties the container)
		var btn_empty = (opts.btn_empty != '') ? opts.btn_empty : El.find('#move-select-empty');

		btn_empty.click(function(e){
			e.preventDefault();
			BOX.move_all(container, base);
		});

		//check btn_fill and add handler (fills the container with all base's options)
		var btn_fill = (opts.btn_fill != '') ? opts.btn_fill : El.find('#move-select-fill');

		btn_fill.click(function(e){
			e.preventDefault();
			BOX.move_all(base, container);
		});

		//Set up some keyboard controls
		var setupControl = function (from_el, to_el, reverse) {
			var arrowCode = 37;
			if(typeof reverse == 'undefined')
			{
				arrowCode = 39;
			}

			from_el.keydown(function(e){
				var from = from_el.attr('id');
				// right|left arrow -- move across
				if ((e.which && e.which == arrowCode) || (e.keyCode && e.keyCode == arrowCode)) {
					e.preventDefault();
					var old_index = from.selectedIndex;
					BOX.move(from_el, to_el);
					from.selectedIndex = (old_index == from.length) ? from.length - 1 : old_index;
				}
				// down arrow -- wrap around
				if ((e.which && event.which == 40) || (e.keyCode && e.keyCode == 40)) {
					from.selectedIndex = (from.length == from.selectedIndex + 1) ? 0 : from.selectedIndex + 1;
				}
				// up arrow -- wrap around
				if ((e.which && e.which == 38) || (e.keyCode && e.keyCode == 38)) {
					from.selectedIndex = (from.selectedIndex == 0) ? from.length - 1 : from.selectedIndex - 1;
				}
				return true;
			});
		};

		setupControl(base, container);
		setupControl(container, base, true);

		if(typeof opts.filter == Object)
		{
			var setupFilter = function(filter, source, destination) {
				filter.keyup(function(e){
					var from = source.attr('id');
					// don't submit form if user pressed Enter
					if ((e.which && e.which == 13) || (e.keyCode && e.keyCode == 13)) {
						e.preventDefault();
						from.selectedIndex = 0;
						BOX.move(source, destination);
						from.selectedIndex = 0;
					}
					var temp = from.selectedIndex;
					BOX.filter(base, filter.val());
					from.selectedIndex = temp;
					return true;
				});

				filter.keydown(function(e){
					var from = source.attr('id');
					// right arrow -- move across
					if ((e.which && e.which == 39) || (e.keyCode && e.keyCode == 39)) {
						e.preventDefault();
						var old_index = from.selectedIndex;
						BOX.move(source, destination);
						from.selectedIndex = (old_index == from.length) ? from.length - 1 : old_index;
					}
					// down arrow -- wrap around
					if ((e.which && event.which == 40) || (e.keyCode && e.keyCode == 40)) {
						from.selectedIndex = (from.length == from.selectedIndex + 1) ? 0 : from.selectedIndex + 1;
					}
					// up arrow -- wrap around
					if ((e.which && e.which == 38) || (e.keyCode && e.keyCode == 38)) {
						from.selectedIndex = (from.selectedIndex == 0) ? from.length - 1 : from.selectedIndex - 1;
					}
					return true;
				});
			}

			//check if we are offering filtering on the base
			if(opts.filter.base == true)
			{
				setupFilter(El.find('.move-select-base-filter'), base, container);
			}
			if(opts.filter.container == true)
			{
				setupFilter(El.find('.move-select-container-filter'), container, base);
			}
		}
		return this;
	};

	// Plugin defaults
	$.fn.moveSelect.defaults = {
		base: '',
		container: '',
		btn_in: '',
		btn_out: '',
		btn_save: '',
		btn_empty: '',
		btn_fill: '',
		filter: {
			base: false,
			container: false
		}
	};

// End of closure.
})( jQuery );<commit_msg>redid how elements were consumed by the plugin<commit_after>/**
 * moveSelect jQuery plugin
 * -----------------+
 * v 0.2b 08/08/2013
 *
 * Move options from one multi-select box to another (with key controls)
 *
 */
(function( $ ) {

	$.fn.moveSelect = function( options ) {
		var El = this;

		// Extend our default options with those provided.
		var opts = $.extend( {}, $.fn.moveSelect.defaults, options );

		/**
		 * Box class
		 *
		 * caches all the options.
		 *
		 * by converting the jquery elements to their ids and making use of native
		 * javascript speeds things up a bit.
		 *
		 * @type {Object}
		 */
		var BOX = {
			cache: new Object(),
			init: function(element) {
				var id = element.attr('id');
				BOX.cache[id] = new Array();

				var cache = BOX.cache[id];
				element.find('option').each(function(){
					cache.push({value: $(this).val(), text: $(this).text(), displayed: 1});
				});
			},
			redisplay: function(element) {
				var id = element.attr('id');
				// Repopulate HTML select box from cache
				var select = document.getElementById(id);

				select.options.length = 0; // clear all options

				var cacheLength = BOX.cache[id].length;
				for (var i = 0, j = cacheLength; i < j; i++) {
					var node = BOX.cache[id][i];
					if (node.displayed) {
						select.options[select.options.length] = new Option(node.text, node.value, false, false);
					}
				}
			},
			filter: function(element, text) {
				var id = element.attr('id');
				// Redisplay the HTML select box, displaying only the choices containing ALL
				// the words in text. (It's an AND search.)
				var tokens = text.toLowerCase().split(/\s+/);
				var node, token;
				for (var i = 0; (node = BOX.cache[id][i]); i++) {
					node.displayed = 1;
					for (var j = 0; (token = tokens[j]); j++) {
						if (node.text.toLowerCase().indexOf(token) == -1) {
							node.displayed = 0;
						}
					}
				}
				BOX.redisplay(id);
			},
			delete_from_cache: function(element, value) {
				var id = element.attr('id');
				var node, delete_index = null;
				for (var i = 0; (node = BOX.cache[id][i]); i++) {
					if (node.value == value) {
						delete_index = i;
						break;
					}
				}
				var j = BOX.cache[id].length - 1;
				for (var i = delete_index; i < j; i++) {
					BOX.cache[id][i] = BOX.cache[id][i+1];
				}
				BOX.cache[id].length--;
			},
			add_to_cache: function(element, option) {
				var id = element.attr('id');
				BOX.cache[id].push({value: option.value, text: option.text, displayed: 1});
			},
			cache_contains: function(element, value) {
				var id = element.attr('id');
				// Check if an item is contained in the cache
				var node;
				for (var i = 0; (node = BOX.cache[id][i]); i++) {
					if (node.value == value) {
						return true;
					}
				}
				return false;
			},
			move: function(from, to) {
				var from_box = document.getElementById(from.attr('id'));
				var to_box = document.getElementById(to.attr('id'));
				var option;
				for (var i = 0; (option = from_box.options[i]); i++) {
					if (option.selected && BOX.cache_contains(from, option.value)) {
						BOX.add_to_cache(to, {value: option.value, text: option.text, displayed: 1});
						BOX.delete_from_cache(from, option.value);
					}
				}
				BOX.sort(from).redisplay(from);
				BOX.sort(to).redisplay(to);
			},
			move_all: function(from, to) {
				var from_box = document.getElementById(from.attr('id'));
				var to_box = document.getElementById(to.attr('id'));
				var option;
				for (var i = 0; (option = from_box.options[i]); i++) {
					if (BOX.cache_contains(from, option.value)) {
						BOX.add_to_cache(to, {value: option.value, text: option.text, displayed: 1});
						BOX.delete_from_cache(from, option.value);
					}
				}
				BOX.sort(from).redisplay(from);
				BOX.sort(to).redisplay(to);
			},
			sort: function(element) {
				var id = element.attr('id');
				BOX.cache[id].sort( function(a, b) {
					a = a.text.toLowerCase();
					b = b.text.toLowerCase();
					try {
						if (a > b) return 1;
						if (a < b) return -1;
					}
					catch (e) {
						// silently fail on IE 'unknown' exception
					}
					return 0;
				} );
				return this;
			}
		};

		/**
		 * If a string is provided look through El for that element and make a jQuery object,
		 * otherwise a jQuery object was provided, just return it.
		 *
		 * @param element string|jQuery
		 * @return jQuery
		 */
		var setupElement = function (element) {
			if(element instanceof jQuery)
			{
				return element;
			}
			else {
				return El.find(element);
			}
		};

		//check base and container, initialise them
		var base = setupElement(opts.base);
		BOX.init(base);

		var container = setupElement(opts.container);
		BOX.init(container);

		//check save btn and attach save handler
		var btn_save = setupElement(opts.btn_save);

		btn_save.click(function(e){
			//unset any selected options in the base element, just in case
			base.find('option').each(function(){
				$(this).attr('selected', false);
			});

			//select all options in the container element
			container.find('option').each(function(){
				$(this).attr('selected', true);
			});
		});

		//check btn_in and add handler
		var btn_in = setupElement(opts.btn_in);

		btn_in.click(function(e) {
			e.preventDefault();
			base.find('option:selected').each(function(){
				//remove the role from the select list
				BOX.move(base, container);
			});
		});

		//check btn_out and add handler
		var btn_out = setupElement(opts.btn_out);

		btn_out.click(function(e) {
			e.preventDefault();
			container.find('option:selected').each(function(){
				//add the role from the select list
				BOX.move(container, base);
			});
		});

		//check btn_empty and add handler (empties the container)
		var btn_empty = setupElement(opts.btn_empty);

		btn_empty.click(function(e){
			e.preventDefault();
			BOX.move_all(container, base);
		});

		//check btn_fill and add handler (fills the container with all base's options)
		var btn_fill = setupElement(opts.btn_fill);

		btn_fill.click(function(e){
			e.preventDefault();
			BOX.move_all(base, container);
		});

		//Set up some keyboard controls
		var setupControl = function (from_el, to_el, reverse) {
			var arrowCode = 37; //left arrow

			if(typeof reverse == 'undefined')
			{
				arrowCode = 39; //right arrow
			}

			from_el.keydown(function(e){
				var from = from_el.attr('id');
				// right|left arrow -- move across
				if ((e.which && e.which == arrowCode) || (e.keyCode && e.keyCode == arrowCode)) {
					e.preventDefault();
					var old_index = from.selectedIndex;
					BOX.move(from_el, to_el);
					from.selectedIndex = (old_index == from.length) ? from.length - 1 : old_index;
				}
				// down arrow -- wrap around
				if ((e.which && event.which == 40) || (e.keyCode && e.keyCode == 40)) {
					from.selectedIndex = (from.length == from.selectedIndex + 1) ? 0 : from.selectedIndex + 1;
				}
				// up arrow -- wrap around
				if ((e.which && e.which == 38) || (e.keyCode && e.keyCode == 38)) {
					from.selectedIndex = (from.selectedIndex == 0) ? from.length - 1 : from.selectedIndex - 1;
				}
				return true;
			});
		};

		setupControl(base, container);
		setupControl(container, base, true);

		if(typeof opts.filter == Object)
		{
			var setupFilter = function(filter, source, destination) {
				filter.keyup(function(e){
					var from = source.attr('id');
					// don't submit form if user pressed Enter
					if ((e.which && e.which == 13) || (e.keyCode && e.keyCode == 13)) {
						e.preventDefault();
						from.selectedIndex = 0;
						BOX.move(source, destination);
						from.selectedIndex = 0;
					}
					var temp = from.selectedIndex;
					BOX.filter(base, filter.val());
					from.selectedIndex = temp;
					return true;
				});

				filter.keydown(function(e){
					var from = source.attr('id');
					// right arrow -- move across
					if ((e.which && e.which == 39) || (e.keyCode && e.keyCode == 39)) {
						e.preventDefault();
						var old_index = from.selectedIndex;
						BOX.move(source, destination);
						from.selectedIndex = (old_index == from.length) ? from.length - 1 : old_index;
					}
					// down arrow -- wrap around
					if ((e.which && event.which == 40) || (e.keyCode && e.keyCode == 40)) {
						from.selectedIndex = (from.length == from.selectedIndex + 1) ? 0 : from.selectedIndex + 1;
					}
					// up arrow -- wrap around
					if ((e.which && e.which == 38) || (e.keyCode && e.keyCode == 38)) {
						from.selectedIndex = (from.selectedIndex == 0) ? from.length - 1 : from.selectedIndex - 1;
					}
					return true;
				});
			}

			//check if we are offering filtering on the base
			if(opts.filter.base != false)
			{
				setupFilter(setupElement(opts.filter.base), base, container);
			}
			if(opts.filter.container != false)
			{
				setupFilter(setupElement(opts.filter.container), container, base);
			}
		}
		return this;
	};

	// Plugin defaults
	$.fn.moveSelect.defaults = {
		base: '#move-select-base', //select[multiple='multiple'] element
		container: '#move-select-container', //select[multiple='multiple'] element
		btn_in: '#move-select-in', //button or a element
		btn_out: '#move-select-out', //button or a element
		btn_save: '#move-select-save', //button or a element
		btn_empty: '#move-select-empty', //button or a element
		btn_fill: '#move-select-fill', //button or a element
		filter: {
			base: false, //false or input[type='text'] element
			container: false //false or input[type='text'] element
		}
	};

// End of closure.
})( jQuery );<|endoftext|>


<commit_before>// Requirements
const mqtt = require('mqtt')
var Redis = require('redis')
var Jexl = require('jexl')

const rules = require('./homeautomation-js-lib/rules.js')
const logging = require('./homeautomation-js-lib/logging.js')
const pushover = require('pushover-notifications')
const Queue = require('bull')
const schedule = require('node-schedule')
const SolarCalc = require('solar-calc')
const moment = require('moment')

require('./homeautomation-js-lib/devices.js')
require('./homeautomation-js-lib/mqtt_helpers.js')

const solarLat = process.env.LOCATION_LAT
const solarLong = process.env.LOCATION_LONG

const redisHost = process.env.REDIS_HOST
const redisPort = process.env.REDIS_PORT
const redisDB = process.env.REDIS_DATABASE
const config_path = process.env.TRANSFORM_CONFIG_PATH

const syslogHost = process.env.SYSLOG_HOST
const syslogPort = process.env.SYSLOG_PORT

// Config
const host = process.env.MQTT_HOST

// Set up modules
logging.set_enabled(true)
logging.setRemoteHost(syslogHost, syslogPort)

// Setup MQTT
var client = mqtt.connect(host)

// MQTT Observation

client.on('connect', () => {
    logging.log('Reconnecting...\n')
    client.subscribe('#')
})

client.on('disconnect', () => {
    logging.log('Reconnecting...\n')
    client.connect(host)
})

function update_topic_for_expression(topic) {
    topic = topic.replace(/\//g, '________')
    topic = topic.replace(/-/g, '_')
    return topic
}

function isValueAnExpression(value) {
    return (value.includes('/') || value.includes('?') || value.includes('+'))
}

var actionQueues = {}

function jobProcessor(job, doneAction) {
    const actions = job.data.actions
    const notify = job.data.notify
    const name = job.data.name
    const context = job.data.context
        // const message = job.data.message
        // const topic = job.data.topic
        // const expression = job.data.expression

    logging.log('action queue: ' + name + '    begin')
    if (actions !== null && actions !== undefined) {
        Object.keys(actions).forEach(function(resultTopic) {
            const publishValue = actions[resultTopic]
            logging.log('evaluating for publish: ' + resultTopic + '  value: ' + publishValue)

            if (isValueAnExpression(publishValue)) {
                const publishExpression = update_topic_for_expression(publishValue)
                var jexl = new Jexl.Jexl()

                jexl.eval(publishExpression, context, function(error, publishResult) {
                    logging.log('  =>(' + name + ') evaluated expression: ' + publishExpression + '   result: ' + publishResult + '   error: ' + error)
                    client.publish(resultTopic, '' + publishResult)
                })

            } else {
                client.publish(resultTopic, '' + publishValue)
            }
        }, this)
    }

    if (notify !== null && notify !== undefined) {
        const baseAppToken = process.env.PUSHOVER_APP_TOKEN
        const baseUserToken = process.env.PUSHOVER_USER_TOKEN

        var p = new pushover({
            user: (notify.user ? notify.user : baseUserToken),
            token: (notify.token ? notify.token : baseAppToken)
        })

        var msg = {
            // These values correspond to the parameters detailed on https://pushover.net/api
            // 'message' is required. All other values are optional.
            message: notify.message,
            title: notify.title,
            sound: notify.sound,
            device: notify.device,
            priority: notify.priority,
            url: notify.url,
            url_title: notify.url_title,
        }

        p.send(msg, function(err, result) {
            if (err !== null && err !== undefined) {
                logging.log('notify error: ' + err)
                logging.log('result: ' + result)
            } else {
                logging.log(' => Successfully notified')
            }
        })
    }
    logging.log('action queue: ' + name + '    end')
    doneAction()
}

var evalQueues = {}

function evaluateProcessor(job, doneEvaluate) {
    const name = job.data.name
    const context = job.data.context_value
    const rule = job.data.rule
    const allowed_times = rule.allowed_times

    var isOKTime = true

    logging.log('eval queue: ' + name + '    begin')

    if (allowed_times !== null && allowed_times !== undefined) {
        isOKTime = false

        allowed_times.forEach(function(timeRange) {
            const split = timeRange.split('-')
            logging.log(' time range from: ' + split[0] + '   to: ' + split[1])

            const startDate = moment(new Date())
            const endDate = moment(new Date())

            const startHours = split[0].split(':')[0]
            const startMinutes = split[0].split(':')[1]

            const endHours = split[1].split(':')[0]
            const endMinutes = split[1].split(':')[1]

            startDate.hours(Number(startHours))
            startDate.minutes(Number(startMinutes))
            endDate.hours(Number(endHours))
            endDate.minutes(Number(endMinutes))

            const result = moment(new Date()).isBetween(startDate, endDate)
            if (result == true) {
                isOKTime = true
            }
        }, this)
    }

    if (!isOKTime) {
        logging.log('eval queue: ' + name + '    end - not a good time')
        doneEvaluate()
        return
    }

    const performAction = function(eval_result, context, name, rule) {
        if (eval_result === true) {
            const actions = rule.actions
            const notify = rule.notify
            var perform_after = rule.perform_after
            if (perform_after === undefined || perform_after === null) {
                perform_after = 0
            }
            const queueName = name + '_action'
            var actionQueue = actionQueues[queueName]
            if (actionQueue !== null && actionQueue !== undefined) {
                logging.log('removed existing action queue: ' + queueName)
                actionQueue.empty()
            }

            actionQueue = Queue(queueName, redisPort, redisHost)
            actionQueues[queueName] = actionQueue

            actionQueue.process(jobProcessor)

            actionQueue.add({
                name: name,
                notify: notify,
                actions: actions,
                context: context
            }, {
                removeOnComplete: true,
                removeOnFail: true,
                delay: (perform_after * 1000), // milliseconds
            })
        }
    }

    if (rule.rules !== null && rule.rules !== undefined && rule.rules.expression !== undefined) {
        const expression = update_topic_for_expression(rule.rules.expression)
        var jexl = new Jexl.Jexl()

        jexl.eval(expression, context, function(error, result) {
            logging.log('  =>(' + name + ') evaluated expression: ' + expression + '   result: ' + result + '   error: ' + error)
            performAction(result, context, name, rule)
            logging.log('eval queue: ' + name + '    end expression')
            doneEvaluate()
        })
    } else {
        performAction(true, context, name, rule)
        logging.log('eval queue: ' + name + '    end expression - no /')
        doneEvaluate
    }
}

function evalulateValue(in_context, name, rule) {

    const queueName = name + '_eval'
    var evalQueue = evalQueues[queueName]

    if (evalQueue !== null && evalQueue !== undefined) {
        evalQueue.empty()
    }

    const actionQueueName = name + '_action'
    var actionQueue = actionQueues[actionQueueName]
    if (actionQueue !== null && actionQueue !== undefined) {
        actionQueue.empty()
    }

    var evaluateAfter = rule.evaluate_after
    if (evaluateAfter === undefined || evaluateAfter === null) {
        evaluateAfter = 0
    }

    evalQueue = Queue(queueName, redisPort, redisHost)
    evalQueues[queueName] = evalQueue

    evalQueue.process(evaluateProcessor)

    var job = {
        rule: rule,
        name: '' + name,
        context_value: {}
    }

    Object.keys(in_context).forEach(function(key) {
        job.context_value[key] = '' + in_context[key]
    }, this)

    evalQueue.add(job, {
        removeOnComplete: true,
        removeOnFail: true,
        delay: (evaluateAfter * 1000), // milliseconds
    })
}

var global_value_cache = {}

client.on('message', (topic, message) => {
    //logging.log(' ' + topic + ':' + message)

    redis.keys('*', function(err, result) {
        const keys = result.sort()
        redis.mget(keys, function(err, values) {
            var context = {}

            for (var index = 0; index < keys.length; index++) {
                const key = keys[index]
                const value = values[index]
                const newKey = update_topic_for_expression(key)
                if (key === topic)
                    context[newKey] = message
                else
                    context[newKey] = value
            }

            context[update_topic_for_expression(topic)] = message

            rules.ruleIterator(function(rule_name, rule) {
                const watch = rule.watch
                if (watch == null || watch == undefined)
                    return
                const devices = watch.devices
                if (devices == null || devices == undefined)
                    return

                if (devices.indexOf(topic) !== -1) {
                    logging.log('found watch: ' + rule_name)
                    const isExpression = (!isValueAnExpression('' + message))
                    if (!isExpression) {
                        var cachedValues = global_value_cache[rule_name]
                        if (cachedValues === null || cachedValues === undefined) {
                            cachedValues = {}
                        }
                        var cachedValue = cachedValues[topic]

                        if (cachedValue !== null && cachedValue !== undefined) {
                            if (('' + message).localeCompare(cachedValue) === 0) {
                                logging.log(' => value not updated')
                                return
                            }
                        }
                        cachedValues[topic] = message
                        global_value_cache[rule_name] = cachedValues
                    }

                    evalulateValue(context, rule_name, rule)

                }
            })
        })
    })
})

const redis = Redis.createClient({
    host: redisHost,
    port: redisPort,
    db: redisDB,
    retry_strategy: function(options) {
        if (options.error && options.error.code === 'ECONNREFUSED') {
            // End reconnecting on a specific error and flush all commands with a individual error
            return new Error('The server refused the connection')
        }
        if (options.total_retry_time > 1000 * 60 * 60) {
            // End reconnecting after a specific timeout and flush all commands with a individual error
            return new Error('Retry time exhausted')
        }
        if (options.times_connected > 10) {
            // End reconnecting with built in error
            return undefined
        }
        // reconnect after
        return Math.min(options.attempt * 100, 3000)
    }
})

// redis callbacks

redis.on('error', function(err) {
    logging.log('redis error ' + err)
})

redis.on('connect', function() {
    logging.log('redis connected')
    rules.load_path(config_path)
})

rules.on('rules-loaded', () => {
    logging.log('rules-loaded!')
    scheduleJobs()
})

var scheduled_jobs = []

function doSchedule(rule_name, jobName, cronSchedule, rule) {
    var newJob = schedule.scheduleJob(cronSchedule, function(rule_value) {
        logging.log(' rule: ' + rule_value)

        redis.keys('*', function(err, result) {
            const keys = result.sort()
            redis.mget(keys, function(err, values) {
                var context = {}

                for (var index = 0; index < keys.length; index++) {
                    const key = keys[index]
                    const value = values[index]
                    const newKey = update_topic_for_expression(key)
                    context[newKey] = value
                }

                logging.log('matching rule: ' + rule_name)

                evalulateValue(
                    context,
                    rule_name,
                    rule)
            })
        })
    }.bind(null, rule))

    if (newJob !== null && newJob !== undefined) {
        logging.log(' scheduled: ' + cronSchedule)
        scheduled_jobs.push(newJob)
    }
}

function scheduleJobs() {
    scheduleDailyJobs()

    scheduled_jobs.forEach(function(job) {
        job.cancel()
    }, this)

    rules.ruleIterator(function(rule_name, rule) {
        const schedule = rule.schedule
        const daily = rule.daily

        if (schedule !== null && schedule !== undefined) {
            Object.keys(schedule).forEach(function(schedule_key) {
                var jobKey = rule_name + '.schedule.' + schedule_key
                const cronSchedule = schedule[schedule_key]
                logging.log(jobKey + ' = ' + cronSchedule)
                doSchedule(rule_name, jobKey, cronSchedule, rule)
            }, this)
        }

        if (daily !== null && daily !== undefined) {

            Object.keys(daily).forEach(function(daily_key) {
                var solar = new SolarCalc(new Date(), Number(solarLat), Number(solarLong))
                var jobKey = rule_name + '.daily.' + daily_key
                const dailyValue = daily[daily_key]
                var offset = 0
                if (dailyValue !== null && dailyValue !== undefined) {
                    if (dailyValue.offset === null || dailyValue.offset === undefined)
                        offset = 0
                    else
                        offset = Number(dailyValue.offset)
                }

                var date = null

                if (daily_key === 'sunrise')
                    date = solar.sunrise
                else if (daily_key === 'sunset')
                    date = solar.sunset
                else if (daily_key === 'civilDawn')
                    date = solar.civilDawn
                else if (daily_key === 'nauticalDawn')
                    date = solar.nauticalDawn
                else if (daily_key === 'astronomicalDawn')
                    date = solar.astronomicalDawn
                else if (daily_key === 'civilDusk')
                    date = solar.civilDusk
                else if (daily_key === 'nauticalDusk')
                    date = solar.nauticalDusk
                else if (daily_key === 'astronomicalDusk')
                    date = solar.astronomicalDusk
                else if (daily_key === 'solarNoon')
                    date = solar.solarNoon

                logging.log(jobKey + ' offset: ' + offset)
                if (date !== null) {
                    var newDate = moment(date).add(offset, 'minutes')
                    doSchedule(rule_name, jobKey, newDate.toDate(), rule)
                }

            }, this)
        }
    })
}

var dailyJob = null

function scheduleDailyJobs() {
    if (dailyJob !== null)
        return

    logging.log('Scheduling Daily Job')

    dailyJob = schedule.scheduleJob('00 00 00 * * * ', function() {
        logging.log('** Daily Job **')
        scheduleJobs()
    })
}<commit_msg>Much simplier cache<commit_after>// Requirements
const mqtt = require('mqtt')
var Redis = require('redis')
var Jexl = require('jexl')

const rules = require('./homeautomation-js-lib/rules.js')
const logging = require('./homeautomation-js-lib/logging.js')
const pushover = require('pushover-notifications')
const Queue = require('bull')
const schedule = require('node-schedule')
const SolarCalc = require('solar-calc')
const moment = require('moment')

require('./homeautomation-js-lib/devices.js')
require('./homeautomation-js-lib/mqtt_helpers.js')

const solarLat = process.env.LOCATION_LAT
const solarLong = process.env.LOCATION_LONG

const redisHost = process.env.REDIS_HOST
const redisPort = process.env.REDIS_PORT
const redisDB = process.env.REDIS_DATABASE
const config_path = process.env.TRANSFORM_CONFIG_PATH

const syslogHost = process.env.SYSLOG_HOST
const syslogPort = process.env.SYSLOG_PORT

// Config
const host = process.env.MQTT_HOST

// Set up modules
logging.set_enabled(true)
logging.setRemoteHost(syslogHost, syslogPort)

// Setup MQTT
var client = mqtt.connect(host)

// MQTT Observation

client.on('connect', () => {
    logging.log('Reconnecting...\n')
    client.subscribe('#')
})

client.on('disconnect', () => {
    logging.log('Reconnecting...\n')
    client.connect(host)
})

function update_topic_for_expression(topic) {
    topic = topic.replace(/\//g, '________')
    topic = topic.replace(/-/g, '_')
    return topic
}

function isValueAnExpression(value) {
    return (value.includes('/') || value.includes('?') || value.includes('+'))
}

var actionQueues = {}

function jobProcessor(job, doneAction) {
    const actions = job.data.actions
    const notify = job.data.notify
    const name = job.data.name
    const context = job.data.context
        // const message = job.data.message
        // const topic = job.data.topic
        // const expression = job.data.expression

    logging.log('action queue: ' + name + '    begin')
    if (actions !== null && actions !== undefined) {
        Object.keys(actions).forEach(function(resultTopic) {
            const publishValue = actions[resultTopic]
            logging.log('evaluating for publish: ' + resultTopic + '  value: ' + publishValue)

            if (isValueAnExpression(publishValue)) {
                const publishExpression = update_topic_for_expression(publishValue)
                var jexl = new Jexl.Jexl()

                jexl.eval(publishExpression, context, function(error, publishResult) {
                    logging.log('  =>(' + name + ') evaluated expression: ' + publishExpression + '   result: ' + publishResult + '   error: ' + error)
                    client.publish(resultTopic, '' + publishResult)
                })

            } else {
                client.publish(resultTopic, '' + publishValue)
            }
        }, this)
    }

    if (notify !== null && notify !== undefined) {
        const baseAppToken = process.env.PUSHOVER_APP_TOKEN
        const baseUserToken = process.env.PUSHOVER_USER_TOKEN

        var p = new pushover({
            user: (notify.user ? notify.user : baseUserToken),
            token: (notify.token ? notify.token : baseAppToken)
        })

        var msg = {
            // These values correspond to the parameters detailed on https://pushover.net/api
            // 'message' is required. All other values are optional.
            message: notify.message,
            title: notify.title,
            sound: notify.sound,
            device: notify.device,
            priority: notify.priority,
            url: notify.url,
            url_title: notify.url_title,
        }

        p.send(msg, function(err, result) {
            if (err !== null && err !== undefined) {
                logging.log('notify error: ' + err)
                logging.log('result: ' + result)
            } else {
                logging.log(' => Successfully notified')
            }
        })
    }
    logging.log('action queue: ' + name + '    end')
    doneAction()
}

var evalQueues = {}

function evaluateProcessor(job, doneEvaluate) {
    const name = job.data.name
    const context = job.data.context_value
    const rule = job.data.rule
    const allowed_times = rule.allowed_times

    var isOKTime = true

    logging.log('eval queue: ' + name + '    begin')

    if (allowed_times !== null && allowed_times !== undefined) {
        isOKTime = false

        allowed_times.forEach(function(timeRange) {
            const split = timeRange.split('-')
            logging.log(' time range from: ' + split[0] + '   to: ' + split[1])

            const startDate = moment(new Date())
            const endDate = moment(new Date())

            const startHours = split[0].split(':')[0]
            const startMinutes = split[0].split(':')[1]

            const endHours = split[1].split(':')[0]
            const endMinutes = split[1].split(':')[1]

            startDate.hours(Number(startHours))
            startDate.minutes(Number(startMinutes))
            endDate.hours(Number(endHours))
            endDate.minutes(Number(endMinutes))

            const result = moment(new Date()).isBetween(startDate, endDate)
            if (result == true) {
                isOKTime = true
            }
        }, this)
    }

    if (!isOKTime) {
        logging.log('eval queue: ' + name + '    end - not a good time')
        doneEvaluate()
        return
    }

    const performAction = function(eval_result, context, name, rule) {
        if (eval_result === true) {
            const actions = rule.actions
            const notify = rule.notify
            var perform_after = rule.perform_after
            if (perform_after === undefined || perform_after === null) {
                perform_after = 0
            }
            const queueName = name + '_action'
            var actionQueue = actionQueues[queueName]
            if (actionQueue !== null && actionQueue !== undefined) {
                logging.log('removed existing action queue: ' + queueName)
                actionQueue.empty()
            }

            actionQueue = Queue(queueName, redisPort, redisHost)
            actionQueues[queueName] = actionQueue

            actionQueue.process(jobProcessor)

            actionQueue.add({
                name: name,
                notify: notify,
                actions: actions,
                context: context
            }, {
                removeOnComplete: true,
                removeOnFail: true,
                delay: (perform_after * 1000), // milliseconds
            })
        }
    }

    if (rule.rules !== null && rule.rules !== undefined && rule.rules.expression !== undefined) {
        const expression = update_topic_for_expression(rule.rules.expression)
        var jexl = new Jexl.Jexl()

        jexl.eval(expression, context, function(error, result) {
            logging.log('  =>(' + name + ') evaluated expression: ' + expression + '   result: ' + result + '   error: ' + error)
            performAction(result, context, name, rule)
            logging.log('eval queue: ' + name + '    end expression')
            doneEvaluate()
        })
    } else {
        performAction(true, context, name, rule)
        logging.log('eval queue: ' + name + '    end expression - no /')
        doneEvaluate
    }
}

function evalulateValue(in_context, name, rule) {

    const queueName = name + '_eval'
    var evalQueue = evalQueues[queueName]

    if (evalQueue !== null && evalQueue !== undefined) {
        evalQueue.empty()
    }

    const actionQueueName = name + '_action'
    var actionQueue = actionQueues[actionQueueName]
    if (actionQueue !== null && actionQueue !== undefined) {
        actionQueue.empty()
    }

    var evaluateAfter = rule.evaluate_after
    if (evaluateAfter === undefined || evaluateAfter === null) {
        evaluateAfter = 0
    }

    evalQueue = Queue(queueName, redisPort, redisHost)
    evalQueues[queueName] = evalQueue

    evalQueue.process(evaluateProcessor)

    var job = {
        rule: rule,
        name: '' + name,
        context_value: {}
    }

    Object.keys(in_context).forEach(function(key) {
        job.context_value[key] = '' + in_context[key]
    }, this)

    evalQueue.add(job, {
        removeOnComplete: true,
        removeOnFail: true,
        delay: (evaluateAfter * 1000), // milliseconds
    })
}

var global_value_cache = {}

client.on('message', (topic, message) => {
    //logging.log(' ' + topic + ':' + message)
    var cachedValue = global_value_cache[topic]

    if (cachedValue !== null && cachedValue !== undefined) {
        if (('' + message).localeCompare(cachedValue) === 0) {
            logging.log(' => value not updated')
            return
        }
    }
    global_value_cache[topic] = message

    redis.keys('*', function(err, result) {
        const keys = result.sort()
        redis.mget(keys, function(err, values) {
            var context = {}

            for (var index = 0; index < keys.length; index++) {
                const key = keys[index]
                const value = values[index]
                const newKey = update_topic_for_expression(key)
                if (key === topic)
                    context[newKey] = message
                else
                    context[newKey] = value
            }

            context[update_topic_for_expression(topic)] = message

            rules.ruleIterator(function(rule_name, rule) {
                const watch = rule.watch
                if (watch == null || watch == undefined)
                    return
                const devices = watch.devices
                if (devices == null || devices == undefined)
                    return

                if (devices.indexOf(topic) !== -1) {
                    logging.log('found watch: ' + rule_name)

                    evalulateValue(context, rule_name, rule)
                }
            })
        })
    })
})

const redis = Redis.createClient({
    host: redisHost,
    port: redisPort,
    db: redisDB,
    retry_strategy: function(options) {
        if (options.error && options.error.code === 'ECONNREFUSED') {
            // End reconnecting on a specific error and flush all commands with a individual error
            return new Error('The server refused the connection')
        }
        if (options.total_retry_time > 1000 * 60 * 60) {
            // End reconnecting after a specific timeout and flush all commands with a individual error
            return new Error('Retry time exhausted')
        }
        if (options.times_connected > 10) {
            // End reconnecting with built in error
            return undefined
        }
        // reconnect after
        return Math.min(options.attempt * 100, 3000)
    }
})

// redis callbacks

redis.on('error', function(err) {
    logging.log('redis error ' + err)
})

redis.on('connect', function() {
    logging.log('redis connected')
    rules.load_path(config_path)
})

rules.on('rules-loaded', () => {
    logging.log('rules-loaded!')
    scheduleJobs()
})

var scheduled_jobs = []

function doSchedule(rule_name, jobName, cronSchedule, rule) {
    var newJob = schedule.scheduleJob(cronSchedule, function(rule_value) {
        logging.log(' rule: ' + rule_value)

        redis.keys('*', function(err, result) {
            const keys = result.sort()
            redis.mget(keys, function(err, values) {
                var context = {}

                for (var index = 0; index < keys.length; index++) {
                    const key = keys[index]
                    const value = values[index]
                    const newKey = update_topic_for_expression(key)
                    context[newKey] = value
                }

                logging.log('matching rule: ' + rule_name)

                evalulateValue(
                    context,
                    rule_name,
                    rule)
            })
        })
    }.bind(null, rule))

    if (newJob !== null && newJob !== undefined) {
        logging.log(' scheduled: ' + cronSchedule)
        scheduled_jobs.push(newJob)
    }
}

function scheduleJobs() {
    scheduleDailyJobs()

    scheduled_jobs.forEach(function(job) {
        job.cancel()
    }, this)

    rules.ruleIterator(function(rule_name, rule) {
        const schedule = rule.schedule
        const daily = rule.daily

        if (schedule !== null && schedule !== undefined) {
            Object.keys(schedule).forEach(function(schedule_key) {
                var jobKey = rule_name + '.schedule.' + schedule_key
                const cronSchedule = schedule[schedule_key]
                logging.log(jobKey + ' = ' + cronSchedule)
                doSchedule(rule_name, jobKey, cronSchedule, rule)
            }, this)
        }

        if (daily !== null && daily !== undefined) {

            Object.keys(daily).forEach(function(daily_key) {
                var solar = new SolarCalc(new Date(), Number(solarLat), Number(solarLong))
                var jobKey = rule_name + '.daily.' + daily_key
                const dailyValue = daily[daily_key]
                var offset = 0
                if (dailyValue !== null && dailyValue !== undefined) {
                    if (dailyValue.offset === null || dailyValue.offset === undefined)
                        offset = 0
                    else
                        offset = Number(dailyValue.offset)
                }

                var date = null

                if (daily_key === 'sunrise')
                    date = solar.sunrise
                else if (daily_key === 'sunset')
                    date = solar.sunset
                else if (daily_key === 'civilDawn')
                    date = solar.civilDawn
                else if (daily_key === 'nauticalDawn')
                    date = solar.nauticalDawn
                else if (daily_key === 'astronomicalDawn')
                    date = solar.astronomicalDawn
                else if (daily_key === 'civilDusk')
                    date = solar.civilDusk
                else if (daily_key === 'nauticalDusk')
                    date = solar.nauticalDusk
                else if (daily_key === 'astronomicalDusk')
                    date = solar.astronomicalDusk
                else if (daily_key === 'solarNoon')
                    date = solar.solarNoon

                logging.log(jobKey + ' offset: ' + offset)
                if (date !== null) {
                    var newDate = moment(date).add(offset, 'minutes')
                    doSchedule(rule_name, jobKey, newDate.toDate(), rule)
                }

            }, this)
        }
    })
}

var dailyJob = null

function scheduleDailyJobs() {
    if (dailyJob !== null)
        return

    logging.log('Scheduling Daily Job')

    dailyJob = schedule.scheduleJob('00 00 00 * * * ', function() {
        logging.log('** Daily Job **')
        scheduleJobs()
    })
}<|endoftext|>