<commit_before>'use strict';

/**
 * OpenFB is a micro-library that lets you integrate your JavaScript application with Facebook.
 * OpenFB works for both BROWSER-BASED apps and CORDOVA/PHONEGAP apps.
 * This library has no dependency: You don't need (and shouldn't use) the Facebook SDK with this library. Whe running in
 * Cordova, you also don't need the Facebook Cordova plugin. There is also no dependency on jQuery.
 * OpenFB allows you to login to Facebook and execute any Facebook Graph API request.
 * @author Christophe Coenraets @ccoenraets
 * @version 0.2
 */
angular.module('openfb', [])

    .factory('OpenFB', function ($rootScope, $q, $window, $http) {

        // So long as we serve all content through https,
        // we should not run afoul of mixed content policies
        var FB_LOGIN_URL = 'https://www.facebook.com/dialog/oauth',

        // By default we store fbtoken in sessionStorage. This can be overriden in init()
            tokenStore = $window.sessionStorage,

            fbAppId,
            oauthRedirectURL,

        // Because the OAuth login spans multiple processes, we need to keep the success/error handlers as variables
        // inside the module instead of keeping them local within the login function.
            deferredCheck,
            deferredLogin,

        // Indicates if the app is running inside Cordova
            runningInCordova,

        // Used in the exit event handler to identify if the login has already been processed elsewhere (in the oauthCallback function)
            loginProcessed;

        document.addEventListener("deviceready", function () {
            runningInCordova = true;
        }, false);

        /**
         * Initialize the OpenFB module. You must use this function and initialize the module with an appId before you can
         * use any other function.
         * @param appId - The id of the Facebook app
         * @param redirectURL - The OAuth redirect URL. Optional. If not provided, we use sensible defaults.
         * @param store - The store used to save the Facebook token. Optional. If not provided, we use sessionStorage.
         */
        function init(appId, redirectURL, store) {
            fbAppId = appId;
            if (redirectURL) oauthRedirectURL = redirectURL;
            if (store) tokenStore = store;
        }

        function isLoggedIn() {
            return !!tokenStore['fbtoken'];
        }

        function checkLogin() {
            deferredCheck = $q.defer();

            if (tokenStore['fbtoken']) {
                if (tokenStore['id']) {
                    deferredCheck.resolve(tokenStore['id'], tokenStore['fbtoken']);
                } else {
                    get('/me').success(function(data) {
                        tokenStore['id'] = data.id;
                        deferredCheck.resolve(data.id, tokenStore['fbtoken']);
                    }).catch(function(err) {
                        deferredCheck.reject(err);
                    });
                }
            }

            return deferredCheck.promise;
        }

        /**
         * Login to Facebook using OAuth. If running in a Browser, the OAuth workflow happens in a a popup window.
         * If running in Cordova container, it happens using the In-App Browser. Don't forget to install the In-App Browser
         * plugin in your Cordova project: cordova plugins add org.apache.cordova.inappbrowser.
         * @param fbScope - The set of Facebook permissions requested
         */
        function login(fbScope) {

            if (!fbAppId) {
                return error({error: 'Facebook App Id not set.'});
            }

            var loginWindow;

            fbScope = fbScope || '';

            deferredLogin = $q.defer();

            loginProcessed = false;

            logout();

            // Check if an explicit oauthRedirectURL has been provided in init(). If not, infer the appropriate value
            if (!oauthRedirectURL) {
                if (runningInCordova) {
                    oauthRedirectURL = 'https://www.facebook.com/connect/login_success.html';
                } else {
                    // Trying to calculate oauthRedirectURL based on the current URL.
                    var index = document.location.href.indexOf('index.html');
                    if (index > 0) {
                        oauthRedirectURL = document.location.href.substring(0, index) + 'oauthcallback.html';
                    } else {
                        return alert("Can't reliably infer the OAuth redirect URI. Please specify it explicitly in openFB.init()");
                    }
                }
            }

            loginWindow = window.open(FB_LOGIN_URL + '?client_id=' + fbAppId + '&redirect_uri=' + oauthRedirectURL +
                '&response_type=token&display=popup&scope=' + fbScope, '_blank', 'location=no');

            // If the app is running in Cordova, listen to URL changes in the InAppBrowser until we get a URL with an access_token or an error
            if (runningInCordova) {
                loginWindow.addEventListener('loadstart', function (event) {
                    var url = event.url;
                    if (url.indexOf("access_token=") > 0 || url.indexOf("error=") > 0) {
                        loginWindow.close();
                        oauthCallback(url);
                    }
                });

                loginWindow.addEventListener('exit', function () {
                    // Handle the situation where the user closes the login window manually before completing the login process
                    deferredLogin.reject({error: 'user_cancelled', error_description: 'User cancelled login process', error_reason: "user_cancelled"});
                });
            }
            // Note: if the app is running in the browser the loginWindow dialog will call back by invoking the
            // oauthCallback() function. See oauthcallback.html for details.

            return deferredLogin.promise;

        }

        /**
         * Called either by oauthcallback.html (when the app is running the browser) or by the loginWindow loadstart event
         * handler defined in the login() function (when the app is running in the Cordova/PhoneGap container).
         * @param url - The oautchRedictURL called by Facebook with the access_token in the querystring at the ned of the
         * OAuth workflow.
         */
        function oauthCallback(url) {
            // Parse the OAuth data received from Facebook
            var queryString,
                obj;

            loginProcessed = true;
            if (url.indexOf("access_token=") > 0) {
                queryString = url.substr(url.indexOf('#') + 1);
                obj = parseQueryString(queryString);
                tokenStore['fbtoken'] = obj['access_token'];
                deferredLogin.resolve();
                if (deferredCheck) {
                    get('/me').success(function(data) {
                        tokenStore['id'] = data.id;
                        deferredCheck.resolve(data.id, tokenStore['fbtoken']);
                    }).catch(function(err) {
                        deferredCheck.reject(err, tokenStore['fbtoken']);
                    });
                }
            } else if (url.indexOf("error=") > 0) {
                queryString = url.substring(url.indexOf('?') + 1, url.indexOf('#'));
                obj = parseQueryString(queryString);
                deferredLogin.reject(obj);
            } else {
                deferredLogin.reject();
            }
        }

        /**
         * Application-level logout: we simply discard the token.
         */
        function logout() {
            delete tokenStore['id'];
            delete tokenStore['fbtoken'];
        }

        /**
         * Helper function to de-authorize the app
         * @param success
         * @param error
         * @returns {*}
         */
        function revokePermissions() {
            return api({method: 'DELETE', path: '/me/permissions'})
                .success(function () {
                    console.log('Permissions revoked');
                });
        }

        /**
         * Lets you make any Facebook Graph API request.
         * @param obj - Request configuration object. Can include:
         *  method:  HTTP method: GET, POST, etc. Optional - Default is 'GET'
         *  path:    path in the Facebook graph: /me, /me.friends, etc. - Required
         *  params:  queryString parameters as a map - Optional
         */
        function api(obj) {

            var method = obj.method || 'GET',
                params = obj.params || {};

            params['access_token'] = tokenStore['fbtoken'];

            return $http({method: method, url: 'https://graph.facebook.com' + obj.path, params: params})
                .error(function(data, status, headers, config) {
                    if (data.error && data.error.type === 'OAuthException') {
                        $rootScope.$emit('OAuthException');
                    }
                });
        }

        /**
         * Helper function for a POST call into the Graph API
         * @param path
         * @param params
         * @returns {*}
         */
        function post(path, params) {
            return api({method: 'POST', path: path, params: params});
        }

        /**
         * Helper function for a GET call into the Graph API
         * @param path
         * @param params
         * @returns {*}
         */
        function get(path, params) {
            return api({method: 'GET', path: path, params: params});
        }

        function parseQueryString(queryString) {
            var qs = decodeURIComponent(queryString),
                obj = {},
                params = qs.split('&');
            params.forEach(function (param) {
                var splitter = param.split('=');
                obj[splitter[0]] = splitter[1];
            });
            return obj;
        }

        return {
            init: init,
            isLoggedIn: isLoggedIn,
            checkLogin: checkLogin,
            login: login,
            logout: logout,
            revokePermissions: revokePermissions,
            api: api,
            post: post,
            get: get,
            oauthCallback: oauthCallback
        }

    });

// Global function called back by the OAuth login dialog
function oauthCallback(url) {
    var injector = angular.element(document.querySelector('body')).injector();
    injector.invoke(function (OpenFB) {
        OpenFB.oauthCallback(url);
    });
}

<commit_msg>Changed console.log to coloog.info<commit_after>'use strict';

/**
 * OpenFB is a micro-library that lets you integrate your JavaScript application with Facebook.
 * OpenFB works for both BROWSER-BASED apps and CORDOVA/PHONEGAP apps.
 * This library has no dependency: You don't need (and shouldn't use) the Facebook SDK with this library. Whe running in
 * Cordova, you also don't need the Facebook Cordova plugin. There is also no dependency on jQuery.
 * OpenFB allows you to login to Facebook and execute any Facebook Graph API request.
 * @author Christophe Coenraets @ccoenraets
 * @version 0.2
 */
angular.module('openfb', [])

    .factory('OpenFB', function ($rootScope, $q, $window, $http) {

        // So long as we serve all content through https,
        // we should not run afoul of mixed content policies
        var FB_LOGIN_URL = 'https://www.facebook.com/dialog/oauth',

        // By default we store fbtoken in sessionStorage. This can be overriden in init()
            tokenStore = $window.sessionStorage,

            fbAppId,
            oauthRedirectURL,

        // Because the OAuth login spans multiple processes, we need to keep the success/error handlers as variables
        // inside the module instead of keeping them local within the login function.
            deferredCheck,
            deferredLogin,

        // Indicates if the app is running inside Cordova
            runningInCordova,

        // Used in the exit event handler to identify if the login has already been processed elsewhere (in the oauthCallback function)
            loginProcessed;

        document.addEventListener("deviceready", function () {
            runningInCordova = true;
        }, false);

        /**
         * Initialize the OpenFB module. You must use this function and initialize the module with an appId before you can
         * use any other function.
         * @param appId - The id of the Facebook app
         * @param redirectURL - The OAuth redirect URL. Optional. If not provided, we use sensible defaults.
         * @param store - The store used to save the Facebook token. Optional. If not provided, we use sessionStorage.
         */
        function init(appId, redirectURL, store) {
            fbAppId = appId;
            if (redirectURL) oauthRedirectURL = redirectURL;
            if (store) tokenStore = store;
        }

        function isLoggedIn() {
            return !!tokenStore['fbtoken'];
        }

        function checkLogin() {
            deferredCheck = $q.defer();

            if (tokenStore['fbtoken']) {
                if (tokenStore['id']) {
                    deferredCheck.resolve(tokenStore['id'], tokenStore['fbtoken']);
                } else {
                    get('/me').success(function(data) {
                        tokenStore['id'] = data.id;
                        deferredCheck.resolve(data.id, tokenStore['fbtoken']);
                    }).catch(function(err) {
                        deferredCheck.reject(err);
                    });
                }
            }

            return deferredCheck.promise;
        }

        /**
         * Login to Facebook using OAuth. If running in a Browser, the OAuth workflow happens in a a popup window.
         * If running in Cordova container, it happens using the In-App Browser. Don't forget to install the In-App Browser
         * plugin in your Cordova project: cordova plugins add org.apache.cordova.inappbrowser.
         * @param fbScope - The set of Facebook permissions requested
         */
        function login(fbScope) {

            if (!fbAppId) {
                return error({error: 'Facebook App Id not set.'});
            }

            var loginWindow;

            fbScope = fbScope || '';

            deferredLogin = $q.defer();

            loginProcessed = false;

            logout();

            // Check if an explicit oauthRedirectURL has been provided in init(). If not, infer the appropriate value
            if (!oauthRedirectURL) {
                if (runningInCordova) {
                    oauthRedirectURL = 'https://www.facebook.com/connect/login_success.html';
                } else {
                    // Trying to calculate oauthRedirectURL based on the current URL.
                    var index = document.location.href.indexOf('index.html');
                    if (index > 0) {
                        oauthRedirectURL = document.location.href.substring(0, index) + 'oauthcallback.html';
                    } else {
                        return alert("Can't reliably infer the OAuth redirect URI. Please specify it explicitly in openFB.init()");
                    }
                }
            }

            loginWindow = window.open(FB_LOGIN_URL + '?client_id=' + fbAppId + '&redirect_uri=' + oauthRedirectURL +
                '&response_type=token&display=popup&scope=' + fbScope, '_blank', 'location=no');

            // If the app is running in Cordova, listen to URL changes in the InAppBrowser until we get a URL with an access_token or an error
            if (runningInCordova) {
                loginWindow.addEventListener('loadstart', function (event) {
                    var url = event.url;
                    if (url.indexOf("access_token=") > 0 || url.indexOf("error=") > 0) {
                        loginWindow.close();
                        oauthCallback(url);
                    }
                });

                loginWindow.addEventListener('exit', function () {
                    // Handle the situation where the user closes the login window manually before completing the login process
                    deferredLogin.reject({error: 'user_cancelled', error_description: 'User cancelled login process', error_reason: "user_cancelled"});
                });
            }
            // Note: if the app is running in the browser the loginWindow dialog will call back by invoking the
            // oauthCallback() function. See oauthcallback.html for details.

            return deferredLogin.promise;

        }

        /**
         * Called either by oauthcallback.html (when the app is running the browser) or by the loginWindow loadstart event
         * handler defined in the login() function (when the app is running in the Cordova/PhoneGap container).
         * @param url - The oautchRedictURL called by Facebook with the access_token in the querystring at the ned of the
         * OAuth workflow.
         */
        function oauthCallback(url) {
            // Parse the OAuth data received from Facebook
            var queryString,
                obj;

            loginProcessed = true;
            if (url.indexOf("access_token=") > 0) {
                queryString = url.substr(url.indexOf('#') + 1);
                obj = parseQueryString(queryString);
                tokenStore['fbtoken'] = obj['access_token'];
                deferredLogin.resolve();
                if (deferredCheck) {
                    get('/me').success(function(data) {
                        tokenStore['id'] = data.id;
                        deferredCheck.resolve(data.id, tokenStore['fbtoken']);
                    }).catch(function(err) {
                        deferredCheck.reject(err, tokenStore['fbtoken']);
                    });
                }
            } else if (url.indexOf("error=") > 0) {
                queryString = url.substring(url.indexOf('?') + 1, url.indexOf('#'));
                obj = parseQueryString(queryString);
                deferredLogin.reject(obj);
            } else {
                deferredLogin.reject();
            }
        }

        /**
         * Application-level logout: we simply discard the token.
         */
        function logout() {
            delete tokenStore['id'];
            delete tokenStore['fbtoken'];
        }

        /**
         * Helper function to de-authorize the app
         * @param success
         * @param error
         * @returns {*}
         */
        function revokePermissions() {
            return api({method: 'DELETE', path: '/me/permissions'})
                .success(function () {
                    colog.info('Permissions revoked');
                });
        }

        /**
         * Lets you make any Facebook Graph API request.
         * @param obj - Request configuration object. Can include:
         *  method:  HTTP method: GET, POST, etc. Optional - Default is 'GET'
         *  path:    path in the Facebook graph: /me, /me.friends, etc. - Required
         *  params:  queryString parameters as a map - Optional
         */
        function api(obj) {

            var method = obj.method || 'GET',
                params = obj.params || {};

            params['access_token'] = tokenStore['fbtoken'];

            return $http({method: method, url: 'https://graph.facebook.com' + obj.path, params: params})
                .error(function(data, status, headers, config) {
                    if (data.error && data.error.type === 'OAuthException') {
                        $rootScope.$emit('OAuthException');
                    }
                });
        }

        /**
         * Helper function for a POST call into the Graph API
         * @param path
         * @param params
         * @returns {*}
         */
        function post(path, params) {
            return api({method: 'POST', path: path, params: params});
        }

        /**
         * Helper function for a GET call into the Graph API
         * @param path
         * @param params
         * @returns {*}
         */
        function get(path, params) {
            return api({method: 'GET', path: path, params: params});
        }

        function parseQueryString(queryString) {
            var qs = decodeURIComponent(queryString),
                obj = {},
                params = qs.split('&');
            params.forEach(function (param) {
                var splitter = param.split('=');
                obj[splitter[0]] = splitter[1];
            });
            return obj;
        }

        return {
            init: init,
            isLoggedIn: isLoggedIn,
            checkLogin: checkLogin,
            login: login,
            logout: logout,
            revokePermissions: revokePermissions,
            api: api,
            post: post,
            get: get,
            oauthCallback: oauthCallback
        }

    });

// Global function called back by the OAuth login dialog
function oauthCallback(url) {
    var injector = angular.element(document.querySelector('body')).injector();
    injector.invoke(function (OpenFB) {
        OpenFB.oauthCallback(url);
    });
}

<|endoftext|>


<commit_before>define(["vendor/howler/howler.min.js"], function (howler) {

  var footstep = new howler.Howl({

        urls:['audio/footstep.mp3'],
        volume: 1.5,
        loop: false,
  });

  var drinkCoffee = new howler.Howl({

    urls:['audio/drink.mp3'],
    volume:1.5,
    loop: false,
  });

  var workOnNotebook = new howler.Howl({
    urls:['audio/sigh&typing.mp3'],
    volume:1.5,
    loop:false,

  });

  var workOnPaper = new howler.Howl({
    urls:['audio/workOnPaper2.mp3'],
    volume:1.5,
    loop:false,
  });

  var boss = new howler.Howl({
    urls:['audio/boss1.mp3'],
    volume:1.5,
    loop:false,
  });

  var stoss = new howler.Howl({
    urls:['audio/stoss&schrei.mp3'],
    volume:1.5,
    loop:false,
  });

  var workmateM1 = new howler.Howl({
    urls:['audio/workmateM1.mp3'],
      volume: 1.5,
      loop:false,
  });

    var workmateM2 = new howler.Howl({
    urls:['audio/workmateM2.mp3'],
      volume: 1.5,
      loop:false,
  });

    var workmateM3 = new howler.Howl({
    urls:['audio/workmateM3.mp3'],
      volume: 1.5,
      loop:false,
  });

   var workmateW1 = new howler.Howl({
    urls:['audio/workmateW1.mp3'],
      volume: 1.5,
      loop:false,
  });

    var workmateW2 = new howler.Howl({
    urls:['audio/workmateW2.mp3'],
      volume: 1.5,
      loop:false,
  });

    var workmateW3 = new howler.Howl({
    urls:['audio/workmateW3.mp3'],
      volume: 1.5,
      loop:false,
  });

  var countDownBeep = new howler.Howl({
    urls:['audio/beepCD.mp3'],
    volume:0.6,
    loop:false,
  });

  return function playAudio(audioString) {
    var audios = { footstep: footstep, drinkCoffee: drinkCoffee, boss: boss, stoss:stoss,
      workOnNotebook:workOnNotebook, workOnPaper:workOnPaper, countDownBeep: countDownBeep,
      workmateM1: workmateM1, workmateM2: workmateM2, workmateM3: workmateM3,
      workmateW1: workmateW1, workmateW2: workmateW2, workmateW3: workmateW3};

    if(audios.hasOwnProperty(audioString)) {

      audioToPlay = audios[audioString];
      audioToPlay.play();
      return audioToPlay;
    }
  };

});<commit_msg>always use var in order to initialize variables<commit_after>define(["vendor/howler/howler.min.js"], function (howler) {

  var footstep = new howler.Howl({

        urls:['audio/footstep.mp3'],
        volume: 1.5,
        loop: false,
  });

  var drinkCoffee = new howler.Howl({

    urls:['audio/drink.mp3'],
    volume:1.5,
    loop: false,
  });

  var workOnNotebook = new howler.Howl({
    urls:['audio/sigh&typing.mp3'],
    volume:1.5,
    loop:false,

  });

  var workOnPaper = new howler.Howl({
    urls:['audio/workOnPaper2.mp3'],
    volume:1.5,
    loop:false,
  });

  var boss = new howler.Howl({
    urls:['audio/boss1.mp3'],
    volume:1.5,
    loop:false,
  });

  var stoss = new howler.Howl({
    urls:['audio/stoss&schrei.mp3'],
    volume:1.5,
    loop:false,
  });

  var workmateM1 = new howler.Howl({
    urls:['audio/workmateM1.mp3'],
      volume: 1.5,
      loop:false,
  });

    var workmateM2 = new howler.Howl({
    urls:['audio/workmateM2.mp3'],
      volume: 1.5,
      loop:false,
  });

    var workmateM3 = new howler.Howl({
    urls:['audio/workmateM3.mp3'],
      volume: 1.5,
      loop:false,
  });

   var workmateW1 = new howler.Howl({
    urls:['audio/workmateW1.mp3'],
      volume: 1.5,
      loop:false,
  });

    var workmateW2 = new howler.Howl({
    urls:['audio/workmateW2.mp3'],
      volume: 1.5,
      loop:false,
  });

    var workmateW3 = new howler.Howl({
    urls:['audio/workmateW3.mp3'],
      volume: 1.5,
      loop:false,
  });

  var countDownBeep = new howler.Howl({
    urls:['audio/beepCD.mp3'],
    volume:0.6,
    loop:false,
  });

  return function playAudio(audioString) {
    var audios = { footstep: footstep, drinkCoffee: drinkCoffee, boss: boss, stoss:stoss,
      workOnNotebook:workOnNotebook, workOnPaper:workOnPaper, countDownBeep: countDownBeep,
      workmateM1: workmateM1, workmateM2: workmateM2, workmateM3: workmateM3,
      workmateW1: workmateW1, workmateW2: workmateW2, workmateW3: workmateW3};

    if(audios.hasOwnProperty(audioString)) {
      var audioToPlay = audios[audioString];
      audioToPlay.play();
      return audioToPlay;
    }
  };

});
<|endoftext|>


<commit_before>'use strict';

var Grid = require('./grid');
var Player = require('./player');
var Chip = require('./chip');

// An AI player that can think for itself; every AI player inherits from the
// base Player model
function AIPlayer(args) {
  Player.call(this, args);
}
AIPlayer.prototype = Object.create(Player.prototype);
AIPlayer.prototype.type = 'ai';
// The duration to wait (in ms) for the user to process the AI player's actions
AIPlayer.waitDelay = 150;
// The maximum number of grid moves to look ahead (this determines the AI's
// intelligence)
AIPlayer.maxComputeDepth = 3;

// Wait for a short moment to give the user time to see and process the AI
// player's actions
AIPlayer.prototype.wait = function (callback) {
  setTimeout(function () {
    callback();
  }, AIPlayer.waitDelay);
};

// Compute the column where the AI player should place its next chip
AIPlayer.prototype.computeNextMove = function (game) {
  var bestMove = this.maximizeMove(
    game.grid, game.getOtherPlayer(this), AIPlayer.maxComputeDepth,
    Grid.minScore, Grid.maxScore);
  // If no particular column yields an advantage or disadvantage, default to the
  // center column
  if (bestMove.column === 0 && bestMove.score === 0) {
    bestMove.column = 3;
  }
  // Choose next available column if original pick is full
  while (game.grid.getNextAvailableSlot({column: bestMove.column}) === null) {
    bestMove.column += 1;
    // Wrap around if needed
    if (bestMove.column === game.grid.columnCount) {
      bestMove.column = 0;
    }
  }
  game.emitter.emit('ai-player:compute-next-move', this, bestMove);
  return bestMove;
};

// Choose a column that will maximize the AI player's chances of winning
AIPlayer.prototype.maximizeMove = function (grid, minPlayer, depth, alpha, beta) {
  var gridScore = grid.getScore({
    currentPlayer: this,
    currentPlayerIsMaxPlayer: true
  });
  // If max search depth was reached or if winning grid was found
  if (depth === 0 || Math.abs(gridScore) === Grid.maxScore) {
    return {column: null, score: gridScore};
  }
  var maxMove = {column: null, score: Grid.minScore};
  for (var c = 0; c < grid.columnCount; c += 1) {
    // Continue to next possible move if this column is full
    if (grid.columns[c].length === grid.rowCount) {
      continue;
    }
    // Clone the current grid and place a chip to generate a new permutation
    var nextGrid = new Grid(grid);
    nextGrid.placeChip({column: c, chip: new Chip({player: this})});
    // Minimize the opponent human player's chances of winning
    var minMove = this.minimizeMove(nextGrid, minPlayer, depth - 1, alpha, beta);
    // If a move yields a lower opponent score, make it the tentative max move
    if (minMove.score > maxMove.score) {
      maxMove.column = c;
      maxMove.score = minMove.score;
      alpha = minMove.score;
    } else if (maxMove.column === null) {
      // Ensure that obvious column choices are not forgotten
      maxMove.column = minMove.column;
      maxMove.score = minMove.score;
      alpha = minMove.score;
    }
    // Stop if there are no moves better than the current max move
    if (alpha >= beta) {
      break;
    }
  }
  return maxMove;
};

// Choose a column that will minimize the human player's chances of winning
AIPlayer.prototype.minimizeMove = function (grid, minPlayer, depth, alpha, beta) {
  var gridScore = grid.getScore({
    currentPlayer: minPlayer,
    currentPlayerIsMaxPlayer: false
  });
  // If max search depth was reached or if winning grid was found
  if (depth === 0 || Math.abs(gridScore) === Grid.maxScore) {
    return {column: null, score: gridScore};
  }
  var minMove = {column: null, score: Grid.maxScore};
  for (var c = 0; c < grid.columnCount; c += 1) {
    // Continue to next possible move if this column is full
    if (grid.columns[c].length === grid.rowCount) {
      continue;
    }
    var nextGrid = new Grid(grid);
    // The human playing against the AI is always the first player
    nextGrid.placeChip({column: c, chip: new Chip({player: minPlayer})});
    // Maximize the AI player's chances of winning
    var maxMove = this.maximizeMove(nextGrid, minPlayer, depth - 1, alpha, beta);
    // If a move yields a higher AI score, make it the tentative max move
    if (maxMove.score < minMove.score) {
      minMove.column = c;
      minMove.score = maxMove.score;
      beta = maxMove.score;
    } else if (minMove.column === null) {
      // Ensure that obvious column choices are not forgotten
      minMove.column = maxMove.column;
      minMove.score = maxMove.score;
      beta = maxMove.score;
    }
    // Stop if there are no moves better than the current min move
    if (alpha >= beta) {
      break;
    }
  }
  return minMove;
};

module.exports = AIPlayer;
<commit_msg>Remove AI player's default column logic<commit_after>'use strict';

var Grid = require('./grid');
var Player = require('./player');
var Chip = require('./chip');

// An AI player that can think for itself; every AI player inherits from the
// base Player model
function AIPlayer(args) {
  Player.call(this, args);
}
AIPlayer.prototype = Object.create(Player.prototype);
AIPlayer.prototype.type = 'ai';
// The duration to wait (in ms) for the user to process the AI player's actions
AIPlayer.waitDelay = 150;
// The maximum number of grid moves to look ahead (this determines the AI's
// intelligence)
AIPlayer.maxComputeDepth = 3;

// Wait for a short moment to give the user time to see and process the AI
// player's actions
AIPlayer.prototype.wait = function (callback) {
  setTimeout(function () {
    callback();
  }, AIPlayer.waitDelay);
};

// Compute the column where the AI player should place its next chip
AIPlayer.prototype.computeNextMove = function (game) {
  var bestMove = this.maximizeMove(
    game.grid, game.getOtherPlayer(this), AIPlayer.maxComputeDepth,
    Grid.minScore, Grid.maxScore);
  // Choose next available column if original pick is full
  while (game.grid.getNextAvailableSlot({column: bestMove.column}) === null) {
    bestMove.column += 1;
    // Wrap around if needed
    if (bestMove.column === game.grid.columnCount) {
      bestMove.column = 0;
    }
  }
  game.emitter.emit('ai-player:compute-next-move', this, bestMove);
  return bestMove;
};

// Choose a column that will maximize the AI player's chances of winning
AIPlayer.prototype.maximizeMove = function (grid, minPlayer, depth, alpha, beta) {
  var gridScore = grid.getScore({
    currentPlayer: this,
    currentPlayerIsMaxPlayer: true
  });
  // If max search depth was reached or if winning grid was found
  if (depth === 0 || Math.abs(gridScore) === Grid.maxScore) {
    return {column: null, score: gridScore};
  }
  var maxMove = {column: null, score: Grid.minScore};
  for (var c = 0; c < grid.columnCount; c += 1) {
    // Continue to next possible move if this column is full
    if (grid.columns[c].length === grid.rowCount) {
      continue;
    }
    // Clone the current grid and place a chip to generate a new permutation
    var nextGrid = new Grid(grid);
    nextGrid.placeChip({column: c, chip: new Chip({player: this})});
    // Minimize the opponent human player's chances of winning
    var minMove = this.minimizeMove(nextGrid, minPlayer, depth - 1, alpha, beta);
    // If a move yields a lower opponent score, make it the tentative max move
    if (minMove.score > maxMove.score) {
      maxMove.column = c;
      maxMove.score = minMove.score;
      alpha = minMove.score;
    } else if (maxMove.column === null) {
      // Ensure that obvious column choices are not forgotten
      maxMove.column = minMove.column;
      maxMove.score = minMove.score;
      alpha = minMove.score;
    }
    // Stop if there are no moves better than the current max move
    if (alpha >= beta) {
      break;
    }
  }
  return maxMove;
};

// Choose a column that will minimize the human player's chances of winning
AIPlayer.prototype.minimizeMove = function (grid, minPlayer, depth, alpha, beta) {
  var gridScore = grid.getScore({
    currentPlayer: minPlayer,
    currentPlayerIsMaxPlayer: false
  });
  // If max search depth was reached or if winning grid was found
  if (depth === 0 || Math.abs(gridScore) === Grid.maxScore) {
    return {column: null, score: gridScore};
  }
  var minMove = {column: null, score: Grid.maxScore};
  for (var c = 0; c < grid.columnCount; c += 1) {
    // Continue to next possible move if this column is full
    if (grid.columns[c].length === grid.rowCount) {
      continue;
    }
    var nextGrid = new Grid(grid);
    // The human playing against the AI is always the first player
    nextGrid.placeChip({column: c, chip: new Chip({player: minPlayer})});
    // Maximize the AI player's chances of winning
    var maxMove = this.maximizeMove(nextGrid, minPlayer, depth - 1, alpha, beta);
    // If a move yields a higher AI score, make it the tentative max move
    if (maxMove.score < minMove.score) {
      minMove.column = c;
      minMove.score = maxMove.score;
      beta = maxMove.score;
    } else if (minMove.column === null) {
      // Ensure that obvious column choices are not forgotten
      minMove.column = maxMove.column;
      minMove.score = maxMove.score;
      beta = maxMove.score;
    }
    // Stop if there are no moves better than the current min move
    if (alpha >= beta) {
      break;
    }
  }
  return minMove;
};

module.exports = AIPlayer;
<|endoftext|>


<commit_before>var service   = require('../service');
var jpgp      = require('../lib/jpgp');
var async     = require('async');
var mongoose  = require('mongoose');
var _         = require('underscore');
var merkle    = require('merkle');
var PublicKey = mongoose.model('PublicKey');
var logger    = require('../lib/logger')('pubkey');

// Services
var KeyService = service.Key;

module.exports.get = function (pgp, currency, conf) {

  var fifo = async.queue(function (task, callback) {
    task(callback);
  }, 1);

  /**
  * Tries to persist a public key given in ASCII-armored format.
  * Returns the database stored public key.
  */
  this.submitPubkey = function(pubkey, callback) {
    fifo.push(function (cb) {
      async.waterfall([
        function (next) {
          logger.debug('⬇ %s', pubkey.fingerprint);
          if (conf.kaccept == "KEYS") {
            KeyService.isKnown(pubkey.fingerprint, next);
          } else {
            next(null, true);
          }
        },
        function (isKnown, next) {
          if (!isKnown) {
            next('Unknown key - rejected');
            return;
          }
          // Known key: persist
          PublicKey.persist(pubkey, function (err) {
            next(err);
          });
        },
        function (next) {
          // If kmanagement == ALL, mark key as handled to handle key's transactions
          if (conf && conf.kmanagement == 'ALL')
            KeyService.handleKey(pubkey.fingerprint, next);
          else
            next();
        },
        function (next){
          PublicKey.getTheOne(pubkey.fingerprint, next);
        },
      ], function (err, dbPubkey) {
        cb(err, dbPubkey);
      });
    }, callback);
  };

  return this;
}
<commit_msg>Fix: adding a key as managed caused PublicKeyService to be stuck<commit_after>var service   = require('../service');
var jpgp      = require('../lib/jpgp');
var async     = require('async');
var mongoose  = require('mongoose');
var _         = require('underscore');
var merkle    = require('merkle');
var PublicKey = mongoose.model('PublicKey');
var logger    = require('../lib/logger')('pubkey');

// Services
var KeyService = service.Key;

module.exports.get = function (pgp, currency, conf) {

  var fifo = async.queue(function (task, callback) {
    task(callback);
  }, 1);

  /**
  * Tries to persist a public key given in ASCII-armored format.
  * Returns the database stored public key.
  */
  this.submitPubkey = function(pubkey, callback) {
    fifo.push(function (cb) {
      async.waterfall([
        function (next) {
          logger.debug('⬇ %s', pubkey.fingerprint);
          if (conf.kaccept == "KEYS") {
            KeyService.isKnown(pubkey.fingerprint, next);
          } else {
            next(null, true);
          }
        },
        function (isKnown, next) {
          if (!isKnown) {
            next('Unknown key - rejected');
            return;
          }
          // Known key: persist
          PublicKey.persist(pubkey, function (err) {
            next(err);
          });
        },
        function (next) {
          // If kmanagement == ALL, mark key as handled to handle key's transactions
          if (conf && conf.kmanagement == 'ALL')
            KeyService.handleKey(pubkey.fingerprint, true, next);
          else
            next();
        },
        function (next){
          PublicKey.getTheOne(pubkey.fingerprint, next);
        },
      ], function (err, dbPubkey) {
        cb(err, dbPubkey);
      });
    }, callback);
  };

  return this;
}
<|endoftext|>


<commit_before>import Service, {inject as service} from '@ember/service';
import moment from 'moment';
import {task} from 'ember-concurrency';
import {tracked} from '@glimmer/tracking';

/**
 * @typedef MrrStat
 * @type {Object}
 * @property {string} date The date (YYYY-MM-DD) on which this MRR was recorded
 * @property {number} mrr The MRR on this date
 */

/**
 * @typedef MemberCountStat
 * @type {Object}
 * @property {string} date The date (YYYY-MM-DD) on which these counts were recorded
 * @property {number} paid Amount of paid members
 * @property {number} free Amount of free members
 * @property {number} comped Amount of comped members
 * @property {number} newPaid Amount of new paid members
 * @property {number} canceledPaid Amount of canceled paid members
 */

/**
 * @typedef MemberCounts
 * @type {Object}
 * @property {number} total Total amount of members
 * @property {number} paid Amount of paid members
 * @property {number} free Amount of free members
 */

/**
 * @todo: THIS ONE IS TEMPORARY
 * @typedef EmailOpenRateStat (Will be the same as post model probably)
 * @type {Object}
 * @property {string} id Post id
 * @property {string} title Post title
 * @property {?Object} Email model
 */

/**
 * @typedef PaidMembersByCadence
 * @type {Object}
 * @property {number} annual Paid memebrs on annual plan
 * @property {number} monthly Paid memebrs on monthly plan
 */

/**
 * @typedef PaidMembersForTier
 * @type {Object}
 * @property {Object} tier Tier object
 * @property {number} members Paid members on this tier
 */

/**
 * @typedef SiteStatus Contains information on what graphs need to be shown
 * @type {Object}
 * @property {boolean} hasPaidTiers Whether the site has paid tiers
 * @property {boolean} stripeEnabled Whether the site has stripe enabled
 * @property {boolean} newslettersEnabled Whether the site has newsletters
 * @property {boolean} membersEnabled Whether the site has members enabled
 */

export default class DashboardStatsService extends Service {
    @service dashboardMocks;
    @service store;

    /**
     * @type {?SiteStatus} Contains information on what graphs need to be shown
     */
    @tracked siteStatus = null;

    /**
     * @type {?MemberCounts} memberCounts
     */
    @tracked
        memberCounts = null;

    /**
     * @type {?MemberCountStat[]}
     */
    @tracked
        memberCountStats = null;

    /**
     * @type {?MrrStat[]}
     */
    @tracked
        mrrStats = null;

    /**
     * @type {PaidMembersByCadence} Number of members for annual and monthly plans
     */
    @tracked
        paidMembersByCadence = null;

    /**
     * @type {PaidMembersForTier[]} Number of members for each tier
     */
    @tracked
        paidMembersByTier = null;

    /**
     * @type {?number} Number of members last seen in last 30 days (could differ if filtered by member status)
     */
    @tracked
        membersLastSeen30d = null;

    /**
     * @type {?number} Number of members last seen in last 7 days (could differ if filtered by member status)
     */
    @tracked
        membersLastSeen7d = null;

    /**
     * @type {?MemberCounts} Number of members that are subscribed (grouped by status)
     */
    @tracked
        newsletterSubscribers = null;

    /**
     * @type {?number} Number of emails sent in last 30 days
     */
    @tracked
        emailsSent30d = null;

    /**
     * @type {?EmailOpenRateStat[]}
     */
    @tracked
        emailOpenRateStats = null;

    /**
     * Amount of days to load for member count and MRR related charts
     */
    @tracked chartDays = 7;

    /**
     * Filter last seen by this status
     * @type {'free'|'paid'|'total'}
     */
    @tracked lastSeenFilterStatus = 'total';

    loadSiteStatus() {
        return this._loadSiteStatus.perform();
    }

    @task
    *_loadSiteStatus() {
        this.siteStatus = null;
        if (this.dashboardMocks.enabled) {
            yield this.dashboardMocks.loadSiteStatus();
            this.siteStatus = {...this.dashboardMocks.siteStatus};
            return;
        }
        // Normal implementation
        // @todo
        this.siteStatus = {
            hasPaidTiers: true,
            stripeEnabled: true,
            newslettersEnabled: true,
            membersEnabled: true
        };
    }

    loadMembersCounts() {
        return this._loadMembersCounts.perform();
    }

    @task({restartable: true})
    *_loadMembersCounts() {
        this.memberCounts = null;
        if (this.dashboardMocks.enabled) {
            yield this.dashboardMocks.waitRandom();
            if (this.dashboardMocks.memberCounts === null) {
                return null;
            }
            this.memberCounts = {...this.dashboardMocks.memberCounts};
            return;
        }

        // @todo We need to have way to reduce the total number of API requests
        const paidResult = yield this.store.query('member', {limit: 1, filter: 'status:paid'});
        const paid = paidResult.meta.pagination.total;

        const freeResult = yield this.store.query('member', {limit: 1, filter: 'status:-paid'});
        const free = freeResult.meta.pagination.total;

        this.memberCounts = {
            total: paid + free,
            paid,
            free
        };
    }

    loadMemberCountStats() {
        // todo: add proper logic to prevent duplicate calls + reuse results if nothing has changed
        return this._loadMemberCountStats.perform();
    }

    /**
     * Loads the members graphs
     * - total paid
     * - total members
     * for each day in the last chartDays days
     */
    @task({restartable: true})
    *_loadMemberCountStats() {
        this.memberCountStats = null;
        if (this.dashboardMocks.enabled) {
            yield this.dashboardMocks.waitRandom();

            if (this.dashboardMocks.memberCountStats === null) {
                // Note: that this shouldn't happen
                return null;
            }
            this.memberCountStats = this.fillMissingDates(this.dashboardMocks.memberCountStats.slice(-this.chartDays), {paid: 0, free: 0, comped: 0}, this.chartDays);
            return;
        }

        // Normal implementation
        // @todo
    }

    loadMrrStats() {
        // todo: add proper logic to prevent duplicate calls + reuse results if nothing has changed
        return this._loadMrrStats.perform();
    }

    /**
     * Loads the mrr graphs for the current chartDays days
     */
    @task({restartable: true})
    *_loadMrrStats() {
        this.mrrStats = null;
        if (this.dashboardMocks.enabled) {
            yield this.dashboardMocks.waitRandom();
            if (this.dashboardMocks.mrrStats === null) {
                return null;
            }
            this.mrrStats = this.fillMissingDates(this.dashboardMocks.mrrStats.slice(-this.chartDays), {mrr: 0}, this.chartDays);
            return;
        }

        // Normal implementation
        // @todo
    }

    loadLastSeen() {
        // todo: add proper logic to prevent duplicate calls + reuse results if nothing has changed
        return this._loadLastSeen.perform();
    }

    /**
     * Loads the mrr graphs
     */
    @task({restartable: true})
    *_loadLastSeen() {
        this.membersLastSeen30d = null;
        this.membersLastSeen7d = null;

        if (this.dashboardMocks.enabled) {
            yield this.dashboardMocks.waitRandom();
            if (this.lastSeenFilterStatus === 'paid') {
                // @todo
            }
            this.membersLastSeen30d = this.dashboardMocks.membersLastSeen30d;
            this.membersLastSeen7d = this.dashboardMocks.membersLastSeen7d;
            return;
        }

        // @todo We need to have way to reduce the total number of API requests

        const start30d = new Date(Date.now() - 30 * 3600 * 1000);
        const start7d = new Date(Date.now() - 7 * 3600 * 1000);

        let extraFilter = '';
        if (this.lastSeenFilterStatus === 'paid') {
            extraFilter = '+status:paid';
        } else if (this.lastSeenFilterStatus === 'free') {
            extraFilter = '+status:-paid';
        }

        // todo: filter by status here
        const result30d = yield this.store.query('member', {limit: 1, filter: 'last_seen_at:>' + start30d.toISOString() + extraFilter});
        this.membersLastSeen30d = result30d.meta.pagination.total;

        const result7d = yield this.store.query('member', {limit: 1, filter: 'last_seen_at:>' + start7d.toISOString() + extraFilter});
        this.membersLastSeen7d = result7d.meta.pagination.total;
    }

    loadPaidMembersByCadence() {
        // todo: add proper logic to prevent duplicate calls + reuse results if nothing has changed
        return this._loadPaidMembersByCadence.perform();
    }

    @task({restartable: true})
    *_loadPaidMembersByCadence() {
        this.paidMembersByCadence = null;

        if (this.dashboardMocks.enabled) {
            yield this.dashboardMocks.waitRandom();
            this.paidMembersByCadence = {...this.dashboardMocks.paidMembersByCadence};
            return;
        }
        // Normal implementation
        // @todo
    }

    loadPaidMembersByTier() {
        // todo: add proper logic to prevent duplicate calls + reuse results if nothing has changed
        return this._loadPaidMembersByTier.perform();
    }

    @task({restartable: true})
    *_loadPaidMembersByTier() {
        this.paidMembersByTier = null;

        if (this.dashboardMocks.enabled) {
            yield this.dashboardMocks.waitRandom();
            this.paidMembersByTier = this.dashboardMocks.paidMembersByTier.slice();
            return;
        }
        // Normal implementation
        // @todo
    }

    loadNewsletterSubscribers() {
        // todo: add proper logic to prevent duplicate calls + reuse results if nothing has changed
        return this._loadNewsletterSubscribers.perform();
    }

    @task({restartable: true})
    *_loadNewsletterSubscribers() {
        this.newsletterSubscribers = null;

        if (this.dashboardMocks.enabled) {
            yield this.dashboardMocks.waitRandom();
            this.newsletterSubscribers = this.dashboardMocks.newsletterSubscribers;
            return;
        }
        // Normal implementation
        // @todo
    }

    loadEmailsSent() {
        // todo: add proper logic to prevent duplicate calls + reuse results if nothing has changed
        return this._loadEmailsSent.perform();
    }

    @task({restartable: true})
    *_loadEmailsSent() {
        this.emailsSent30d = null;

        if (this.dashboardMocks.enabled) {
            yield this.dashboardMocks.waitRandom();
            this.emailsSent30d = this.dashboardMocks.emailsSent30d;
            return;
        }
        // Normal implementation
        // @todo
    }

    loadEmailOpenRateStats() {
        // todo: add proper logic to prevent duplicate calls + reuse results if nothing has changed
        return this._loadEmailOpenRateStats.perform();
    }

    @task({restartable: true})
    *_loadEmailOpenRateStats() {
        this.emailOpenRateStats = null;

        if (this.dashboardMocks.enabled) {
            yield this.dashboardMocks.waitRandom();
            this.emailOpenRateStats = this.dashboardMocks.emailOpenRateStats;
            return;
        }

        const posts = yield this.store.query('post', {limit: 5, filter: 'status:published', order: 'published_at desc'});
        this.emailOpenRateStats = posts;
    }

    /**
     * For now this is only used when reloading all the graphs after changing the mocked data
     * @todo: reload only data that we loaded earlier
     */
    reloadAll() {
        this.loadMembersCounts();
        this.loadMrrStats();
        this.loadMemberCountStats();
        this.loadLastSeen();
        this.loadPaidMembersByCadence();
        this.loadPaidMembersByTier();

        this.loadNewsletterSubscribers();
        this.loadEmailsSent();
        this.loadEmailOpenRateStats();
    }

    /**
     * Fill data to match a given amount of days
     * @param {MemberCountStat[]|MrrStat[]} data
     * @param {MemberCountStat|MrrStat} defaultData
     * @param {number} days Amount of days to fill the graph with
     */
    fillMissingDates(data, defaultData, days) {
        let currentRangeDate = moment().subtract(days, 'days');

        let endDate = moment().add(1, 'hour');
        const output = [];
        const firstDateInRangeIndex = data.findIndex((val) => {
            return moment(val.date).isAfter(currentRangeDate);
        });
        let initialDateInRangeVal = firstDateInRangeIndex > 0 ? data[firstDateInRangeIndex - 1] : null;
        if (firstDateInRangeIndex === 0 && !initialDateInRangeVal) {
            initialDateInRangeVal = data[firstDateInRangeIndex];
        }
        if (data.length > 0 && !initialDateInRangeVal && firstDateInRangeIndex !== 0) {
            initialDateInRangeVal = data[data.length - 1];
        }

        let lastVal = initialDateInRangeVal ? initialDateInRangeVal : defaultData;

        while (currentRangeDate.isBefore(endDate)) {
            let dateStr = currentRangeDate.format('YYYY-MM-DD');
            const dataOnDate = data.find(d => d.date === dateStr);
            lastVal = dataOnDate ? dataOnDate : {...lastVal, date: dateStr};
            output.push(lastVal);
            currentRangeDate = currentRangeDate.add(1, 'day');
        }
        return output;
    }
}
<commit_msg>Added basic member counts chart implementation for dashboard 5<commit_after>import Service, {inject as service} from '@ember/service';
import moment from 'moment';
import {task} from 'ember-concurrency';
import {tracked} from '@glimmer/tracking';

/**
 * @typedef MrrStat
 * @type {Object}
 * @property {string} date The date (YYYY-MM-DD) on which this MRR was recorded
 * @property {number} mrr The MRR on this date
 */

/**
 * @typedef MemberCountStat
 * @type {Object}
 * @property {string} date The date (YYYY-MM-DD) on which these counts were recorded
 * @property {number} paid Amount of paid members
 * @property {number} free Amount of free members
 * @property {number} comped Amount of comped members
 * @property {number} newPaid Amount of new paid members
 * @property {number} canceledPaid Amount of canceled paid members
 */

/**
 * @typedef MemberCounts
 * @type {Object}
 * @property {number} total Total amount of members
 * @property {number} paid Amount of paid members
 * @property {number} free Amount of free members
 */

/**
 * @todo: THIS ONE IS TEMPORARY
 * @typedef EmailOpenRateStat (Will be the same as post model probably)
 * @type {Object}
 * @property {string} id Post id
 * @property {string} title Post title
 * @property {?Object} Email model
 */

/**
 * @typedef PaidMembersByCadence
 * @type {Object}
 * @property {number} annual Paid memebrs on annual plan
 * @property {number} monthly Paid memebrs on monthly plan
 */

/**
 * @typedef PaidMembersForTier
 * @type {Object}
 * @property {Object} tier Tier object
 * @property {number} members Paid members on this tier
 */

/**
 * @typedef SiteStatus Contains information on what graphs need to be shown
 * @type {Object}
 * @property {boolean} hasPaidTiers Whether the site has paid tiers
 * @property {boolean} stripeEnabled Whether the site has stripe enabled
 * @property {boolean} newslettersEnabled Whether the site has newsletters
 * @property {boolean} membersEnabled Whether the site has members enabled
 */

export default class DashboardStatsService extends Service {
    @service dashboardMocks;
    @service store;
    @service ajax;
    @service ghostPaths;

    /**
     * @type {?SiteStatus} Contains information on what graphs need to be shown
     */
    @tracked siteStatus = null;

    /**
     * @type {?MemberCounts} memberCounts
     */
    @tracked
        memberCounts = null;

    /**
     * @type {?MemberCountStat[]}
     */
    @tracked
        memberCountStats = null;

    /**
     * @type {?MrrStat[]}
     */
    @tracked
        mrrStats = null;

    /**
     * @type {PaidMembersByCadence} Number of members for annual and monthly plans
     */
    @tracked
        paidMembersByCadence = null;

    /**
     * @type {PaidMembersForTier[]} Number of members for each tier
     */
    @tracked
        paidMembersByTier = null;

    /**
     * @type {?number} Number of members last seen in last 30 days (could differ if filtered by member status)
     */
    @tracked
        membersLastSeen30d = null;

    /**
     * @type {?number} Number of members last seen in last 7 days (could differ if filtered by member status)
     */
    @tracked
        membersLastSeen7d = null;

    /**
     * @type {?MemberCounts} Number of members that are subscribed (grouped by status)
     */
    @tracked
        newsletterSubscribers = null;

    /**
     * @type {?number} Number of emails sent in last 30 days
     */
    @tracked
        emailsSent30d = null;

    /**
     * @type {?EmailOpenRateStat[]}
     */
    @tracked
        emailOpenRateStats = null;

    /**
     * Amount of days to load for member count and MRR related charts
     */
    @tracked chartDays = 7;

    /**
     * Filter last seen by this status
     * @type {'free'|'paid'|'total'}
     */
    @tracked lastSeenFilterStatus = 'total';

    loadSiteStatus() {
        return this._loadSiteStatus.perform();
    }

    @task
    *_loadSiteStatus() {
        this.siteStatus = null;
        if (this.dashboardMocks.enabled) {
            yield this.dashboardMocks.loadSiteStatus();
            this.siteStatus = {...this.dashboardMocks.siteStatus};
            return;
        }
        // Normal implementation
        // @todo
        this.siteStatus = {
            hasPaidTiers: true,
            stripeEnabled: true,
            newslettersEnabled: true,
            membersEnabled: true
        };
    }

    loadMembersCounts() {
        return this._loadMembersCounts.perform();
    }

    @task({restartable: true})
    *_loadMembersCounts() {
        this.memberCounts = null;
        if (this.dashboardMocks.enabled) {
            yield this.dashboardMocks.waitRandom();
            if (this.dashboardMocks.memberCounts === null) {
                return null;
            }
            this.memberCounts = {...this.dashboardMocks.memberCounts};
            return;
        }

        // @todo We need to have way to reduce the total number of API requests
        const paidResult = yield this.store.query('member', {limit: 1, filter: 'status:paid'});
        const paid = paidResult.meta.pagination.total;

        const freeResult = yield this.store.query('member', {limit: 1, filter: 'status:-paid'});
        const free = freeResult.meta.pagination.total;

        this.memberCounts = {
            total: paid + free,
            paid,
            free
        };
    }

    loadMemberCountStats() {
        // todo: add proper logic to prevent duplicate calls + reuse results if nothing has changed
        return this._loadMemberCountStats.perform();
    }

    /**
     * Loads the members graphs
     * - total paid
     * - total members
     * for each day in the last chartDays days
     */
    @task({restartable: true})
    *_loadMemberCountStats() {
        this.memberCountStats = null;
        if (this.dashboardMocks.enabled) {
            yield this.dashboardMocks.waitRandom();

            if (this.dashboardMocks.memberCountStats === null) {
                // Note: that this shouldn't happen
                return null;
            }
            this.memberCountStats = this.fillMissingDates(this.dashboardMocks.memberCountStats, {paid: 0, free: 0, comped: 0}, this.chartDays);
            return;
        }

        // @todo: we need to reuse the result of the call when we reload, because the endpoint returns all available days
        // at the moment. We can reuse the result to show 7 days, 30 days, ...
        let statsUrl = this.ghostPaths.url.api('members/stats/count');
        let stats = yield this.ajax.request(statsUrl);
        this.memberCountStats = this.fillMissingDates(stats.data, {paid: 0, free: 0, comped: 0}, this.chartDays);
    }

    loadMrrStats() {
        // todo: add proper logic to prevent duplicate calls + reuse results if nothing has changed
        return this._loadMrrStats.perform();
    }

    /**
     * Loads the mrr graphs for the current chartDays days
     */
    @task({restartable: true})
    *_loadMrrStats() {
        this.mrrStats = null;
        if (this.dashboardMocks.enabled) {
            yield this.dashboardMocks.waitRandom();
            if (this.dashboardMocks.mrrStats === null) {
                return null;
            }
            this.mrrStats = this.fillMissingDates(this.dashboardMocks.mrrStats.slice(-this.chartDays), {mrr: 0}, this.chartDays);
            return;
        }

        // Normal implementation
        // @todo
    }

    loadLastSeen() {
        // todo: add proper logic to prevent duplicate calls + reuse results if nothing has changed
        return this._loadLastSeen.perform();
    }

    /**
     * Loads the mrr graphs
     */
    @task({restartable: true})
    *_loadLastSeen() {
        this.membersLastSeen30d = null;
        this.membersLastSeen7d = null;

        if (this.dashboardMocks.enabled) {
            yield this.dashboardMocks.waitRandom();
            if (this.lastSeenFilterStatus === 'paid') {
                // @todo
            }
            this.membersLastSeen30d = this.dashboardMocks.membersLastSeen30d;
            this.membersLastSeen7d = this.dashboardMocks.membersLastSeen7d;
            return;
        }

        // @todo We need to have way to reduce the total number of API requests

        const start30d = new Date(Date.now() - 30 * 3600 * 1000);
        const start7d = new Date(Date.now() - 7 * 3600 * 1000);

        let extraFilter = '';
        if (this.lastSeenFilterStatus === 'paid') {
            extraFilter = '+status:paid';
        } else if (this.lastSeenFilterStatus === 'free') {
            extraFilter = '+status:-paid';
        }

        // todo: filter by status here
        const result30d = yield this.store.query('member', {limit: 1, filter: 'last_seen_at:>' + start30d.toISOString() + extraFilter});
        this.membersLastSeen30d = result30d.meta.pagination.total;

        const result7d = yield this.store.query('member', {limit: 1, filter: 'last_seen_at:>' + start7d.toISOString() + extraFilter});
        this.membersLastSeen7d = result7d.meta.pagination.total;
    }

    loadPaidMembersByCadence() {
        // todo: add proper logic to prevent duplicate calls + reuse results if nothing has changed
        return this._loadPaidMembersByCadence.perform();
    }

    @task({restartable: true})
    *_loadPaidMembersByCadence() {
        this.paidMembersByCadence = null;

        if (this.dashboardMocks.enabled) {
            yield this.dashboardMocks.waitRandom();
            this.paidMembersByCadence = {...this.dashboardMocks.paidMembersByCadence};
            return;
        }
        // Normal implementation
        // @todo
    }

    loadPaidMembersByTier() {
        // todo: add proper logic to prevent duplicate calls + reuse results if nothing has changed
        return this._loadPaidMembersByTier.perform();
    }

    @task({restartable: true})
    *_loadPaidMembersByTier() {
        this.paidMembersByTier = null;

        if (this.dashboardMocks.enabled) {
            yield this.dashboardMocks.waitRandom();
            this.paidMembersByTier = this.dashboardMocks.paidMembersByTier.slice();
            return;
        }
        // Normal implementation
        // @todo
    }

    loadNewsletterSubscribers() {
        // todo: add proper logic to prevent duplicate calls + reuse results if nothing has changed
        return this._loadNewsletterSubscribers.perform();
    }

    @task({restartable: true})
    *_loadNewsletterSubscribers() {
        this.newsletterSubscribers = null;

        if (this.dashboardMocks.enabled) {
            yield this.dashboardMocks.waitRandom();
            this.newsletterSubscribers = this.dashboardMocks.newsletterSubscribers;
            return;
        }
        // Normal implementation
        // @todo
    }

    loadEmailsSent() {
        // todo: add proper logic to prevent duplicate calls + reuse results if nothing has changed
        return this._loadEmailsSent.perform();
    }

    @task({restartable: true})
    *_loadEmailsSent() {
        this.emailsSent30d = null;

        if (this.dashboardMocks.enabled) {
            yield this.dashboardMocks.waitRandom();
            this.emailsSent30d = this.dashboardMocks.emailsSent30d;
            return;
        }
        // Normal implementation
        // @todo
    }

    loadEmailOpenRateStats() {
        // todo: add proper logic to prevent duplicate calls + reuse results if nothing has changed
        return this._loadEmailOpenRateStats.perform();
    }

    @task({restartable: true})
    *_loadEmailOpenRateStats() {
        this.emailOpenRateStats = null;

        if (this.dashboardMocks.enabled) {
            yield this.dashboardMocks.waitRandom();
            this.emailOpenRateStats = this.dashboardMocks.emailOpenRateStats;
            return;
        }

        const posts = yield this.store.query('post', {limit: 5, filter: 'status:published', order: 'published_at desc'});
        this.emailOpenRateStats = posts;
    }

    /**
     * For now this is only used when reloading all the graphs after changing the mocked data
     * @todo: reload only data that we loaded earlier
     */
    reloadAll() {
        this.loadMembersCounts();
        this.loadMrrStats();
        this.loadMemberCountStats();
        this.loadLastSeen();
        this.loadPaidMembersByCadence();
        this.loadPaidMembersByTier();

        this.loadNewsletterSubscribers();
        this.loadEmailsSent();
        this.loadEmailOpenRateStats();
    }

    /**
     * Fill data to match a given amount of days
     * @param {MemberCountStat[]|MrrStat[]} data
     * @param {MemberCountStat|MrrStat} defaultData
     * @param {number} days Amount of days to fill the graph with
     */
    fillMissingDates(data, defaultData, days) {
        let currentRangeDate = moment().subtract(days, 'days');

        let endDate = moment().add(1, 'hour');
        const output = [];
        const firstDateInRangeIndex = data.findIndex((val) => {
            return moment(val.date).isAfter(currentRangeDate);
        });
        let initialDateInRangeVal = firstDateInRangeIndex > 0 ? data[firstDateInRangeIndex - 1] : null;
        if (firstDateInRangeIndex === 0 && !initialDateInRangeVal) {
            initialDateInRangeVal = data[firstDateInRangeIndex];
        }
        if (data.length > 0 && !initialDateInRangeVal && firstDateInRangeIndex !== 0) {
            initialDateInRangeVal = data[data.length - 1];
        }

        let lastVal = initialDateInRangeVal ? initialDateInRangeVal : defaultData;

        while (currentRangeDate.isBefore(endDate)) {
            let dateStr = currentRangeDate.format('YYYY-MM-DD');
            const dataOnDate = data.find(d => d.date === dateStr);
            lastVal = dataOnDate ? dataOnDate : {...lastVal, date: dateStr};
            output.push(lastVal);
            currentRangeDate = currentRangeDate.add(1, 'day');
        }
        return output;
    }
}
<|endoftext|>


<commit_before>import Ember from 'ember';

export default Ember.Service.extend({
  databaseNamespace: 'ember-idb',
  version: 1,
  objectStores: [],
  init: function() {
    this._addObjectStores();
  },
  getConnection: function () {
    return new Ember.RSVP.Promise((resolve, reject) => {

      var openRequest = indexedDB.open(this.get('databaseNamespace'), this.get('version'));

      openRequest.onsuccess = function (e) {
        resolve(e.target.result);
      }

      openRequest.onerror = function (e) {
        reject(e.target);
      };
    });
  },
  save: function (storeName, object) {
    return new Ember.RSVP.Promise((resolve, reject) => {
      this.getConnection().then(function(db) {

        var transaction = db.transaction([storeName], 'readwrite');

        var objectStore = transaction.objectStore(storeName);

        var storeRequest = objectStore.add(object);

        storeRequest.onsuccess = function () {
          resolve(true);
          db.close();
        }

        storeRequest.onerror = function (e) {
          console.log('Store error', e);
          reject(false);
          db.close()
        }

      });
    })
  },
  retreive: function(storeName, id) {
    return new Ember.RSVP.Promise((resolve, reject) => {
      this.getConnection().then(function(db) {
        var transaction = db.transaction([storeName], 'readonly');

        var objectStore = transaction.objectStore(storeName);

        var findRequest = objectStore.get(id);

        findRequest.onsuccess = function (e) {
          var result = e.target.result;
          if (result !== undefined) {
            resolve(result)
          } else {
            reject(`Record with id ${id} not found`);
          }
          db.close();
        }

        findRequest.onerror = function (e) {
          console.log('Retreive error', e);
          db.close();
        }
      });
    });
  },
  _addObjectStores: function () {
    var self = this;
    return new Ember.RSVP.Promise((resolve, reject) => {
      var version = this.get('version') + 1;

      var openRequest = indexedDB.open(this.get('databaseNamespace', version));

      openRequest.onupgradeneeded = function (e) {
        var db = e.target.result;
        self.objectStores.forEach(function(storeName){
          if (!db.objectStoreNames.contains(storeName)) {
            return db.createObjectStore(storeName, { autoIncrement : true });
          };
        });
      }

      openRequest.onsuccess = function (e) {
        e.target.result.close();
        self.set('version', version);
        resolve();
      }

      openRequest.onerror = function (e) {
        console.log('onerror', e);
        reject(e);
      }

    })
  }
});
<commit_msg>Don't need to return false when save fails<commit_after>import Ember from 'ember';

export default Ember.Service.extend({
  databaseNamespace: 'ember-idb',
  version: 1,
  objectStores: [],
  init: function() {
    this._addObjectStores();
  },
  getConnection: function () {
    return new Ember.RSVP.Promise((resolve, reject) => {

      var openRequest = indexedDB.open(this.get('databaseNamespace'), this.get('version'));

      openRequest.onsuccess = function (e) {
        resolve(e.target.result);
      }

      openRequest.onerror = function (e) {
        reject(e.target);
      };
    });
  },
  save: function (storeName, object) {
    return new Ember.RSVP.Promise((resolve, reject) => {
      this.getConnection().then(function(db) {

        var transaction = db.transaction([storeName], 'readwrite');

        var objectStore = transaction.objectStore(storeName);

        var storeRequest = objectStore.add(object);

        storeRequest.onsuccess = function () {
          resolve(true);
          db.close();
        }

        storeRequest.onerror = function (e) {
          console.log('Store error', e);
          reject();
          db.close()
        }

      });
    })
  },
  retreive: function(storeName, id) {
    return new Ember.RSVP.Promise((resolve, reject) => {
      this.getConnection().then(function(db) {
        var transaction = db.transaction([storeName], 'readonly');

        var objectStore = transaction.objectStore(storeName);

        var findRequest = objectStore.get(id);

        findRequest.onsuccess = function (e) {
          var result = e.target.result;
          if (result !== undefined) {
            resolve(result)
          } else {
            reject(`Record with id ${id} not found`);
          }
          db.close();
        }

        findRequest.onerror = function (e) {
          console.log('Retreive error', e);
          db.close();
        }
      });
    });
  },
  _addObjectStores: function () {
    var self = this;
    return new Ember.RSVP.Promise((resolve, reject) => {
      var version = this.get('version') + 1;

      var openRequest = indexedDB.open(this.get('databaseNamespace', version));

      openRequest.onupgradeneeded = function (e) {
        var db = e.target.result;
        self.objectStores.forEach(function(storeName){
          if (!db.objectStoreNames.contains(storeName)) {
            return db.createObjectStore(storeName, { autoIncrement : true });
          };
        });
      }

      openRequest.onsuccess = function (e) {
        e.target.result.close();
        self.set('version', version);
        resolve();
      }

      openRequest.onerror = function (e) {
        console.log('onerror', e);
        reject(e);
      }

    })
  }
});
<|endoftext|>


<commit_before>define(['backbone', 'underscore', 'common/utils/padTwo', 'common/utils/modulify'],
  function(Backbone, _, padTwo, modulify) {
    'use strict';

    // Convert exam in ISO format to 12-hour date/time format. We slice off the
    // SGT time zone and interpret as UTC time, then use the getUTC* methods so
    // that they will correspond to Singapore time regardless of the local time
    // zone.
    var examStr = function (exam) {
      if (exam) {
        var date = new Date(exam.slice(0,16) + 'Z');
        var hours = date.getUTCHours();
        return padTwo(date.getUTCDate()) +
          '-' + padTwo(date.getUTCMonth() + 1) +
          '-' + date.getUTCFullYear() +
          ' ' + (hours % 12 || 12) +
          ':' + padTwo(date.getUTCMinutes()) +
          ' ' + (hours < 12 ? 'AM' : 'PM');
      }
      return null;
    };

    var DESCRIPTION_LIMIT = 40;

    var shortenDescription = function (desc) {
      return desc.split(' ').splice(0, DESCRIPTION_LIMIT).join(' ');
    }

    var workloadify = function (workload) {
      var workloadArray = workload.split('-');
      var workloadComponents = {
        lectureHours: workloadArray[0],
        tutorialHours: workloadArray[1],
        labHours: workloadArray[2],
        projectHours: workloadArray[3],
        preparationHours: workloadArray[4]
      };
      _.each(workloadComponents, function (value, key) {
        workloadComponents[key] = parseInt(value);
      });
      return workloadComponents;
    }

    return Backbone.Model.extend({
      idAttribute: 'ModuleCode',
      initialize: function() {
        // TODO: Display exam date when 2014-2015/1 exam timetable released.

        var description = this.get('ModuleDescription');
        if (description && description.split(' ').length > DESCRIPTION_LIMIT + 10) {
          this.set('ShortModuleDescription', shortenDescription(this.get('ModuleDescription')));
        }

        var workload = this.get('Workload');
        if (workload) {
          this.set('WorkloadComponents', workloadify(workload));
        }

        var prerequisite = this.get('Prerequisite');
        if (prerequisite) {
          this.set('parsedPrerequisite', modulify.linkifyModules(prerequisite));
        }

        var preclusion = this.get('Preclusion');
        if (preclusion) {
          this.set('parsedPreclusion', modulify.linkifyModules(preclusion));
        }

        var timetable = this.get('Timetable');
        if (timetable) {
          var timetableTypes = [];
          _.each(timetable, function (lesson) {
            if (timetableTypes.indexOf(lesson.LessonType) < 0) {
              timetableTypes.push(lesson.LessonType);
            }
          });

          var AVAILABLE_TYPES = ['Lecture', 'Sectional Teaching', 'Seminar-Style Module Class', 'Packaged Lecture', 'Packaged Tutorial',
                                 'Tutorial', 'Tutorial Type 2', 'Tutorial Type 3', 'Design Lecture', 'Laboratory', 'Recitation'];
          timetableTypes = _.sortBy(timetableTypes, function (type) {
            return AVAILABLE_TYPES.indexOf(type);
          })

          var formattedTimetable = [];
          _.each(timetableTypes, function (type) {
            var lessons = _.filter(timetable, function (lesson) {
              return lesson.LessonType == type;
            });
            lessons = _.sortBy(lessons, function (lesson) {
              return parseInt(lesson.ClassNo);
            });
            _.each(lessons, function (lesson) {
              delete lesson.LessonType;
            });
            formattedTimetable.push({
              LessonType: type,
              Lessons: lessons
            });
          })

          this.set('FormattedTimetable', formattedTimetable);
        }

        var corsBiddingStats = this.get('CorsBiddingStats');
        if (corsBiddingStats) {
          var formattedCorsBiddingStats = [];

          var semesters = [];
          _.each(corsBiddingStats, function (stats) {
            var sem = stats.AcadYear + ',' + stats.Semester;
            if (semesters.indexOf(sem) < 0) {
              semesters.push(sem);
            }
          });

          _.each(semesters, function (sem) {
            var parts = sem.split(',');
            var acadYear = parts[0];
            var semester = parts[1];
            var stats = _.filter(corsBiddingStats, function (stat) {
              return stat.AcadYear === acadYear && stat.Semester === semester;
            });

            _.each(stats, function (stat) {
              delete stat.AcadYear;
              delete stat.Semester;
            });

            formattedCorsBiddingStats.push({
              Semester: 'AY' + acadYear + ' Sem ' + semester,
              BiddingStats: stats
            });
          });
          this.set('FormattedCorsBiddingStats', formattedCorsBiddingStats);
        }

        this.set('examStr', examStr(this.get('ExamDate')));
      }
    });
  });
<commit_msg>Use omit for repeated fields in CORS bidding history<commit_after>define(['backbone', 'underscore', 'common/utils/padTwo', 'common/utils/modulify'],
  function(Backbone, _, padTwo, modulify) {
    'use strict';

    // Convert exam in ISO format to 12-hour date/time format. We slice off the
    // SGT time zone and interpret as UTC time, then use the getUTC* methods so
    // that they will correspond to Singapore time regardless of the local time
    // zone.
    var examStr = function (exam) {
      if (exam) {
        var date = new Date(exam.slice(0,16) + 'Z');
        var hours = date.getUTCHours();
        return padTwo(date.getUTCDate()) +
          '-' + padTwo(date.getUTCMonth() + 1) +
          '-' + date.getUTCFullYear() +
          ' ' + (hours % 12 || 12) +
          ':' + padTwo(date.getUTCMinutes()) +
          ' ' + (hours < 12 ? 'AM' : 'PM');
      }
      return null;
    };

    var DESCRIPTION_LIMIT = 40;

    var shortenDescription = function (desc) {
      return desc.split(' ').splice(0, DESCRIPTION_LIMIT).join(' ');
    }

    var workloadify = function (workload) {
      var workloadArray = workload.split('-');
      var workloadComponents = {
        lectureHours: workloadArray[0],
        tutorialHours: workloadArray[1],
        labHours: workloadArray[2],
        projectHours: workloadArray[3],
        preparationHours: workloadArray[4]
      };
      _.each(workloadComponents, function (value, key) {
        workloadComponents[key] = parseInt(value);
      });
      return workloadComponents;
    }

    return Backbone.Model.extend({
      idAttribute: 'ModuleCode',
      initialize: function() {
        // TODO: Display exam date when 2014-2015/1 exam timetable released.

        var description = this.get('ModuleDescription');
        if (description && description.split(' ').length > DESCRIPTION_LIMIT + 10) {
          this.set('ShortModuleDescription', shortenDescription(this.get('ModuleDescription')));
        }

        var workload = this.get('Workload');
        if (workload) {
          this.set('WorkloadComponents', workloadify(workload));
        }

        var prerequisite = this.get('Prerequisite');
        if (prerequisite) {
          this.set('parsedPrerequisite', modulify.linkifyModules(prerequisite));
        }

        var preclusion = this.get('Preclusion');
        if (preclusion) {
          this.set('parsedPreclusion', modulify.linkifyModules(preclusion));
        }

        var timetable = this.get('Timetable');
        if (timetable) {
          var timetableTypes = [];
          _.each(timetable, function (lesson) {
            if (timetableTypes.indexOf(lesson.LessonType) < 0) {
              timetableTypes.push(lesson.LessonType);
            }
          });

          var AVAILABLE_TYPES = ['Lecture', 'Sectional Teaching', 'Seminar-Style Module Class', 'Packaged Lecture', 'Packaged Tutorial',
                                 'Tutorial', 'Tutorial Type 2', 'Tutorial Type 3', 'Design Lecture', 'Laboratory', 'Recitation'];
          timetableTypes = _.sortBy(timetableTypes, function (type) {
            return AVAILABLE_TYPES.indexOf(type);
          })

          var formattedTimetable = [];
          _.each(timetableTypes, function (type) {
            var lessons = _.filter(timetable, function (lesson) {
              return lesson.LessonType == type;
            });
            lessons = _.sortBy(lessons, function (lesson) {
              return parseInt(lesson.ClassNo);
            });
            _.each(lessons, function (lesson) {
              delete lesson.LessonType;
            });
            formattedTimetable.push({
              LessonType: type,
              Lessons: lessons
            });
          })

          this.set('FormattedTimetable', formattedTimetable);
        }

        var corsBiddingStats = this.get('CorsBiddingStats');
        if (corsBiddingStats) {
          var formattedCorsBiddingStats = [];

          var semesters = [];
          _.each(corsBiddingStats, function (stats) {
            var sem = stats.AcadYear + ',' + stats.Semester;
            if (semesters.indexOf(sem) < 0) {
              semesters.push(sem);
            }
          });

          _.each(semesters, function (sem) {
            var parts = sem.split(',');
            var acadYear = parts[0];
            var semester = parts[1];
            var stats = _.filter(corsBiddingStats, function (stat) {
              return stat.AcadYear === acadYear && stat.Semester === semester;
            });

            stats = _.map(stats, function (stat) {
              stat = _.omit(stat, ['AcadYear', 'Semester']);
              return stat;
            });

            formattedCorsBiddingStats.push({
              Semester: 'AY' + acadYear + ' Sem ' + semester,
              BiddingStats: stats
            });
          });
          this.set('FormattedCorsBiddingStats', formattedCorsBiddingStats);
        }

        this.set('examStr', examStr(this.get('ExamDate')));
      }
    });
  });
<|endoftext|>


<commit_before>export function ContactGroupsCtrl ($scope, $uibModal, Api, Alerting) {
  /************************************************/
  /**         Initial load & onChanged           **/
  /************************************************/
  let querySuccess = function (contactGroups) {
    $scope.contactGroups = contactGroups
    if (contactGroups.length === 0) {
      Alerting.AlertAddMsg('bottom', 'warning', 'There are currently no contact lists created')
    }
  }

  let queryError = function (err) {
    // on error - add server error alert
    Alerting.AlertAddServerMsg(err.status)
  }

  // do the initial request
  Api.ContactGroups.query(querySuccess, queryError)

  $scope.$on('contactGroupChanged', function () {
    Api.ContactGroups.query(querySuccess, queryError)
  })
  /************************************************/
  /**         Initial load & onChanged           **/
  /************************************************/

  /**********************************************************/
  /**         Add/edit contactGroups popup modal           **/
  /**********************************************************/
  $scope.addContactGroup = function () {
    Alerting.AlertReset()
    $uibModal.open({
      templateUrl: 'views/contactGroupsModal.html',
      controller: 'ContactGroupsModalCtrl',
      resolve: {
        contactGroup: function () {
        }
      }
    })
  }

  $scope.editContactGroup = function (contactGroup) {
    Alerting.AlertReset()

    $uibModal.open({
      templateUrl: 'views/contactGroupsModal.html',
      controller: 'ContactGroupsModalCtrl',
      resolve: {
        contactGroup: function () {
          return contactGroup
        }
      }
    })
  }
  /**********************************************************/
  /**         Add/edit contactGroups popup modal           **/
  /**********************************************************/

  /*******************************************/
  /**         Delete Confirmation           **/
  /*******************************************/
  let deleteSuccess = function () {
    // On success
    $scope.contactGroups = Api.ContactGroups.query()
    Alerting.AlertAddMsg('top', 'success', 'The contact list has been deleted successfully')
  }

  let deleteError = function (err) {
    if (err.status === 409) {
      let warningMessage = 'Could not delete the contact list because it is associated with the following channels: '
      for (let i = 0; i < err.data.length; i++) {
        if (i > 0) {
          warningMessage += ', '
        }
        warningMessage += err.data[i].name
      }
      Alerting.AlertAddMsg('top', 'warning', warningMessage)
    } else {
      // add the error message
      Alerting.AlertAddMsg('top', 'danger', 'An error has occurred while deleting the contact list: #' + err.status + ' - ' + err.data)
    }
  }

  $scope.confirmDelete = function (contactGroup) {
    Alerting.AlertReset()

    let deleteObject = {
      title: 'Delete Contact Group',
      button: 'Delete',
      message: 'Are you sure you wish to delete the Contact list "' + contactGroup.group + '"?'
    }

    let modalInstance = $uibModal.open({
      templateUrl: 'views/confirmModal.html',
      controller: 'ConfirmModalCtrl',
      resolve: {
        confirmObject: function () {
          return deleteObject
        }
      }
    })

    modalInstance.result.then(function () {
      // Delete confirmed - delete the contact group
      contactGroup.$remove(deleteSuccess, deleteError)
    }, function () {
      // delete cancelled - do nothing
    })
  }

  /*******************************************/
  /**         Delete Confirmation           **/
  /*******************************************/
}
<commit_msg>Corrected contactGroups<commit_after>import { contactGroupsModal, confirmModal } from '~/views'
import { ContactGroupsModalCtrl, ConfirmModalCtrl } from './'

export function ContactGroupsCtrl ($scope, $uibModal, Api, Alerting) {
  /************************************************/
  /**         Initial load & onChanged           **/
  /************************************************/
  let querySuccess = function (contactGroups) {
    $scope.contactGroups = contactGroups
    if (contactGroups.length === 0) {
      Alerting.AlertAddMsg('bottom', 'warning', 'There are currently no contact lists created')
    }
  }

  let queryError = function (err) {
    // on error - add server error alert
    Alerting.AlertAddServerMsg(err.status)
  }

  // do the initial request
  Api.ContactGroups.query(querySuccess, queryError)

  $scope.$on('contactGroupChanged', function () {
    Api.ContactGroups.query(querySuccess, queryError)
  })
  /************************************************/
  /**         Initial load & onChanged           **/
  /************************************************/

  /**********************************************************/
  /**         Add/edit contactGroups popup modal           **/
  /**********************************************************/
  $scope.addContactGroup = function () {
    Alerting.AlertReset()
    $uibModal.open({
      template: contactGroupsModal,
      controller: ContactGroupsModalCtrl,
      resolve: {
        contactGroup: function () {
        }
      }
    })
  }

  $scope.editContactGroup = function (contactGroup) {
    Alerting.AlertReset()

    $uibModal.open({
      template: contactGroupsModal,
      controller: ContactGroupsModalCtrl,
      resolve: {
        contactGroup: function () {
          return contactGroup
        }
      }
    })
  }
  /**********************************************************/
  /**         Add/edit contactGroups popup modal           **/
  /**********************************************************/

  /*******************************************/
  /**         Delete Confirmation           **/
  /*******************************************/
  let deleteSuccess = function () {
    // On success
    $scope.contactGroups = Api.ContactGroups.query()
    Alerting.AlertAddMsg('top', 'success', 'The contact list has been deleted successfully')
  }

  let deleteError = function (err) {
    if (err.status === 409) {
      let warningMessage = 'Could not delete the contact list because it is associated with the following channels: '
      for (let i = 0; i < err.data.length; i++) {
        if (i > 0) {
          warningMessage += ', '
        }
        warningMessage += err.data[i].name
      }
      Alerting.AlertAddMsg('top', 'warning', warningMessage)
    } else {
      // add the error message
      Alerting.AlertAddMsg('top', 'danger', 'An error has occurred while deleting the contact list: #' + err.status + ' - ' + err.data)
    }
  }

  $scope.confirmDelete = function (contactGroup) {
    Alerting.AlertReset()

    let deleteObject = {
      title: 'Delete Contact Group',
      button: 'Delete',
      message: 'Are you sure you wish to delete the Contact list "' + contactGroup.group + '"?'
    }

    let modalInstance = $uibModal.open({
      template: confirmModal,
      controller: ConfirmModalCtrl,
      resolve: {
        confirmObject: function () {
          return deleteObject
        }
      }
    })

    modalInstance.result.then(function () {
      // Delete confirmed - delete the contact group
      contactGroup.$remove(deleteSuccess, deleteError)
    }, function () {
      // delete cancelled - do nothing
    })
  }

  /*******************************************/
  /**         Delete Confirmation           **/
  /*******************************************/
}
<|endoftext|>


<commit_before>"use strict";

var fs = require('fs'),
    request = require("request"),
    url = require("url"),
    limitsConfigSchema = require('./limits-config-schema'),
    Bucket = require("./bucket").Bucket,
    log4js = require('log4js');

exports.isValidConfig = isValidConfig;
exports.LimitsConfiguration = LimitsConfiguration;
exports.LimitsConfigurationLoader = LimitsConfigurationLoader;

exports.defaultLimitsConfig = {
    version: 1,
    max_requests: 0,  // 0 means unlimited
    buffer_ratio: 0,
    buckets: [{name: "default"}]
};

if (!isValidConfig(exports.defaultLimitsConfig)) {
    throw new Error("Invalid defaultLimitsConfig");
}

var logger = log4js.getLogger();

function LimitsConfiguration(cfg) {
    this.buckets = [];
    this.bufferRatio = cfg.buffer_ratio;

    this.maxRequests = cfg.max_requests;
    this.maxRequestsWithoutBuffer = Math.floor(this.maxRequests * (1 - this.bufferRatio));
    this.healthcheckUrl = cfg.healthcheck_url;

    this.buckets = this.initializeBuckets(cfg.buckets);

    var sumOfCapacityUnits = this.calculateTotalCapacityUnits();
    this.buckets.forEach(function (bucket) {
        if (sumOfCapacityUnits === 0) {
            bucket.maxRequests = 0;
        } else {
            var bucketCapacityRatio = bucket.capacityUnit / sumOfCapacityUnits;
            bucket.maxRequests = Math.ceil(this.maxRequestsWithoutBuffer * bucketCapacityRatio);
        }
    }, this);
}

LimitsConfiguration.prototype = {
    initializeBuckets: function (buckets) {
        var defaultBucket = null;
        var initializedBuckets = [];

        buckets.forEach(function (bucketConfig) {
            var bucket = new Bucket(bucketConfig);

            if (bucket.isDefault()) {
                if (defaultBucket !== null) {
                    throw new Error("There is more than one default buckets defined in the limits configuration.");
                }
                defaultBucket = bucket;  // we will push it separately to be at the end of the array
            } else {
                initializedBuckets.push(bucket);
            }
        });

        if (defaultBucket === null) {
            throw new Error("No default bucket was set.");
        }
        initializedBuckets.push(defaultBucket);

        return initializedBuckets;
    },

    calculateTotalCapacityUnits: function () {
        var capacityUnits = 0;
        this.buckets.forEach(function (bucket) {
            capacityUnits += bucket.capacityUnit;
        });
        return capacityUnits;
    }
};

function LimitsConfigurationLoader(configEndpoint) {
    this.configEndpoint = configEndpoint;
    this.limitsConfigurationSource = null;

}

LimitsConfigurationLoader.prototype = {
    load: function (callback) {
        this.limitsConfigurationSource = new LimitsConfigurationSource(this.configEndpoint);
        var forwardValidConfigCallback = this.buildForwardValidConfigCallback(callback);

        if (this.limitsConfigurationSource.isUrl()) {
            this.loadFromURL(this.configEndpoint, forwardValidConfigCallback);
        } else if (this.limitsConfigurationSource.isFilePath()) {
            this.loadFromFile(this.limitsConfigurationSource.parsedConfigEndpoint.path, forwardValidConfigCallback);
        } else {
            throw new Error("Illegal url: " + this.configEndpoint);
        }
    },

    buildForwardValidConfigCallback: function (callback) {
        var _this = this;
        return function (config) {
            if (isValidConfig(config)) {
                callback(config);
            } else {
                logger.error("Could not load config from " + _this.configEndpoint + ". Using the existing limits configuration...");
                callback(null);
            }
        };
    },

    loadFromURL: function (url, forwardValidConfigCallback) {
        request({
            url: url,
            json: true
        }, function (error, response, body) {
            if (!error && response.statusCode === 200) {
                forwardValidConfigCallback(body);
            } else {
                logger.debug("Could not load config from url. " + error + "; url: " + url);
                forwardValidConfigCallback(null);
            }
        });
    },

    loadFromFile: function (path, forwardValidConfigCallback) {
        fs.readFile(path, {encoding: 'utf8'}, function (err, data) {
            if (err) {
                logger.error("Could not load config from file. " + err + "; file: " + path);
            } else {
                forwardValidConfigCallback(JSON.parse(data));
            }
        });
    }
};

function LimitsConfigurationSource(configEndpoint) {
    this.parsedConfigEndpoint = url.parse(configEndpoint);
}

LimitsConfigurationSource.prototype = {
    isUrl: function () {
        return this.parsedConfigEndpoint.protocol === "https:" || this.parsedConfigEndpoint.protocol === "http:";
    },

    isFilePath: function () {
        return this.parsedConfigEndpoint.protocol === "file:";
    }
};

function isValidConfig(config) {
    var result = limitsConfigSchema.validate(config);
    if (result.errors.length > 0) {
        logger.error("Invalid config: " + result.errors);
    }
    return result.valid;
}
<commit_msg>Revert "Quick fix to see if  swallowed error log"<commit_after>"use strict";

var fs = require('fs'),
    request = require("request"),
    url = require("url"),
    limitsConfigSchema = require('./limits-config-schema'),
    Bucket = require("./bucket").Bucket,
    log4js = require('log4js');

exports.isValidConfig = isValidConfig;
exports.LimitsConfiguration = LimitsConfiguration;
exports.LimitsConfigurationLoader = LimitsConfigurationLoader;

exports.defaultLimitsConfig = {
    version: 1,
    max_requests: 0,  // 0 means unlimited
    buffer_ratio: 0,
    buckets: [{name: "default"}]
};

if (!isValidConfig(exports.defaultLimitsConfig)) {
    throw new Error("Invalid defaultLimitsConfig");
}

var logger = log4js.getLogger();

function LimitsConfiguration(cfg) {
    this.buckets = [];
    this.bufferRatio = cfg.buffer_ratio;

    this.maxRequests = cfg.max_requests;
    this.maxRequestsWithoutBuffer = Math.floor(this.maxRequests * (1 - this.bufferRatio));
    this.healthcheckUrl = cfg.healthcheck_url;

    this.buckets = this.initializeBuckets(cfg.buckets);

    var sumOfCapacityUnits = this.calculateTotalCapacityUnits();
    this.buckets.forEach(function (bucket) {
        if (sumOfCapacityUnits === 0) {
            bucket.maxRequests = 0;
        } else {
            var bucketCapacityRatio = bucket.capacityUnit / sumOfCapacityUnits;
            bucket.maxRequests = Math.ceil(this.maxRequestsWithoutBuffer * bucketCapacityRatio);
        }
    }, this);
}

LimitsConfiguration.prototype = {
    initializeBuckets: function (buckets) {
        var defaultBucket = null;
        var initializedBuckets = [];

        buckets.forEach(function (bucketConfig) {
            var bucket = new Bucket(bucketConfig);

            if (bucket.isDefault()) {
                if (defaultBucket !== null) {
                    throw new Error("There is more than one default buckets defined in the limits configuration.");
                }
                defaultBucket = bucket;  // we will push it separately to be at the end of the array
            } else {
                initializedBuckets.push(bucket);
            }
        });

        if (defaultBucket === null) {
            throw new Error("No default bucket was set.");
        }
        initializedBuckets.push(defaultBucket);

        return initializedBuckets;
    },

    calculateTotalCapacityUnits: function () {
        var capacityUnits = 0;
        this.buckets.forEach(function (bucket) {
            capacityUnits += bucket.capacityUnit;
        });
        return capacityUnits;
    }
};

function LimitsConfigurationLoader(configEndpoint) {
    this.configEndpoint = configEndpoint;
    this.limitsConfigurationSource = null;

}

LimitsConfigurationLoader.prototype = {
    load: function (callback) {
        this.limitsConfigurationSource = new LimitsConfigurationSource(this.configEndpoint);
        var forwardValidConfigCallback = this.buildForwardValidConfigCallback(callback);

        if (this.limitsConfigurationSource.isUrl()) {
            this.loadFromURL(this.configEndpoint, forwardValidConfigCallback);
        } else if (this.limitsConfigurationSource.isFilePath()) {
            this.loadFromFile(this.limitsConfigurationSource.parsedConfigEndpoint.path, forwardValidConfigCallback);
        } else {
            throw new Error("Illegal url: " + this.configEndpoint);
        }
    },

    buildForwardValidConfigCallback: function (callback) {
        var _this = this;
        return function (config) {
            if (isValidConfig(config)) {
                callback(config);
            } else {
                logger.error("Could not load config from " + _this.configEndpoint + ". Using the existing limits configuration...");
                callback(null);
            }
        };
    },

    loadFromURL: function (url, forwardValidConfigCallback) {
        request({
            url: url,
            json: true
        }, function (error, response, body) {
            if (!error && response.statusCode === 200) {
                forwardValidConfigCallback(body);
            } else {
                logger.error("Could not load config from url. " + error + "; url: " + url);
                forwardValidConfigCallback(null);
            }
        });
    },

    loadFromFile: function (path, forwardValidConfigCallback) {
        fs.readFile(path, {encoding: 'utf8'}, function (err, data) {
            if (err) {
                logger.error("Could not load config from file. " + err + "; file: " + path);
            } else {
                forwardValidConfigCallback(JSON.parse(data));
            }
        });
    }
};

function LimitsConfigurationSource(configEndpoint) {
    this.parsedConfigEndpoint = url.parse(configEndpoint);
}

LimitsConfigurationSource.prototype = {
    isUrl: function () {
        return this.parsedConfigEndpoint.protocol === "https:" || this.parsedConfigEndpoint.protocol === "http:";
    },

    isFilePath: function () {
        return this.parsedConfigEndpoint.protocol === "file:";
    }
};

function isValidConfig(config) {
    var result = limitsConfigSchema.validate(config);
    if (result.errors.length > 0) {
        logger.error("Invalid config: " + result.errors);
    }
    return result.valid;
}
<|endoftext|>