<commit_before>angular.module('shopper.home', [
    'ui.router'
])
.config(function config($stateProvider ) {
    $stateProvider.state('home', {
        url: '/home',
        views: {
            'main': {
                controller: 'HomeCtrl',
                templateUrl: 'home/home.tpl.html'
            }
        }
    });
})
.controller('HomeCtrl', function($scope, $rootScope, $timeout, $translate, $window, HomeService) {
    $scope.$on('bodyClicked', function(scope, event) {
        var productSearchQueryKey = 'productSearchQuery';
        if (event.target.id === productSearchQueryKey)
            return;
        $scope[productSearchQueryKey] = '';
    });

    $scope.$on('updateAllProducts', function(scope, data) {
        $scope.allProducts = data;
    });

    $scope.$on('updateLists', function(scope, data) {
        $scope.lists = data;
    });

    $scope.$on('goToFirstList', function(scope) {
        if ($scope.currentListIndex > 0) {
            $scope.currentListIndex--;
        }
    });

    $scope.$on('hideListDetail', function(scope) {
        $scope.showMe = true;
    });

    $scope.$watch('currentListIndex', function(newValue) {
        if ($scope.lists[newValue]) {
            localStorage.lastListId = $scope.lists[newValue].id;
        }
    });

    $scope.refresh = function(initialLoad) {
        var allProductsSorted = [];
        var allProductsClean = JSON.parse(angular.toJson($scope.allProducts));
        for (var i = 0; i < allProductsClean.length; i++) {
            var arrayOfSortedKeyValues = [];
            var keys = Object.keys(allProductsClean[i]).sort();
            for (var j = 0; j < keys.length; j++) {
                arrayOfSortedKeyValues.push(allProductsClean[i][keys[j]]);
            }
            allProductsSorted.push(arrayOfSortedKeyValues);
        }
        var allProductsSortedJsonString = JSON.stringify(allProductsSorted);
        HomeService.getAllProducts(SparkMD5.hash(allProductsSortedJsonString));

        var listsClean = JSON.parse(angular.toJson($scope.lists));
        var listsStringified = JSON.stringify(listsClean);
        HomeService.getListsWithProductsOfUser(SparkMD5.hash(listsStringified)).then(function() {
            if (initialLoad) {
                $timeout(function() {
                    if (localStorage.lastListId) {
                        for (var i = 0; i < $scope.lists.length; i++) {
                            if ($scope.lists[i].id.toString() === localStorage.lastListId) {
                                $scope.currentListIndex = i;
                                break;
                            }
                        }
                    }
                });
            }
        });

        $timeout(function() {
            $scope.refresh();
        }, 1500);
    };

    $scope.addCustomProductToList = function(name) {
        if (name.trim() === '')
            return;

        HomeService.addCustomProductToList($scope.capitalizeString(name), $scope.lists[$scope.currentListIndex]).success(function(data) {
            $scope.refresh();
        });

        $scope.productSearchQuery = '';
    };

    $scope.isProductInList = function(product) {
        if (getCurrentList() === undefined) {
            return false;
        }

        for (var i = 0; i < getCurrentList().products.length; i++) {
            if (product.id === getCurrentList().products[i].id) {
                return true;
            }
        }
        return false;
    };

    $scope.addProductToList = function(product) {
        if ($scope.isProductInList(product))
            return;

        HomeService.addProductToList(product, getCurrentList()).then(function() {
            $scope.refresh();
        });

        $scope.productSearchQuery = '';
    };

    $scope.removeProductFromList = function(product) {
        HomeService.removeProductFromList(product, getCurrentList());
        getCurrentList().products[getCurrentList().products.indexOf(product)].disabled = true;
    };

    $scope.addList = function() {
        $translate('WHAT_SHOULD_WE_CALL_IT').then(function(translation) {
            var name = prompt(translation);
            if (! name)
                return;
            HomeService.addList(name).success(function(data) {
                HomeService.getListsWithProductsOfUser().success(function() {
                    $timeout(function() {
                        $scope.currentListIndex = $scope.lists.length - 1;
                    });
                });
            });
        });

    };

    $scope.editList = function(index) {
        $scope.showMe = false;
        $rootScope.$broadcast('showListDetail', $scope.lists[index]);
    };

    $scope.editComment = function (product, list) {
        $translate('CHANGE_COMMENT').then(function(translation) {
            var oldComment = (typeof product.comment === 'string') ? product.comment : '';
            var newComment = $window.prompt(translation, oldComment);
            if (newComment === null || newComment === oldComment)
                return;
            product.disabled = true;
            HomeService.changeComment(product, list, newComment).then(function () {
                $scope.refresh();
            });
        });
    };

    $scope.moveListOnePosition = function(direction) {
        var currentListIndex = $scope.currentListIndex;
        var otherListIndex = $scope.currentListIndex + direction;

        if (otherListIndex < 0 || otherListIndex > $scope.lists.length - 1) {
            return;
        }

        $timeout(function() {
            $scope.currentListIndex = otherListIndex;
        });
        var otherList = $scope.lists[otherListIndex];
        $scope.lists[otherListIndex] = $scope.lists[currentListIndex];
        $scope.lists[currentListIndex] = otherList;

        HomeService.moveListOnePosition($scope.lists[otherListIndex].id, direction);
    };

    $scope.capitalizeString = function(str) {
        if (typeof str === 'string') {
            return str.charAt(0).toUpperCase() + str.slice(1);
        }
        return '';
    };

    function getCurrentList() {
        return $scope.lists[$scope.currentListIndex];
    }

    $scope.showMe = true;
    $scope.lists = [];
    $scope.allProducts = [];
    $scope.currentListIndex = 0;
    $scope.refresh(true);
})
.service('HomeService', function($http, $rootScope, Api, Session) {
    this.getAllProducts = function(hash) {
        var params = Api.getParams();
        params.cmd = 'get_all_products';
        params.user_id = Session.user.id;
        params.hash = hash;

        $http.get(Api.url, { params: params }).success(function(data) {
            if (data !== 'null')
                $rootScope.$broadcast('updateAllProducts', data);
        });
    };

    this.getListsWithProductsOfUser = function(hash) {
        var params = Api.getParams();
        params.cmd = 'get_lists_with_products_of_user';
        params.user_id = Session.user.id;
        params.hash = hash;

        return $http.get(Api.url, { params: params }).success(function(data) {
            if (data !== 'null')
                $rootScope.$broadcast('updateLists', data);
        });
    };

    this.addProductToList = function(product, list) {
        var params = Api.getParams();
        params.cmd = 'add_product_to_list';
        params.product_id = product.id;
        params.list_id = list.id;
        params.comment = product.comment;
        return $http.get(Api.url, { params: params });
    };

    this.addCustomProductToList = function(name, list) {
        var params = Api.getParams();
        params.cmd = 'add_custom_product_to_list';
        params.name = name;
        params.list_id = list.id;
        params.user_id = Session.user.id;
        return $http.get(Api.url, { params: params });
    };

    this.removeProductFromList = function(product, list) {
        var params = Api.getParams();
        params.cmd = 'remove_product_from_list';
        params.product_id = product.id;
        params.list_id = list.id;
        return $http.get(Api.url, { params: params });
    };

    this.addList = function(name) {
        var params = Api.getParams();
        params.cmd = 'add_list';
        params.name = name;
        params.user_id = Session.user.id;
        return $http.get(Api.url, { params: params });
    };

    this.moveListOnePosition = function(list_id, direction) {
        var params = Api.getParams();
        params.cmd = 'move_list_one_position';
        params.list_id = list_id;
        params.direction = direction;
        params.user_id = Session.user.id;
        return $http.get(Api.url, { params: params });
    };

    this.changeComment = function (product, list, comment) {
        var params = Api.getParams();
        params.cmd = 'change_comment';
        params.product_id = product.id;
        params.list_id = list.id;
        params.comment = comment;
        params.user_id = Session.user.id;
        return $http.get(Api.url, { params: params });
    };
})
.filter('productsFilter', function () {
    function fetchComment(query, pattern) {
        if (query.indexOf(' ' + pattern) !== -1)
            pattern = ' ' + pattern;
        return query.replace(pattern, '');
    }

    return function(items, query) {
        if (query === undefined || query === '')
            return items;

        var i, j, filteredItems = [];
        query = query.toLowerCase();

        for (i = 0; i < items.length; i++) {
            var itemName = items[i].name.toLowerCase();

            if (query.indexOf(itemName) !== -1) {
                items[i].comment = fetchComment(query, itemName);
                filteredItems.push(items[i]);
                continue;
            }

            var queryTokens = query.split(' ');
            for (j = 1; j <= queryTokens.length; j++) {
                var pattern = queryTokens.slice(-j).join(' ');
                if (itemName.startsWith(pattern)) {
                    items[i].comment = fetchComment(query, pattern);
                    filteredItems.push(items[i]);
                    break;
                }
            }
        }

        return filteredItems;
    };
})
;
<commit_msg>Bugfix: On desktops after exiting the prompt ‘comment change’ window, the mouse has a carousel move event going on<commit_after>angular.module('shopper.home', [
    'ui.router'
])
.config(function config($stateProvider ) {
    $stateProvider.state('home', {
        url: '/home',
        views: {
            'main': {
                controller: 'HomeCtrl',
                templateUrl: 'home/home.tpl.html'
            }
        }
    });
})
.controller('HomeCtrl', function($scope, $rootScope, $timeout, $translate, $window, HomeService) {
    $scope.$on('bodyClicked', function(scope, event) {
        var productSearchQueryKey = 'productSearchQuery';
        if (event.target.id === productSearchQueryKey)
            return;
        $scope[productSearchQueryKey] = '';
    });

    $scope.$on('updateAllProducts', function(scope, data) {
        $scope.allProducts = data;
    });

    $scope.$on('updateLists', function(scope, data) {
        $scope.lists = data;
    });

    $scope.$on('goToFirstList', function(scope) {
        if ($scope.currentListIndex > 0) {
            $scope.currentListIndex--;
        }
    });

    $scope.$on('hideListDetail', function(scope) {
        $scope.showMe = true;
    });

    $scope.$watch('currentListIndex', function(newValue) {
        if ($scope.lists[newValue]) {
            localStorage.lastListId = $scope.lists[newValue].id;
        }
    });

    $scope.refresh = function(initialLoad) {
        var allProductsSorted = [];
        var allProductsClean = JSON.parse(angular.toJson($scope.allProducts));
        for (var i = 0; i < allProductsClean.length; i++) {
            var arrayOfSortedKeyValues = [];
            var keys = Object.keys(allProductsClean[i]).sort();
            for (var j = 0; j < keys.length; j++) {
                arrayOfSortedKeyValues.push(allProductsClean[i][keys[j]]);
            }
            allProductsSorted.push(arrayOfSortedKeyValues);
        }
        var allProductsSortedJsonString = JSON.stringify(allProductsSorted);
        HomeService.getAllProducts(SparkMD5.hash(allProductsSortedJsonString));

        var listsClean = JSON.parse(angular.toJson($scope.lists));
        var listsStringified = JSON.stringify(listsClean);
        HomeService.getListsWithProductsOfUser(SparkMD5.hash(listsStringified)).then(function() {
            if (initialLoad) {
                $timeout(function() {
                    if (localStorage.lastListId) {
                        for (var i = 0; i < $scope.lists.length; i++) {
                            if ($scope.lists[i].id.toString() === localStorage.lastListId) {
                                $scope.currentListIndex = i;
                                break;
                            }
                        }
                    }
                });
            }
        });

        $timeout(function() {
            $scope.refresh();
        }, 1500);
    };

    $scope.addCustomProductToList = function(name) {
        if (name.trim() === '')
            return;

        HomeService.addCustomProductToList($scope.capitalizeString(name), $scope.lists[$scope.currentListIndex]).success(function(data) {
            $scope.refresh();
        });

        $scope.productSearchQuery = '';
    };

    $scope.isProductInList = function(product) {
        if (getCurrentList() === undefined) {
            return false;
        }

        for (var i = 0; i < getCurrentList().products.length; i++) {
            if (product.id === getCurrentList().products[i].id) {
                return true;
            }
        }
        return false;
    };

    $scope.addProductToList = function(product) {
        if ($scope.isProductInList(product))
            return;

        HomeService.addProductToList(product, getCurrentList()).then(function() {
            $scope.refresh();
        });

        $scope.productSearchQuery = '';
    };

    $scope.removeProductFromList = function(product) {
        HomeService.removeProductFromList(product, getCurrentList());
        getCurrentList().products[getCurrentList().products.indexOf(product)].disabled = true;
    };

    $scope.addList = function() {
        $translate('WHAT_SHOULD_WE_CALL_IT').then(function(translation) {
            var name = prompt(translation);
            if (! name)
                return;
            HomeService.addList(name).success(function(data) {
                HomeService.getListsWithProductsOfUser().success(function() {
                    $timeout(function() {
                        $scope.currentListIndex = $scope.lists.length - 1;
                    });
                });
            });
        });

    };

    $scope.editList = function(index) {
        $scope.showMe = false;
        $rootScope.$broadcast('showListDetail', $scope.lists[index]);
    };

    $scope.editComment = function (product, list) {
        $translate('CHANGE_COMMENT').then(function(translation) {
            $timeout(function() {
                var oldComment = (typeof product.comment === 'string') ? product.comment : '';
                var newComment = $window.prompt(translation, oldComment);
                if (newComment === null || newComment === oldComment)
                    return;
                product.disabled = true;
                HomeService.changeComment(product, list, newComment).then(function () {
                    $scope.refresh();
                });
            }, 0);
        });
    };

    $scope.moveListOnePosition = function(direction) {
        var currentListIndex = $scope.currentListIndex;
        var otherListIndex = $scope.currentListIndex + direction;

        if (otherListIndex < 0 || otherListIndex > $scope.lists.length - 1) {
            return;
        }

        $timeout(function() {
            $scope.currentListIndex = otherListIndex;
        });
        var otherList = $scope.lists[otherListIndex];
        $scope.lists[otherListIndex] = $scope.lists[currentListIndex];
        $scope.lists[currentListIndex] = otherList;

        HomeService.moveListOnePosition($scope.lists[otherListIndex].id, direction);
    };

    $scope.capitalizeString = function(str) {
        if (typeof str === 'string') {
            return str.charAt(0).toUpperCase() + str.slice(1);
        }
        return '';
    };

    function getCurrentList() {
        return $scope.lists[$scope.currentListIndex];
    }

    $scope.showMe = true;
    $scope.lists = [];
    $scope.allProducts = [];
    $scope.currentListIndex = 0;
    $scope.refresh(true);
})
.service('HomeService', function($http, $rootScope, Api, Session) {
    this.getAllProducts = function(hash) {
        var params = Api.getParams();
        params.cmd = 'get_all_products';
        params.user_id = Session.user.id;
        params.hash = hash;

        $http.get(Api.url, { params: params }).success(function(data) {
            if (data !== 'null')
                $rootScope.$broadcast('updateAllProducts', data);
        });
    };

    this.getListsWithProductsOfUser = function(hash) {
        var params = Api.getParams();
        params.cmd = 'get_lists_with_products_of_user';
        params.user_id = Session.user.id;
        params.hash = hash;

        return $http.get(Api.url, { params: params }).success(function(data) {
            if (data !== 'null')
                $rootScope.$broadcast('updateLists', data);
        });
    };

    this.addProductToList = function(product, list) {
        var params = Api.getParams();
        params.cmd = 'add_product_to_list';
        params.product_id = product.id;
        params.list_id = list.id;
        params.comment = product.comment;
        return $http.get(Api.url, { params: params });
    };

    this.addCustomProductToList = function(name, list) {
        var params = Api.getParams();
        params.cmd = 'add_custom_product_to_list';
        params.name = name;
        params.list_id = list.id;
        params.user_id = Session.user.id;
        return $http.get(Api.url, { params: params });
    };

    this.removeProductFromList = function(product, list) {
        var params = Api.getParams();
        params.cmd = 'remove_product_from_list';
        params.product_id = product.id;
        params.list_id = list.id;
        return $http.get(Api.url, { params: params });
    };

    this.addList = function(name) {
        var params = Api.getParams();
        params.cmd = 'add_list';
        params.name = name;
        params.user_id = Session.user.id;
        return $http.get(Api.url, { params: params });
    };

    this.moveListOnePosition = function(list_id, direction) {
        var params = Api.getParams();
        params.cmd = 'move_list_one_position';
        params.list_id = list_id;
        params.direction = direction;
        params.user_id = Session.user.id;
        return $http.get(Api.url, { params: params });
    };

    this.changeComment = function (product, list, comment) {
        var params = Api.getParams();
        params.cmd = 'change_comment';
        params.product_id = product.id;
        params.list_id = list.id;
        params.comment = comment;
        params.user_id = Session.user.id;
        return $http.get(Api.url, { params: params });
    };
})
.filter('productsFilter', function () {
    function fetchComment(query, pattern) {
        if (query.indexOf(' ' + pattern) !== -1)
            pattern = ' ' + pattern;
        return query.replace(pattern, '');
    }

    return function(items, query) {
        if (query === undefined || query === '')
            return items;

        var i, j, filteredItems = [];
        query = query.toLowerCase();

        for (i = 0; i < items.length; i++) {
            var itemName = items[i].name.toLowerCase();

            if (query.indexOf(itemName) !== -1) {
                items[i].comment = fetchComment(query, itemName);
                filteredItems.push(items[i]);
                continue;
            }

            var queryTokens = query.split(' ');
            for (j = 1; j <= queryTokens.length; j++) {
                var pattern = queryTokens.slice(-j).join(' ');
                if (itemName.startsWith(pattern)) {
                    items[i].comment = fetchComment(query, pattern);
                    filteredItems.push(items[i]);
                    break;
                }
            }
        }

        return filteredItems;
    };
})
;
<|endoftext|>


<commit_before>/**
 * Each section of the site has its own module. It probably also has
 * submodules, though this boilerplate is too simple to demonstrate it. Within
 * `src/app/home`, however, could exist several additional folders representing
 * additional modules that would then be listed as dependencies of this one.
 * For example, a `note` section could have the submodules `note.create`,
 * `note.delete`, `note.edit`, etc.
 *
 * Regardless, so long as dependencies are managed correctly, the build process
 * will automatically take take of the rest.
 *
 * The dependencies block here is also where component dependencies should be
 * specified, as shown below.
 */

angular.module( 'App.list', [
  'ui.router',
  'ngProgress',
  'ngAnimate',
  'ui.bootstrap',
  'ui.bootstrap.tpls',
  'flow'
])

/**
 * Filters
 */
.filter('classicDate', function() {
  return function(date) {
    date = (date<10000000000)?date*1000:date;
    return moment(date).format('YYYY-MM-DD, h:mm:ss a');
  };
})

.filter('fromNow', function() {
  return function(date) {
    date = (date<10000000000)?date*1000:date;
    return moment(date).fromNow();
  };
})

.filter('fileSize', function() {
  return function(bytes) {
    return humanFileSize(bytes);
  };
})

/**
 * Each section or module of the site can also have its own routes. AngularJS
 * will handle ensuring they are all available at run-time, but splitting it
 * this way makes each module more "self-contained".
 */
.config(function ViewConfig( $stateProvider ) {
  $stateProvider.state( 'list', {
    url: '/list/{path:.*}',
    views: {
      "main": {
        controller: 'ListCtrl',
        templateUrl: 'list/list.tpl.html'
      }
    },
    data:{ pageTitle: 'Listing resources' }
  });
})

.config(['flowFactoryProvider', function (flowFactoryProvider) {
  flowFactoryProvider.defaults = {
    permanentErrors: [404, 500, 501],
    maxChunkRetries: 1,
    chunkRetryInterval: 5000,
    simultaneousUploads: 4
  };
  flowFactoryProvider.on('catchAll', function (event) {
    console.log('catchAll', arguments);
  });
  // Can be used with different implementations of Flow.js
  // flowFactoryProvider.factory = fustyFlowFactory;
}])

/**
 * And of course we define a controller for our route.
 */
.controller( 'ListCtrl', function ListController( $scope, $http, $location, $modal, $sce, $stateParams, ngProgress ) {
  $scope.hideMenu = function() {
    $scope.$parent.showMenu = false;
  };

  // variables
  $scope.schema = '';
  $scope.resources = [];
  $scope.listLocation = true;
  $scope.breadCrumbs = [];

  // var storage = $scope.$parent.userProfile.storagespace;
  // $scope.schema = (storage !== undefined)?storage.slice(0, storage.indexOf('://')):$location.$$protocol;
  // $scope.path = $stateParams.path;

  $scope.prepareList = function(url) {
    if (url && url.length > 0) {
      $scope.listLocation = true;
      $location.path('/list/'+stripSchema(url));
    } else {
      $scope.listLocation = false;
      notify('Warning', 'Please provide a URL');
    }
  };

  // TODO: rdflib fetch does not respond properly to 404
  $scope.listDir = function (url) {
    console.log("Requested path: "+url); // debug
    var elms = url.split("/");
    var schema = '';
    var path = '';
    if (elms[0].substring(0, 4) == 'http') {
      schema = elms[0];
      elms.splice(0,1);
    } else {
      schema = 'https';
    }
    $scope.path = schema+'://';
    console.log("So far: "+$scope.path);

    for (i=0; i<elms.length; i++) {
      if (elms[i].length > 0) {
        path = (i===0)?elms[0]+'/':path+elms[i]+'/';
        var dir = {
          uri: '#/list/'+schema+'/'+path,
          name: elms[i]
        };

        $scope.breadCrumbs.push(dir);
      }
    }
    $scope.path += path;

    // start progress bar
    ngProgress.reset();
    ngProgress.start();

    var RDF = $rdf.Namespace("http://www.w3.org/1999/02/22-rdf-syntax-ns#");
    var RDFS = $rdf.Namespace("http://www.w3.org/2000/01/rdf-schema#");
    var LDP = $rdf.Namespace("http://www.w3.org/ns/ldp#");
    var POSIX = $rdf.Namespace("http://www.w3.org/ns/posix/stat#");

    var g = $rdf.graph();
    var f = $rdf.fetcher(g, TIMEOUT);
    // add CORS proxy
    $rdf.Fetcher.crossSiteProxyTemplate=PROXY;

    // fetch user data
    f.nowOrWhenFetched($scope.path,undefined,function(ok, body) {
      if (!ok) {
        notify('Error', 'Could not fetch dir listing.');
        ngProgress.complete();
        $scope.listLocation = false;

        console.log(ok);
        console.log(body);
      } else {
        $scope.listLocation = true;
      }
      $scope.$apply();

      var dirs = g.statementsMatching(undefined, RDF("type"), POSIX("Directory"));
      for ( var i in dirs ) {
        if ( dirs[i].subject.uri.split('://')[1].split('/').length <= 2 ) {
          continue;
        }
        var d = {};
        if ( dirs[i].subject.uri == $scope.path ) {
          d = {
            uri: dirs[i].subject.uri,
            path: dirname(document.location.href)+'/',
            type: '-',
            name: '../',
            mtime: g.any(dirs[i].subject, POSIX("mtime")).value,
            size: '-'
          };
        } else {
          d = {
            uri: dirs[i].subject.uri,
            path: document.location.href+basename(dirs[i].subject.uri)+'/',
            type: 'Directory',
            name: basename(dirs[i].subject.value),
            mtime: g.any(dirs[i].subject, POSIX("mtime")).value,
            size: '-'
          };
        }
        $scope.resources.push(d);
      }
      var files = g.statementsMatching(undefined, RDF("type"), RDFS("Resource"));
      for (i in files) {
        var f = {
          uri: files[i].subject.uri,
          path: '#/view/'+stripSchema(files[i].subject.uri),
          type: 'File', // TODO: use the real type
          name: basename(files[i].subject.value),
          mtime: g.any(files[i].subject, POSIX("mtime")).value,
          size: g.any(files[i].subject, POSIX("size")).value
        };
        $scope.resources.push(f);
        $scope.$apply();
      }
      console.log($scope.resources);
      ngProgress.complete();
      $scope.$apply();
    });
  };

  $scope.upload = function () {
    console.log("Uploading files");
    console.log($flow.files);
  };

  $scope.newDir = function(dirName) {
    $http({
      method: 'POST',
      url: $scope.path,
      data: '',
      headers: {
        'Content-Type': 'text/turtle',
        'Link': '<http://www.w3.org/ns/ldp#BasicContainer>; rel="type"',
        'Slug': dirName
      },
      withCredentials: true
    }).
    success(function(data, status) {
      if (status == 200 || status == 201) {
        notify('Success', 'Directory created.');
        // add dir to local list
        var now = new Date().getTime();
        var d = {
            uri: $scope.path+dirName,
            path: document.location.href+basename($scope.path+dirName)+'/',
            type: 'Directory',
            name: dirName,
            mtime: now,
            size: '-'
          };
        console.log(d);
        $scope.resources.push(d);
      }
    }).
    error(function(data, status) {
      if (status == 401) {
        notify('Forbidden', 'Authentication required to create new directory.');
      } else if (status == 403) {
        notify('Forbidden', 'Insufficient permissions to create new directory.');
      } else {
        notify('Failed'+status, data);
      }
    });
  };

  $scope.newFile = function(fileName) {
    $http({
      method: 'PUT',
      url: $scope.path+fileName,
      data: '',
      headers: {
        'Content-Type': 'text/turtle',
        'Link': '<http://www.w3.org/ns/ldp#Resource>; rel="type"'
      },
      withCredentials: true
    }).
    success(function(data, status, headers) {
      if (status == 200 || status == 201) {
        notify('Success', 'Resource created.');
        // Add resource to the list
        var res = headers('Location');
        var f = {
          uri: res,
          path: '#/view/'+stripSchema(res),
          type: 'File', // TODO: use the real type
          name: basename(res),
          mtime: moment().fromNow(),
          size: '-'
        };
        // TODO Refresh the view
        $scope.resources.push(f);
        // Add

      }
    }).
    error(function(data, status) {
      if (status == 401) {
        notify('Forbidden', 'Authentication required to create new resource.');
      } else if (status == 403) {
        notify('Forbidden', 'Insufficient permissions to create new resource.');
      } else {
        notify('Failed'+status, data);
      }
    });
  };

  $scope.refreshResource = function(uri) {
    // TODO
  };

  $scope.deleteResource = function(resourceUri) {
    $http({
      method: 'DELETE',
      url: resourceUri,
      withCredentials: true
    }).
    success(function(data, status) {
      if (status == 200 || status == 201) {
        notify('Success', basename(resourceUri)+' was deleted.');
        $scope.removeResource(resourceUri);
      }
    }).
    error(function(data, status) {
      if (status == 401) {
        notify('Forbidden', 'Authentication required to delete resource.');
      } else if (status == 403) {
        notify('Forbidden', 'Insufficient permissions to delete resource.');
      } else if (status == 409) {
        notify('Failed', 'Conflict detected. In case of directory, check if not empty.');
      } else {
        notify('Failed'+status, data);
      }
    });
  };

  $scope.removeResource = function(uri) {
    if ($scope.resources) {
      for(var i = $scope.resources.length - 1; i >= 0; i--){
        if($scope.resources[i].uri == uri){
            $scope.resources.splice(i,1);
        }
      }
    }
  };

  // New dir dialog
  $scope.openNewDir = function () {
    var modalInstance = $modal.open({
      templateUrl: 'newdir.html',
      controller: ModalNewDirCtrl,
      size: 'sm'
    });
    modalInstance.result.then($scope.newDir);
  };
  // New file creation dialog
  $scope.openNewFile = function () {
    var modalInstance = $modal.open({
      templateUrl: 'newfile.html',
      controller: ModalNewFileCtrl,
      size: 'sm'
    });
    modalInstance.result.then($scope.newFile);
  };
  // Remove resource dialog
  $scope.openDelete = function (uri) {
    console.log("Calling openDelete for "+uri);
    var modalInstance = $modal.open({
      templateUrl: 'delete.html',
      controller: ModalDeleteCtrl,
      size: 'sm',
      resolve: {
        delUri: function () {
          return uri;
        }
      }
    });
    modalInstance.result.then($scope.deleteResource);
  };
  // New file upload dialog
  $scope.openNewUpload = function () {
    var modalInstance = $modal.open({
      templateUrl: 'uploadfiles.html',
      controller: ModalUploadCtrl,
      size: 'sm'
    });
  };

  // Display list for current path
  if ($stateParams.path.length > 0) {
    $scope.listDir($stateParams.path);
  } else {
    $scope.listLocation = false;
  }
 });

// Modal Ctrls
var ModalNewDirCtrl = function ($scope, $modalInstance) {
  $scope.newDir = function(dirName) {
    $modalInstance.close(dirName);
  };

  $scope.ok = function () {
    $modalInstance.close();
  };

  $scope.cancel = function () {
    $modalInstance.dismiss('cancel');
  };
};

var ModalNewFileCtrl = function ($scope, $modalInstance) {
  // TODO
  // $scope.expr = "/^[A-Za-z0-9_-(\.)]*$/";

  $scope.newFile = function(fileName) {
    $modalInstance.close(fileName);
  };

  $scope.ok = function () {
    $modalInstance.close();
  };

  $scope.cancel = function () {
    $modalInstance.dismiss('cancel');
  };
};

var ModalDeleteCtrl = function ($scope, $modalInstance, delUri) {
  $scope.delUri = delUri;

  $scope.deleteResource = function() {
    $modalInstance.close($scope.delUri);
  };

  $scope.ok = function () {
    $modalInstance.close();
  };

  $scope.cancel = function () {
    $modalInstance.dismiss('cancel');
  };

};<commit_msg>Fixed trailing slash bug.<commit_after>/**
 * Each section of the site has its own module. It probably also has
 * submodules, though this boilerplate is too simple to demonstrate it. Within
 * `src/app/home`, however, could exist several additional folders representing
 * additional modules that would then be listed as dependencies of this one.
 * For example, a `note` section could have the submodules `note.create`,
 * `note.delete`, `note.edit`, etc.
 *
 * Regardless, so long as dependencies are managed correctly, the build process
 * will automatically take take of the rest.
 *
 * The dependencies block here is also where component dependencies should be
 * specified, as shown below.
 */

angular.module( 'App.list', [
  'ui.router',
  'ngProgress',
  'ngAnimate',
  'ui.bootstrap',
  'ui.bootstrap.tpls',
  'flow'
])

/**
 * Filters
 */
.filter('classicDate', function() {
  return function(date) {
    date = (date<10000000000)?date*1000:date;
    return moment(date).format('YYYY-MM-DD, h:mm:ss a');
  };
})

.filter('fromNow', function() {
  return function(date) {
    date = (date<10000000000)?date*1000:date;
    return moment(date).fromNow();
  };
})

.filter('fileSize', function() {
  return function(bytes) {
    return humanFileSize(bytes);
  };
})

/**
 * Each section or module of the site can also have its own routes. AngularJS
 * will handle ensuring they are all available at run-time, but splitting it
 * this way makes each module more "self-contained".
 */
.config(function ViewConfig( $stateProvider ) {
  $stateProvider.state( 'list', {
    url: '/list/{path:.*}',
    views: {
      "main": {
        controller: 'ListCtrl',
        templateUrl: 'list/list.tpl.html'
      }
    },
    data:{ pageTitle: 'Listing resources' }
  });
})

.config(['flowFactoryProvider', function (flowFactoryProvider) {
  flowFactoryProvider.defaults = {
    permanentErrors: [404, 500, 501],
    maxChunkRetries: 1,
    chunkRetryInterval: 5000,
    simultaneousUploads: 4
  };
  flowFactoryProvider.on('catchAll', function (event) {
    console.log('catchAll', arguments);
  });
  // Can be used with different implementations of Flow.js
  // flowFactoryProvider.factory = fustyFlowFactory;
}])

/**
 * And of course we define a controller for our route.
 */
.controller( 'ListCtrl', function ListController( $scope, $http, $location, $modal, $sce, $stateParams, ngProgress ) {
  $scope.hideMenu = function() {
    $scope.$parent.showMenu = false;
  };

  // variables
  $scope.schema = '';
  $scope.resources = [];
  $scope.listLocation = true;
  $scope.breadCrumbs = [];

  // var storage = $scope.$parent.userProfile.storagespace;
  // $scope.schema = (storage !== undefined)?storage.slice(0, storage.indexOf('://')):$location.$$protocol;
  // $scope.path = $stateParams.path;

  $scope.prepareList = function(url) {
    if (url && url.length > 0) {
      $scope.listLocation = true;
      $location.path('/list/'+stripSchema(url));
    } else {
      $scope.listLocation = false;
      notify('Warning', 'Please provide a URL');
    }
  };

  // TODO: rdflib fetch does not respond properly to 404
  $scope.listDir = function (url) {
    console.log("Requested path: "+url); // debug
    var elms = url.split("/");
    var schema = '';
    var path = '';
    if (elms[0].substring(0, 4) == 'http') {
      schema = elms[0];
      elms.splice(0,1);
    } else {
      schema = 'https';
    }
    $scope.path = schema+'://';
    console.log("So far: "+$scope.path);

    for (i=0; i<elms.length; i++) {
      if (elms[i].length > 0) {
        path = (i===0)?elms[0]+'/':path+elms[i]+'/';
        var dir = {
          uri: '#/list/'+schema+'/'+path,
          name: elms[i]
        };

        $scope.breadCrumbs.push(dir);
      }
    }
    $scope.path += path;

    // start progress bar
    ngProgress.reset();
    ngProgress.start();

    var RDF = $rdf.Namespace("http://www.w3.org/1999/02/22-rdf-syntax-ns#");
    var RDFS = $rdf.Namespace("http://www.w3.org/2000/01/rdf-schema#");
    var LDP = $rdf.Namespace("http://www.w3.org/ns/ldp#");
    var POSIX = $rdf.Namespace("http://www.w3.org/ns/posix/stat#");

    var g = $rdf.graph();
    var f = $rdf.fetcher(g, TIMEOUT);
    // add CORS proxy
    $rdf.Fetcher.crossSiteProxyTemplate=PROXY;

    // fetch user data
    f.nowOrWhenFetched($scope.path,undefined,function(ok, body) {
      if (!ok) {
        notify('Error', 'Could not fetch dir listing.');
        ngProgress.complete();
        $scope.listLocation = false;

        console.log(ok);
        console.log(body);
      } else {
        $scope.listLocation = true;
      }
      $scope.$apply();

      var dirs = g.statementsMatching(undefined, RDF("type"), POSIX("Directory"));
      for ( var i in dirs ) {
        if ( dirs[i].subject.uri.split('://')[1].split('/').length <= 2 ) {
          continue;
        }
        var d = {};
        if ( dirs[i].subject.uri == $scope.path ) {
          d = {
            uri: dirs[i].subject.uri,
            path: dirname(document.location.href)+'/',
            type: '-',
            name: '../',
            mtime: g.any(dirs[i].subject, POSIX("mtime")).value,
            size: '-'
          };
        } else {
          var base = (document.location.href.charAt(document.location.href.length - 1) === '/')?document.location.href:document.location.href+'/';
          d = {
            uri: dirs[i].subject.uri,
            path: base+basename(dirs[i].subject.uri)+'/',
            type: 'Directory',
            name: basename(dirs[i].subject.value),
            mtime: g.any(dirs[i].subject, POSIX("mtime")).value,
            size: '-'
          };
        }
        $scope.resources.push(d);
      }
      var files = g.statementsMatching(undefined, RDF("type"), RDFS("Resource"));
      for (i in files) {
        var f = {
          uri: files[i].subject.uri,
          path: '#/view/'+stripSchema(files[i].subject.uri),
          type: 'File', // TODO: use the real type
          name: basename(files[i].subject.value),
          mtime: g.any(files[i].subject, POSIX("mtime")).value,
          size: g.any(files[i].subject, POSIX("size")).value
        };
        $scope.resources.push(f);
        $scope.$apply();
      }
      console.log($scope.resources);
      ngProgress.complete();
      $scope.$apply();
    });
  };

  $scope.upload = function () {
    console.log("Uploading files");
    console.log($flow.files);
  };

  $scope.newDir = function(dirName) {
    $http({
      method: 'POST',
      url: $scope.path,
      data: '',
      headers: {
        'Content-Type': 'text/turtle',
        'Link': '<http://www.w3.org/ns/ldp#BasicContainer>; rel="type"',
        'Slug': dirName
      },
      withCredentials: true
    }).
    success(function(data, status) {
      if (status == 200 || status == 201) {
        notify('Success', 'Directory created.');
        // add dir to local list
        var now = new Date().getTime();
        var base = (document.location.href.charAt(document.location.href.length - 1) === '/')?document.location.href:document.location.href+'/';
        var d = {
            uri: $scope.path+dirName,
            path: base+basename($scope.path+dirName)+'/',
            type: 'Directory',
            name: dirName,
            mtime: now,
            size: '-'
          };
        console.log(d);
        $scope.resources.push(d);
      }
    }).
    error(function(data, status) {
      if (status == 401) {
        notify('Forbidden', 'Authentication required to create new directory.');
      } else if (status == 403) {
        notify('Forbidden', 'Insufficient permissions to create new directory.');
      } else {
        notify('Failed'+status, data);
      }
    });
  };

  $scope.newFile = function(fileName) {
    $http({
      method: 'PUT',
      url: $scope.path+fileName,
      data: '',
      headers: {
        'Content-Type': 'text/turtle',
        'Link': '<http://www.w3.org/ns/ldp#Resource>; rel="type"'
      },
      withCredentials: true
    }).
    success(function(data, status, headers) {
      if (status == 200 || status == 201) {
        notify('Success', 'Resource created.');
        // Add resource to the list
        var res = headers('Location');
        var f = {
          uri: res,
          path: '#/view/'+stripSchema(res),
          type: 'File', // TODO: use the real type
          name: basename(res),
          mtime: moment().fromNow(),
          size: '-'
        };
        // TODO Refresh the view
        $scope.resources.push(f);
        // Add

      }
    }).
    error(function(data, status) {
      if (status == 401) {
        notify('Forbidden', 'Authentication required to create new resource.');
      } else if (status == 403) {
        notify('Forbidden', 'Insufficient permissions to create new resource.');
      } else {
        notify('Failed'+status, data);
      }
    });
  };

  $scope.refreshResource = function(uri) {
    // TODO
  };

  $scope.deleteResource = function(resourceUri) {
    $http({
      method: 'DELETE',
      url: resourceUri,
      withCredentials: true
    }).
    success(function(data, status) {
      if (status == 200 || status == 201) {
        notify('Success', basename(resourceUri)+' was deleted.');
        $scope.removeResource(resourceUri);
      }
    }).
    error(function(data, status) {
      if (status == 401) {
        notify('Forbidden', 'Authentication required to delete resource.');
      } else if (status == 403) {
        notify('Forbidden', 'Insufficient permissions to delete resource.');
      } else if (status == 409) {
        notify('Failed', 'Conflict detected. In case of directory, check if not empty.');
      } else {
        notify('Failed'+status, data);
      }
    });
  };

  $scope.removeResource = function(uri) {
    if ($scope.resources) {
      for(var i = $scope.resources.length - 1; i >= 0; i--){
        if($scope.resources[i].uri == uri){
            $scope.resources.splice(i,1);
        }
      }
    }
  };

  // New dir dialog
  $scope.openNewDir = function () {
    var modalInstance = $modal.open({
      templateUrl: 'newdir.html',
      controller: ModalNewDirCtrl,
      size: 'sm'
    });
    modalInstance.result.then($scope.newDir);
  };
  // New file creation dialog
  $scope.openNewFile = function () {
    var modalInstance = $modal.open({
      templateUrl: 'newfile.html',
      controller: ModalNewFileCtrl,
      size: 'sm'
    });
    modalInstance.result.then($scope.newFile);
  };
  // Remove resource dialog
  $scope.openDelete = function (uri) {
    console.log("Calling openDelete for "+uri);
    var modalInstance = $modal.open({
      templateUrl: 'delete.html',
      controller: ModalDeleteCtrl,
      size: 'sm',
      resolve: {
        delUri: function () {
          return uri;
        }
      }
    });
    modalInstance.result.then($scope.deleteResource);
  };
  // New file upload dialog
  $scope.openNewUpload = function () {
    var modalInstance = $modal.open({
      templateUrl: 'uploadfiles.html',
      controller: ModalUploadCtrl,
      size: 'sm'
    });
  };

  // Display list for current path
  if ($stateParams.path.length > 0) {
    $scope.listDir($stateParams.path);
  } else {
    $scope.listLocation = false;
  }
 });

// Modal Ctrls
var ModalNewDirCtrl = function ($scope, $modalInstance) {
  $scope.newDir = function(dirName) {
    $modalInstance.close(dirName);
  };

  $scope.ok = function () {
    $modalInstance.close();
  };

  $scope.cancel = function () {
    $modalInstance.dismiss('cancel');
  };
};

var ModalNewFileCtrl = function ($scope, $modalInstance) {
  // TODO
  // $scope.expr = "/^[A-Za-z0-9_-(\.)]*$/";

  $scope.newFile = function(fileName) {
    $modalInstance.close(fileName);
  };

  $scope.ok = function () {
    $modalInstance.close();
  };

  $scope.cancel = function () {
    $modalInstance.dismiss('cancel');
  };
};

var ModalDeleteCtrl = function ($scope, $modalInstance, delUri) {
  $scope.delUri = delUri;

  $scope.deleteResource = function() {
    $modalInstance.close($scope.delUri);
  };

  $scope.ok = function () {
    $modalInstance.close();
  };

  $scope.cancel = function () {
    $modalInstance.dismiss('cancel');
  };

};<|endoftext|>


<commit_before>var path = require('path'),
    cwd = process.cwd(),
    make = require('../../api/make');

module.exports = function (options) {
    return function (req, res, next) {
        options = options || {};

        var startTime = new Date(),
            root = options.root || cwd,
            pathname = req._parsedUrl.pathname,
            filename = path.join(root, pathname),
            target = path.relative(root, filename),
            targets = target ? [target] : [];

        make(targets, {
                dir: root,
                mode: options.mode,
                cache: true,
                strict: false
            })
            .then(function () {
                var endTime = new Date();

                console.log('----- ' + path.normalize(pathname) + ' ' + (endTime - startTime) + 'ms');

                next();
            })
            .fail(function (err) {
                next(err);
            });
    };
};
<commit_msg>fix(server): provide cache option<commit_after>var path = require('path'),
    cwd = process.cwd(),
    make = require('../../api/make');

module.exports = function (options) {
    return function (req, res, next) {
        options = options || {};

        var startTime = new Date(),
            root = options.root || cwd,
            pathname = req._parsedUrl.pathname,
            filename = path.join(root, pathname),
            target = path.relative(root, filename),
            targets = target ? [target] : [];

        make(targets, {
                dir: root,
                mode: options.mode,
                cache: !options.hasOwnProperty('cache') || options.cache,
                strict: false
            })
            .then(function () {
                var endTime = new Date();

                console.log('----- ' + path.normalize(pathname) + ' ' + (endTime - startTime) + 'ms');

                next();
            })
            .fail(function (err) {
                next(err);
            });
    };
};
<|endoftext|>


<commit_before><commit_msg>Preparing AI Class<commit_after>function Ai(){

}

<|endoftext|>


<commit_before>// eslint-disable-next-line import/no-unresolved
import {assert} from 'meteor/practicalmeteor:chai';
import sinon from 'sinon';
import '../common/main.js';
import '../common/notifications.js';
import './push.api.js';
import './server.js';

const mockRequire = require('mock-require');

const APP_IDENTIFIER = 'APP_IDENTIFIER';
const FIREBASE_CREDENTIALS = 'FIREBASE_CREDENTIALS';
const FIREBASE_EMAIL = 'FIREBASE_EMAIL';
const FIREBASE_PRIVATE_KEY = 'FIREBASE_PRIVATE_KEY';

let originals = {
  GlobalVets: global.GlobalVets,
  nodeEnvs: {
    PUSH_NOTIFICATIONS_DISABLED: process.env.PUSH_NOTIFICATIONS_DISABLED,
    NODE_ENV: process.env.NODE_ENV
  }
};

if (!global.GlobalVets) {
  console.log('######## Init GlobalVets ######');
  global.GlobalVets = {};
}

const defaultVet = {
  _id: 'VET_ID',
  firebaseCredentials: {
    firebaseCredentials: FIREBASE_CREDENTIALS,
    projectId: FIREBASE_EMAIL,
    privateKey: FIREBASE_PRIVATE_KEY
  },
  appIdentifier: APP_IDENTIFIER
};

describe('Testing push API', function () {

  let messagingMock = sinon.mock({
    send: function (fcmMessage) {
      console.log(`Sending message ${JSON.stringify(fcmMessage)}`);
    }
  });

  let firebaseMock = sinon.mock({
    messaging: function () {
      return messagingMock;
    }
  });

  let firebaseAdminMocks = {
    initializeApp: function (...args) {
      console.log('Spies, open you eyes! firebaseAdmin.initializeApp', JSON.stringify(args));
      return firebaseMock;
    },
    cert: function (...args) {
      console.log('Spies, open you eyes! firebaseAdmin.credential.cert', JSON.stringify(args));
      return 'mock-cert';
    }
  };

  let firebaseAdminInitializeAppSpy;

  before(function () {
    global.GlobalVets = {
      find: () => {
        return {
          fetch: () => {
            return [defaultVet];
          }
        };
      },
      findOne: () => {
        return defaultVet;
      }
    };

    process.env.PUSH_NOTIFICATIONS_DISABLED = false;
    process.env.NODE_ENV = 'production';

    firebaseAdminInitializeAppSpy = sinon.spy(firebaseAdminMocks, 'initializeApp');

    mockRequire('firebase-admin', {
      initializeApp: firebaseAdminMocks.initializeApp,
      credential: {
        cert: firebaseAdminMocks.cert
      }
    });

    mockRequire.reRequire('firebase-admin');

    Meteor.methods({
      'checkCertificate': function (vetId) {
        check(vetId, String);

        return 365;
      }
    });

  });

  after(function () {
    global.GlobalVets = originals.GlobalVets;

    process.env.PUSH_NOTIFICATIONS_DISABLED = originals.nodeEnvs.PUSH_NOTIFICATIONS_DISABLED;
    process.env.NODE_ENV = originals.nodeEnvs.NODE_ENV;

    firebaseAdminInitializeAppSpy.restore();
    mockRequire.stop('firebase-admin');

  });

  afterEach(function () {
    firebaseAdminInitializeAppSpy.reset();
  });

  it('Initialized FCM admin with correct data', function () {

    Push.Configure({});

    assert.equal(firebaseAdminInitializeAppSpy.callCount, 1);
    assert.equal(firebaseAdminInitializeAppSpy.getCall(0).args[0].credential, 'mock-cert');
    assert.ok(firebaseAdminInitializeAppSpy.getCall(0).args[1].startsWith('APP_IDENTIFIER__'));
  });
});
<commit_msg>Tested firebase admin initialization.<commit_after>// eslint-disable-next-line import/no-unresolved
import {assert} from 'meteor/practicalmeteor:chai';
import sinon from 'sinon';
import '../common/main.js';
import '../common/notifications.js';
import './push.api.js';
import './server.js';

const mockRequire = require('mock-require');

const APP_IDENTIFIER = 'APP_IDENTIFIER';
const FIREBASE_PROJECT_ID = 'FIREBASE_PROJECT_ID';
const FIREBASE_EMAIL = 'FIREBASE_EMAIL';
const FIREBASE_PRIVATE_KEY = 'FIREBASE_PRIVATE_KEY';

let originals = {
  GlobalVets: global.GlobalVets,
  nodeEnvs: {
    PUSH_NOTIFICATIONS_DISABLED: process.env.PUSH_NOTIFICATIONS_DISABLED,
    NODE_ENV: process.env.NODE_ENV
  }
};

if (!global.GlobalVets) {
  console.log('######## Init GlobalVets ######');
  global.GlobalVets = {};
}

const defaultVet = {
  _id: 'VET_ID',
  firebaseCredentials: {
    projectId: FIREBASE_PROJECT_ID,
    clientEmail: FIREBASE_EMAIL,
    privateKey: FIREBASE_PRIVATE_KEY
  },
  appIdentifier: APP_IDENTIFIER
};

describe('Testing push API', function () {

  let messagingMock = sinon.mock({
    send: function (fcmMessage) {
      console.log(`Sending message ${JSON.stringify(fcmMessage)}`);
    }
  });

  let firebaseMock = sinon.mock({
    messaging: function () {
      return messagingMock;
    }
  });

  let firebaseAdminMocks = {
    initializeApp: function (...args) {
      console.log('Spies, open you eyes! firebaseAdmin.initializeApp', JSON.stringify(args));
      return firebaseMock;
    },
    cert: function (...args) {
      console.log('Spies, open you eyes! firebaseAdmin.credential.cert', JSON.stringify(args));
      return 'mock-cert';
    }
  };

  let firebaseAdminInitializeAppSpy;
  let firebaseAdminCertSpy;

  before(function () {
    global.GlobalVets = {
      find: () => {
        return {
          fetch: () => {
            return [defaultVet];
          }
        };
      },
      findOne: () => {
        return defaultVet;
      }
    };

    process.env.PUSH_NOTIFICATIONS_DISABLED = false;
    process.env.NODE_ENV = 'production';

    firebaseAdminInitializeAppSpy = sinon.spy(firebaseAdminMocks, 'initializeApp');
    firebaseAdminCertSpy = sinon.spy(firebaseAdminMocks, 'cert');

    mockRequire('firebase-admin', {
      initializeApp: firebaseAdminMocks.initializeApp,
      credential: {
        cert: firebaseAdminMocks.cert
      }
    });

    mockRequire.reRequire('firebase-admin');

    Meteor.methods({
      'checkCertificate': function (vetId) {
        check(vetId, String);

        return 365;
      }
    });

  });

  after(function () {
    global.GlobalVets = originals.GlobalVets;

    process.env.PUSH_NOTIFICATIONS_DISABLED = originals.nodeEnvs.PUSH_NOTIFICATIONS_DISABLED;
    process.env.NODE_ENV = originals.nodeEnvs.NODE_ENV;

    firebaseAdminInitializeAppSpy.restore();
    firebaseAdminCertSpy.restore();
    mockRequire.stop('firebase-admin');

  });

  afterEach(function () {
    firebaseAdminInitializeAppSpy.reset();
    firebaseAdminCertSpy.reset();
  });

  it('Initializes FCM admin with correct data', function () {

    Push.Configure({});

    assert.equal(firebaseAdminInitializeAppSpy.callCount, 1);
    assert.equal(firebaseAdminInitializeAppSpy.getCall(0).args[0].credential, 'mock-cert');
    assert.ok(firebaseAdminInitializeAppSpy.getCall(0).args[1].startsWith('APP_IDENTIFIER__'));

    assert.equal(firebaseAdminCertSpy.callCount, 1);

    let serviceAccountConfig = firebaseAdminCertSpy.getCall(0).args[0];
    assert.isNotNull(serviceAccountConfig);
    assert.equal(serviceAccountConfig.projectId, FIREBASE_PROJECT_ID);
    assert.equal(serviceAccountConfig.clientEmail, FIREBASE_EMAIL);
    assert.equal(serviceAccountConfig.privateKey, FIREBASE_PRIVATE_KEY);
  });
});
<|endoftext|>


<commit_before>'use strict';

const Router = require('./index');
const moment = require('moment');
const inherits = require('util').inherits;
const middleware = require('storj-service-middleware');
const rawbody = middleware.rawbody;
const log = require('../../logger');
const authenticate = middleware.authenticate;
const { CREDIT_TYPES, PROMO_CODE, PROMO_EXPIRES, PROMO_AMOUNT, PAYMENT_PROCESSORS: { STRIPE } } = require('storj-service-storage-models/lib/constants');
const paymentProcessorAdapters = require('storj-service-storage-models/lib/models/payment-processor-adapters');
const stripe = require('../vendor/stripe');
const errors = require('storj-service-error-types');
const Promise = require('bluebird');

// TODO: Refactor all stripe-related endpoints into a single endpoint
// to remain payment processor agnostic.

/**
 * Handles endpoints for all user related operations
 */
function CreditsRouter(options) {
  if (!(this instanceof CreditsRouter)) {
    return new CreditsRouter(options);
  }
  Router.apply(this, arguments);
  this.models = options.storage.models;
}

inherits(CreditsRouter, Router);

CreditsRouter.prototype.handleReferralSignup = function(req, res) {
  const self = this;
  const Marketing = self.models.Marketing;

  console.log('HIT: handleReferralSignup', req.body);

  Marketing.isValidReferralLink(req.body.referralLink).then((marketing) => {
      console.log('VALID MARKETING DOC: ', marketing)
      marketing._id = marketing.id;
    self._getReferral(req.body, marketing)
      .then((referral) => {
        self._issueReferralSignupCredit(req.body, referral)
          .then((credit) => self._convertReferralRecipient(referral, credit))
          .then((referral) => res.status(200).send(referral))
          .catch((err) => res.status(500).send(err));
      })
      .catch((err) => res.status(500).send(err))
  })
  .catch((err) => {
    if (err.message === 'Invalid referral link') {
      return self.handleRegularSignup(req, res);
    }
    res.status(500).send(err);
  });
};

CreditsRouter.prototype.handleRegularSignup = function(req, res) {
  console.log('HIT: handleRegularSignup for', req.body);
  const self = this;
  self._issueRegularSignupCredit(req.body)
    .then((credit) => res.status(200).send(credit))
    .catch((err) => res.status(500).send(err));
};

CreditsRouter.prototype.handleSignups = function (req, res) {
  console.log('handlingSignup: ', req.body);

  const self = this;
  const Marketing = this.models.Marketing;
  Marketing.create(req.body.email, function(err, marketing) {
    if (err) {
      console.log('Error creating signup marketing: ', err);
      return res.status(500).send(err);
    }

    if (req.body.referralLink) {
      return self.handleReferralSignup(req, res);
    }

    return self.handleRegularSignup(req, res);
  })
}

CreditsRouter.prototype._issueRegularSignupCredit = function(data) {
  console.log('_issueRegularSignupCredit data:', data)
  const Credit = this.models.Credit;

  return new Promise((resolve, reject) => {
    const newCredit = new Credit({
      user: data.email,
      type: CREDIT_TYPES.AUTO,
      promo_code: PROMO_CODE.NEW_SIGNUP,
      promo_amount: PROMO_AMOUNT.NEW_SIGNUP,
      promo_expires: PROMO_EXPIRES.NEW_SIGNUP
    });

    newCredit
      .save()
      .then((credit) => {
        console.log('REGULAR USER CREDIT CREATED:', credit);
        return resolve(credit);
      })
      .catch((err) => {
        console.log('Error with creating regular sign up credit', err);
        return reject(err);
      });
  });
};

CreditsRouter.prototype._getReferral = function(data, marketing) {
  const Referral = this.models.Referral;
  console.log('data', data, 'marketing', marketing)
  return new Promise((resolve, reject) => {
    Referral
      .findOne({
        'sender.referralLink': data.referralLink,
        'recipient.email': data.email
      })
      .then((referral) => {
        if (referral) {
          console.log('GOT EXISTING REFERRAL: ', referral);
          return resolve(referral);
        }
        console.log('CREATING NEW REFERRAL...');
        Referral
          .create(marketing, data.email, 'link')
          .then((referral) => {
            console.log('NEW REFERRAL CREATED: ', referral)
            return resolve(referral)
          })
          .catch((err) => reject(errors.InternalError(err)));
      })
      .catch((err) => reject(errors.InternalError(err)))
  });
};

CreditsRouter.prototype._issueReferralSignupCredit = function(data, referral) {
  const Credit = this.models.Credit;

  return new Promise((resolve, reject) => {
    console.log('_issueReferralSignupCredit', data, referral.id);

    const newCredit = new Credit({
      user: data.email,
      type: CREDIT_TYPES.AUTO,
      promo_code: PROMO_CODE.REFERRAL_RECIPIENT,
      promo_amount: PROMO_AMOUNT.REFERRAL_RECIPIENT,
      promo_expires: PROMO_EXPIRES.REFERRAL_RECIPIENT,
      promo_referral_id: referral.id
    });

    newCredit.save().then((credit) => {
      console.log('REFEERRAL SIGNUP CREDIT CREATED: ', credit);
      return resolve(credit);
    })
    .catch((err) => {
      console.log('ERROR _issueReferralSignupCredit', err);
      return reject(err);
    });
  });
};

/**
 * anonymous function - description
 *
 * @param  {type} credit    description
 * @param  {type} marketing refers to sender marketing document, NOT recipient
 * @param  {type} data      description
 * @return {type}           description
 */
CreditsRouter.prototype._convertReferralRecipient = function(referral, credit) {

  console.log('_convertReferralRecipient')
  const Referral = this.models.Referral;

  return new Promise((resolve, reject) => {
    referral.convert_recipient_signup(credit)
      .then((referral) => {
        console.log('CONVERTED REFERRAL SIGNUP: ', referral)
        return resolve(referral);
      })
      .catch((err) => reject(errors.InternalError(err)));
  });
};

function getBalances(credits, debits) {
  const sumCredits = (total, item) => {
    return total + item.invoiced_amount;
  };

  const sumDebits = (total, item) => {
    return total + item.amount;
  };

  const creditSum = credits.reduce(sumCredits, 0);
  const debitSum = debits.reduce(sumDebits, 0);
  const invoicedBalance = debitSum - creditSum;

  const promoBalance = credits.reduce((total, item) => {
    return total + item.promo_amount;
  }, 0);

  return {
    invoicedBalance,
    promoBalance
  }
}

function handlePaymentFailure(res) {
  console.log("payment failed: ", res.locals.event.type);
}

CreditsRouter.prototype.verify = function(req, res, next) {
  if (process.env.NODE_ENV === 'production') {
    const eventId = req.body.id;
    stripe.events.retrieve(eventId, function(err, event) {
      if (err) {
        console.error('error verifying stripe event');
        next(err);
      }
      res.locals.event = event;
      next(null);
    })
  } else {
    res.locals.event = req.body;
    // NB: for manual testing only, need to remove
    // res.locals.event.data.object.customer = 'cus_97ADNC3zbcPQkR';
    next(null);
  }
};

CreditsRouter.prototype.checkType = function(type) {
  return function(req, res, next) {
    if (!type.test(res.locals.event.type)) {
      console.error("Expected event type to match: ", type);
      console.error("Received: ", res.locals.event.type);
      return res.sendStatus(400);
    }

    return next(null);
  }
};

CreditsRouter.prototype.createCredit = function(req, res) {
  try {
    if (/invoice.payment_failure/.test(res.locals.event.type)) {
      handlePaymentFailure(res);
      return res.sendStatus(203);
    }

    let invoice = res.locals.event.data.object;
    const customerId = invoice.customer;

    if (invoice.object === 'invoice' && invoice.attempted === 'false') {
      console.error("Error in CreditsRouter#createCredit: invoice.object should be invoice: ", invoice.object);
      console.error("Error in CreditsRouter#createCredit: invoice.attempted should be false: ", invoice.attempted);
      return res.sendStatus(400);
    }

    // NB: total includes stripe discounts
    if (invoice.total <= 0) {
      // TODO: better error handling
      const err = new Error('Error in CreditRouter#createCredit: invoice.total < 0');
      console.error(err);
      return res.sendStatus(400);
    }

    // TODO: refactor this to be payment processor agnostic!
    /**
     * 1) verify invoice amount?
     * 2) create stripe invoice item with negative amount = spendable credit/promo <= invoiced amount
     * 4) fetch updated stripe invoice
     * 3) create credit w/ invoiced_amount = updated stripe invoice amount
     */
    //-- lookup payment processor
    this.models.PaymentProcessor.findOne({
      'rawData.0.customer.id': customerId
    })
      .then((stripeProcessor) => {
        return Promise.all([
          this.models.Debit.find({
            user: stripeProcessor.user
          }),
          this.models.Credit.find({
            user: stripeProcessor.user
          }),
          Promise.resolve(stripeProcessor.user)
        ]);
      })
      .then((results) => {
        //-- lookup debits and credits for current user
        const [debits, credits, user] = results;
        const { invoicedBalance, promoBalance } = getBalances(credits, debits);
        let promo_amount;
        let invoiced_amount;

        if (promoBalance >= invoice.total) {
          promo_amount = -invoice.total;
          invoiced_amount = 0;
          // TODO: should we do this?:  `creditParams.paid = true;`
        } else {
          promo_amount = -promoBalance;
          invoiced_amount = (invoice.total - promoBalance);
        }

        if (promoBalance <= 0) {
          return Promise.resolve({
            invoiced_amount,
            promo_amount,
            user
          });
        }

        return new Promise((resolve, reject) => {
          const rejectWithError = (err) => {
            console.error('Error in CreditsRouter#createCredit: ' +
              'stripe.invoiceItems.create error:', err);
            return reject(err);
          };

          stripe.invoiceItems.create({
            customer: customerId,
            // NB: important! - associates new "discount" invoice
            //     item with current invoice
            invoice: invoice.id,
            // NB: important! - promo_amount is negative
            amount: promo_amount,
            currency: 'usd',
            description: 'Storj.io promo/credit applied'
          }, (err, invoiceItem) => {
            if (err) return rejectWithError(err);

            stripe.invoices.retrieve(invoice.id, (err, updatedInvoice) => {
              if (err) return rejectWithError(err);

              invoice = updatedInvoice;

              return resolve({
                invoiced_amount,
                promo_amount,
                user
              });
            });
          });
        });
      })
      .then(({ invoiced_amount, promo_amount, user }) => {
        //-- create new storj credit document
        // TODO: RESUME HERE: spend promo here...
        this.models.Credit.create({
          payment_processor: STRIPE,
          type: CREDIT_TYPES.AUTO,
          data: { invoice },
          invoiced_amount,
          promo_amount,
          user
        });
      })
      .catch((err) => {
        console.error(err);
        throw new Error(err);
      });
  } catch (err) {
    console.error('Error caught in `routes/credit#createCredit`: %j', err);
    return res.sendStatus(500);
  }

  return res.sendStatus(201);
};

CreditsRouter.prototype.confirmCredit = function(req, res) {
  try {
    const invoice = res.locals.event.data.object;

    if (!invoice.paid) {
      console.error('Error in CreditsRouter#confirmCredit: invoice has ' +
        'not been paid: %j', invoice);
      return res.status(202).json({ 'message': 'Invoice has not been paid.' })
    }

    this.models.Credit.findOne({
      'data.invoice.id': invoice.id
    })
      .then((credit) => {
        if (credit.invoiced_amount !== invoice.total) {
          console.error("Invoiced amount not equal to invoice subtotal.");
          console.error("Expected: ", credit.invoiced_amount);
          console.error("Received: ", invoice.total);
          return res.sendStatus(202);
        }

        if (!credit) {
          console.error("Could not find credit with invoice id: ", invoice.id);
          return res.sendStatus(202);
        }

        credit.paid_amount = invoice.total;
        credit.paid = true;
        credit.data = {
          invoice
        };

        // TODO: is this necessary?
        credit.markModified('data');

        return credit.save();
      })
      .then((credit) => {
        console.log('Credit updated: %j', credit);
        return res.sendStatus(204)
      })
      .catch((err) => {
        console.error("Error updating credit: ", err);
        return res.sendStatus(500);
      })
  } catch (err) {
    console.error('Error caught in `routes/credit#confirmCredit`: %j', err);
    return res.sendStatus(500);
  }
};

CreditsRouter.prototype.getReferralSenderCreditsByDate = function(req, res, next) {
  const Credit = this.models.credit;
  Credit.find({
    created: { $gte: req.periodStart, $lt: req.periodEnd }
  }, function(err, credits) {
    if (err) {
      return next(new errors.InternalError(err.message));
    }
    res.status(200).send(credits.map(credit => credit.toObject()))
  })
};

/**
 * Export definitions
 * @private
 */
CreditsRouter.prototype._definitions = function() {
  return [
    ['POST', '/credits',
      rawbody,
      this.verify,
      this.checkType(/^invoice.created$/),
      this.createCredit
    ],
    ['POST', '/credits/confirm',
      rawbody,
      this.verify,
      this.checkType(/^invoice.payment_(succeeded|failed)$/),
      this.confirmCredit
    ],
    ['GET',
      '/credits/referral/:periodStart/:periodEnd',
      authenticate,
      this.getReferralSenderCreditsByDate
    ],
    [
      'POST', '/credits/signups',
      rawbody,
      this.handleSignups
    ]
  ];
};

module.exports = CreditsRouter;
<commit_msg>bugfixing for staging<commit_after>'use strict';

const Router = require('./index');
const moment = require('moment');
const inherits = require('util').inherits;
const middleware = require('storj-service-middleware');
const rawbody = middleware.rawbody;
const log = require('../../logger');
const authenticate = middleware.authenticate;
const { CREDIT_TYPES, PROMO_CODE, PROMO_EXPIRES, PROMO_AMOUNT, PAYMENT_PROCESSORS: { STRIPE } } = require('storj-service-storage-models/lib/constants');
const paymentProcessorAdapters = require('storj-service-storage-models/lib/models/payment-processor-adapters');
const stripe = require('../vendor/stripe');
const errors = require('storj-service-error-types');
const Promise = require('bluebird');

// TODO: Refactor all stripe-related endpoints into a single endpoint
// to remain payment processor agnostic.

/**
 * Handles endpoints for all user related operations
 */
function CreditsRouter(options) {
  if (!(this instanceof CreditsRouter)) {
    return new CreditsRouter(options);
  }
  Router.apply(this, arguments);
  this.models = options.storage.models;
}

inherits(CreditsRouter, Router);

CreditsRouter.prototype.handleReferralSignup = function(req, res) {
  const self = this;
  const Marketing = self.models.Marketing;

  console.log('HIT: handleReferralSignup', req.body);

  Marketing.isValidReferralLink(req.body.referralLink).then((marketing) => {
      console.log('VALID MARKETING DOC: ', marketing)
      marketing._id = marketing.id;
    self._getReferral(req.body, marketing)
      .then((referral) => {
        self._issueReferralSignupCredit(req.body, referral)
          .then((credit) => self._convertReferralRecipient(referral, credit))
          .then((referral) => res.status(200).send(referral))
          .catch((err) => res.status(500).send(err));
      })
      .catch((err) => res.status(500).send(err))
  })
  .catch((err) => {
    if (err.message === 'Invalid referral link') {
      return self.handleRegularSignup(req, res);
    }
    res.status(500).send(err);
  });
};

CreditsRouter.prototype.handleRegularSignup = function(req, res) {
  console.log('HIT: handleRegularSignup for', req.body);
  const self = this;
  self._issueRegularSignupCredit(req.body)
    .then((credit) => res.status(200).send(credit))
    .catch((err) => res.status(500).send(err));
};

CreditsRouter.prototype.handleSignups = function (req, res) {
  console.log('handlingSignup: ', req.body);

  const self = this;
  const Marketing = this.models.Marketing;
  Marketing.create(req.body.email, function(err, marketing) {
    if (err) {
      console.log('Error creating signup marketing: ', err);
      return res.status(500).send(err);
    }

    if (req.body.referralLink) {
      return self.handleReferralSignup(req, res);
    }

    return self.handleRegularSignup(req, res);
  })
}

CreditsRouter.prototype._issueRegularSignupCredit = function(data) {
  console.log('_issueRegularSignupCredit data:', data)
  const Credit = this.models.Credit;

  return new Promise((resolve, reject) => {
    const newCredit = new Credit({
      user: data.email,
      type: CREDIT_TYPES.AUTO,
      promo_code: PROMO_CODE.NEW_SIGNUP,
      promo_amount: PROMO_AMOUNT.NEW_SIGNUP,
      promo_expires: PROMO_EXPIRES.NEW_SIGNUP
    });

    newCredit
      .save()
      .then((credit) => {
        console.log('REGULAR USER CREDIT CREATED:', credit);
        return resolve(credit);
      })
      .catch((err) => {
        console.log('Error with creating regular sign up credit', err);
        return reject(err);
      });
  });
};

CreditsRouter.prototype._getReferral = function(data, marketing) {
  const Referral = this.models.Referral;
  console.log('data', data, 'marketing', marketing)
  return new Promise((resolve, reject) => {
    Referral
      .findOne({
        'sender.referralLink': data.referralLink,
        'recipient.email': data.email
      })
      .then((referral) => {
        if (referral) {
          console.log('GOT EXISTING REFERRAL: ', referral);
          return resolve(referral);
        }
        console.log('CREATING NEW REFERRAL...');
        Referral
          .create(marketing, data.email, 'link')
          .then((referral) => {
            console.log('NEW REFERRAL CREATED: ', referral)
            return resolve(referral)
          })
          .catch((err) => reject(errors.InternalError(err)));
      })
      .catch((err) => reject(errors.InternalError(err)))
  });
};

CreditsRouter.prototype._issueReferralSignupCredit = function(data, referral) {
  const Credit = this.models.Credit;

  return new Promise((resolve, reject) => {
    console.log('_issueReferralSignupCredit', data, referral.id);

    const newCredit = new Credit({
      user: data.email,
      type: CREDIT_TYPES.AUTO,
      promo_code: PROMO_CODE.REFERRAL_RECIPIENT,
      promo_amount: PROMO_AMOUNT.REFERRAL_RECIPIENT,
      promo_expires: PROMO_EXPIRES.REFERRAL_RECIPIENT,
      promo_referral_id: referral.id
    });

    newCredit.save().then((credit) => {
      console.log('REFEERRAL SIGNUP CREDIT CREATED: ', credit);
      return resolve(credit);
    })
    .catch((err) => {
      console.log('ERROR _issueReferralSignupCredit', err);
      return reject(err);
    });
  });
};

/**
 * anonymous function - description
 *
 * @param  {type} credit    description
 * @param  {type} marketing refers to sender marketing document, NOT recipient
 * @param  {type} data      description
 * @return {type}           description
 */
CreditsRouter.prototype._convertReferralRecipient = function(referral, credit) {

  console.log('_convertReferralRecipient')
  const Referral = this.models.Referral;

  return new Promise((resolve, reject) => {
    referral.convert_recipient_signup(credit)
      .then((referral) => {
        console.log('CONVERTED REFERRAL SIGNUP: ', referral)
        return resolve(referral);
      })
      .catch((err) => reject(errors.InternalError(err)));
  });
};

function getBalances(credits, debits) {
  const sumCredits = (total, item) => {
    return total + item.invoiced_amount;
  };

  const sumDebits = (total, item) => {
    return total + item.amount;
  };

  const creditSum = credits.reduce(sumCredits, 0);
  const debitSum = debits.reduce(sumDebits, 0);
  const invoicedBalance = debitSum - creditSum;

  const promoBalance = credits.reduce((total, item) => {
    return total + item.promo_amount;
  }, 0);

  return {
    invoicedBalance,
    promoBalance
  }
}

function handlePaymentFailure(res) {
  console.log("payment failed: ", res.locals.event.type);
}

CreditsRouter.prototype.verify = function(req, res, next) {
  if (process.env.NODE_ENV === 'production') {
    const eventId = req.body.id;
    stripe.events.retrieve(eventId, function(err, event) {
      if (err) {
        console.error('error verifying stripe event');
        next(err);
      }
      res.locals.event = event;
      next(null);
    })
  } else {
    res.locals.event = req.body;
    // NB: for manual testing only, need to remove
    // res.locals.event.data.object.customer = 'cus_97ADNC3zbcPQkR';
    next(null);
  }
};

CreditsRouter.prototype.checkType = function(type) {
  return function(req, res, next) {
    if (!type.test(res.locals.event.type)) {
      console.error("Expected event type to match: ", type);
      console.error("Received: ", res.locals.event.type);
      return res.sendStatus(400);
    }

    return next(null);
  }
};

CreditsRouter.prototype.createCredit = function(req, res) {
  try {
    if (/invoice.payment_failure/.test(res.locals.event.type)) {
      handlePaymentFailure(res);
      return res.sendStatus(203);
    }

    let invoice = res.locals.event.data.object;
    const customerId = invoice.customer;

    if (invoice.object === 'invoice' && invoice.attempted === 'false') {
      console.error("Error in CreditsRouter#createCredit: invoice.object should be invoice: ", invoice.object);
      console.error("Error in CreditsRouter#createCredit: invoice.attempted should be false: ", invoice.attempted);
      return res.sendStatus(400);
    }

    // NB: total includes stripe discounts
    if (invoice.total <= 0) {
      // TODO: better error handling
      const err = new Error('Error in CreditRouter#createCredit: invoice.total < 0');
      console.error(err);
      return res.sendStatus(400);
    }

    // TODO: refactor this to be payment processor agnostic!
    /**
     * 1) verify invoice amount?
     * 2) create stripe invoice item with negative amount = spendable credit/promo <= invoiced amount
     * 4) fetch updated stripe invoice
     * 3) create credit w/ invoiced_amount = updated stripe invoice amount
     */
    //-- lookup payment processor
    this.models.PaymentProcessor.findOne({
      'rawData.0.customer.id': customerId
    })
      .then((stripeProcessor) => {
        return Promise.all([
          this.models.Debit.find({
            user: stripeProcessor.user
          }),
          this.models.Credit.find({
            user: stripeProcessor.user
          }),
          Promise.resolve(stripeProcessor.user)
        ]);
      })
      .then((results) => {
        //-- lookup debits and credits for current user
        const [debits, credits, user] = results;
        const { invoicedBalance, promoBalance } = getBalances(credits, debits);
        let promo_amount;
        let invoiced_amount;

        if (promoBalance >= invoice.total) {
          promo_amount = -invoice.total;
          invoiced_amount = 0;
          // TODO: should we do this?:  `creditParams.paid = true;`
        } else {
          promo_amount = -promoBalance;
          invoiced_amount = (invoice.total - promoBalance);
        }

        if (promoBalance <= 0) {
          return Promise.resolve({
            invoiced_amount,
            user
          });
        }

        return new Promise((resolve, reject) => {
          const rejectWithError = (err) => {
            console.error('Error in CreditsRouter#createCredit: ' +
              'stripe.invoiceItems.create error:', err);
            return reject(err);
          };

          stripe.invoiceItems.create({
            customer: customerId,
            // NB: important! - associates new "discount" invoice
            //     item with current invoice
            invoice: invoice.id,
            // NB: important! - promo_amount is negative
            amount: promo_amount,
            currency: 'usd',
            description: 'Storj.io promo/credit applied'
          }, (err, invoiceItem) => {
            if (err) return rejectWithError(err);

            stripe.invoices.retrieve(invoice.id, (err, updatedInvoice) => {
              if (err) return rejectWithError(err);

              invoice = updatedInvoice;

              return resolve({
                invoiced_amount,
                promo_amount,
                user
              });
            });
          });
        });
      })
      .then(({ invoiced_amount, promo_amount, user }) => {
        //-- create new storj credit document
        // TODO: RESUME HERE: spend promo here...
        this.models.Credit.create({
          payment_processor: STRIPE,
          type: CREDIT_TYPES.AUTO,
          data: { invoice },
          invoiced_amount,
          promo_amount,
          user
        });
      })
      .catch((err) => {
        console.error(err);
        throw new Error(err);
      });
  } catch (err) {
    console.error('Error caught in `routes/credit#createCredit`: %j', err);
    return res.sendStatus(500);
  }

  return res.sendStatus(201);
};

CreditsRouter.prototype.confirmCredit = function(req, res) {
  try {
    const invoice = res.locals.event.data.object;

    if (!invoice.paid) {
      console.error('Error in CreditsRouter#confirmCredit: invoice has ' +
        'not been paid: %j', invoice);
      return res.status(202).json({ 'message': 'Invoice has not been paid.' })
    }

    this.models.Credit.findOne({
      'data.invoice.id': invoice.id
    })
      .then((credit) => {
        if (credit.invoiced_amount !== invoice.total) {
          console.error("Invoiced amount not equal to invoice subtotal.");
          console.error("Expected: ", credit.invoiced_amount);
          console.error("Received: ", invoice.total);
          return res.sendStatus(202);
        }

        if (!credit) {
          console.error("Could not find credit with invoice id: ", invoice.id);
          return res.sendStatus(202);
        }

        credit.paid_amount = invoice.total;
        credit.paid = true;
        credit.data = {
          invoice
        };

        // TODO: is this necessary?
        credit.markModified('data');

        return credit.save();
      })
      .then((credit) => {
        console.log('Credit updated: %j', credit);
        return res.sendStatus(204)
      })
      .catch((err) => {
        console.error("Error updating credit: ", err);
        return res.sendStatus(500);
      })
  } catch (err) {
    console.error('Error caught in `routes/credit#confirmCredit`: %j', err);
    return res.sendStatus(500);
  }
};

CreditsRouter.prototype.getReferralSenderCreditsByDate = function(req, res, next) {
  const Credit = this.models.credit;
  Credit.find({
    created: { $gte: req.periodStart, $lt: req.periodEnd }
  }, function(err, credits) {
    if (err) {
      return next(new errors.InternalError(err.message));
    }
    res.status(200).send(credits.map(credit => credit.toObject()))
  })
};

/**
 * Export definitions
 * @private
 */
CreditsRouter.prototype._definitions = function() {
  return [
    ['POST', '/credits',
      rawbody,
      this.verify,
      this.checkType(/^invoice.created$/),
      this.createCredit
    ],
    ['POST', '/credits/confirm',
      rawbody,
      this.verify,
      this.checkType(/^invoice.payment_(succeeded|failed)$/),
      this.confirmCredit
    ],
    ['GET',
      '/credits/referral/:periodStart/:periodEnd',
      authenticate,
      this.getReferralSenderCreditsByDate
    ],
    [
      'POST', '/credits/signups',
      rawbody,
      this.handleSignups
    ]
  ];
};

module.exports = CreditsRouter;
<|endoftext|>


<commit_before>var drag = require('../events').drag;
var mouse = require('../events').mouse;
var util = require('./util');

module.exports = init;

var events = {
  bounds_changed: 'moveend',
  center_changed: 'moveend',
  zoom_changed: 'zoomend'
};

function handleEvent(self, fn, e) {
  if (e && e.stopPropagation) {
    e.stopPropagation();
  }
  fn.call(self, e);
}

function handleMouseEvent(self, fn, e) {
  self.ll(e);
  if (!e.ll) {
    // mouse events are expected to have location
    return;
  }
  if (!self._l) {
    // give features a chance to handle it first
    setTimeout(function () {
      if (e.alreadyHandledByFeature) {
        return;
      }
      handleEvent(self, fn, e);
    }, 1);
    return;
  }
  e.alreadyHandledByFeature = true;
  handleEvent(self, fn, e);
}

function handleClickEvent(self, fn, e) {
  e.alreadyHandledByFeature = true;
  if (e && e.stopPropagation) {
    e.stopPropagation();
  }
  self.ll(e);
  if (!e.ll) {
    // mouse events are expected to have location
    return;
  }
  fn.call(self, e);
}

function ll(e) {
  if (e && e.lngLat) {
    e.ll = util.mll2ll(e.lngLat);
  }
}

function init(self, options) {
  var listeners = {};

  function on(event, fn) {
    var handler, feature;
    event = events[event] || event;
    if (event === 'click' && self._l) {
      handler = handleClickEvent.bind(undefined, self, fn);
    }
    else if (mouse[event]) {
      handler = handleMouseEvent.bind(undefined, self, fn);
    }
    else {
      handler = handleEvent.bind(undefined, self, fn);
    }
    if (!drag[event]) {
      if (self._l) {
        feature = self._l.id;
        if (self._m) {
          self._featureEventHandler.on(event, self.layerId, feature, handler);
        }
      }
      else if (self._m) {
        self._m.on(event, handler);
      }
    }
    listeners[event] = listeners[event] || [];
    listeners[event].push({
      event: event,
      feature: feature,
      fn: fn,
      handler: handler
    });
    return self;
  }

  function off(event, fn) {
    if (event === undefined) {
      Object.keys(listeners).forEach(function(event) {
        listeners[event].forEach(function (listener) {
          off(listener.event, listener.fn);
        });
      });
      listeners = {};
    }
    else {
      event = events[event] || event;
      if (listeners[event] && listeners[event].some(function (listener, i, listeners) {
        if (listener.fn === fn) {
          if (!drag[event]) {
            if (listener.feature) {
              if (self._m) {
                self._featureEventHandler.off(event, self.layerId, listener.feature, listener.handler);
              }
            }
            else {
              self._m.off(event, listener.handler);
            }
          }
          listeners.splice(i, 1);
          return true;
        }
      }) && !listeners[event].length) {
        delete listeners[event];
      }
    }
    return self;
  }

  function fire(event, e) {
    if (listeners[event]) {
      listeners[event].forEach(function (listener) {
        listener.handler(e);
      });
    }
  }

  function add(map) {
    if (!self._m) {
      self._m = map._m;
      options.onadd(map);
      self._featureEventHandler = map._featureEventHandler;
      Object.keys(listeners).forEach(function(event) {
        if (!drag[event]) {
          listeners[event].forEach(function (listener) {
            if (listener.feature) {
              self._featureEventHandler.on(listener.event, self.layerId, listener.feature, listener.handler);
            }
            else {
              self._m.on(listener.event, listener.handler);
            }
          });
        }
      });
    }
    return self;
  }

  function remove() {
    if (self._m) {
      Object.keys(listeners).forEach(function(event) {
        if (!drag[event]) {
          listeners[event].forEach(function (listener) {
            if (listener.feature) {
              self._featureEventHandler.off(listener.event, self.layerId, listener.feature, listener.handler);
            }
            else {
              self._m.off(listener.event, listener.handler);
            }
          });
        }
      });
      delete self._featureEventHandler;
      options.onremove();
      delete self._m;
    }
    return self;
  }

  self.on = on;
  self.off = off;
  self.fire = fire;
  self.ll = self.ll || ll;

  if (options) {
    if (options.onadd) {
      self.add = add;
    }
    if (options.onremove) {
      self.remove = remove;
    }
  }

  return self;
}
<commit_msg>register for map events using new feature event handler<commit_after>var drag = require('../events').drag;
var mouse = require('../events').mouse;
var util = require('./util');

module.exports = init;

var events = {
  bounds_changed: 'moveend',
  center_changed: 'moveend',
  zoom_changed: 'zoomend'
};

function handleEvent(self, fn, e) {
  if (e && e.stopPropagation) {
    e.stopPropagation();
  }
  fn.call(self, e);
}

function handleMouseEvent(self, fn, e) {
  self.ll(e);
  if (!e.ll) {
    // mouse events are expected to have location
    return;
  }
  handleEvent(self, fn, e);
}

function handleClickEvent(self, fn, e) {
  e.alreadyHandledByFeature = true;
  if (e && e.stopPropagation) {
    e.stopPropagation();
  }
  self.ll(e);
  if (!e.ll) {
    // mouse events are expected to have location
    return;
  }
  fn.call(self, e);
}

function ll(e) {
  if (e && e.lngLat) {
    e.ll = util.mll2ll(e.lngLat);
  }
}

function init(self, options) {
  var listeners = {};

  function on(event, fn) {
    var handler, feature;
    event = events[event] || event;
    if (event === 'click' && self._l) {
      handler = handleClickEvent.bind(undefined, self, fn);
    }
    else if (mouse[event]) {
      handler = handleMouseEvent.bind(undefined, self, fn);
    }
    else {
      handler = handleEvent.bind(undefined, self, fn);
    }
    if (!drag[event]) {
      if (self._l) {
        feature = self._l.id;
        if (self._m) {
          self._featureEventHandler.on(event, self.layerId, feature, handler);
        }
      }
      else if (self._m) {
        self._featureEventHandler.mapOn(event, handler);
      }
    }
    listeners[event] = listeners[event] || [];
    listeners[event].push({
      event: event,
      feature: feature,
      fn: fn,
      handler: handler
    });
    return self;
  }

  function off(event, fn) {
    if (event === undefined) {
      Object.keys(listeners).forEach(function(event) {
        listeners[event].forEach(function (listener) {
          off(listener.event, listener.fn);
        });
      });
      listeners = {};
    }
    else {
      event = events[event] || event;
      if (listeners[event] && listeners[event].some(function (listener, i, listeners) {
        if (listener.fn === fn) {
          if (!drag[event]) {
            if (listener.feature) {
              if (self._m) {
                self._featureEventHandler.off(event, self.layerId, listener.feature, listener.handler);
              }
            }
            else {
              self._featureEventHandler.mapOff(event, listener.handler);
            }
          }
          listeners.splice(i, 1);
          return true;
        }
      }) && !listeners[event].length) {
        delete listeners[event];
      }
    }
    return self;
  }

  function fire(event, e) {
    if (listeners[event]) {
      listeners[event].forEach(function (listener) {
        listener.handler(e);
      });
    }
  }

  function add(map) {
    if (!self._m) {
      self._m = map._m;
      options.onadd(map);
      self._featureEventHandler = map._featureEventHandler;
      Object.keys(listeners).forEach(function(event) {
        if (!drag[event]) {
          listeners[event].forEach(function (listener) {
            if (listener.feature) {
              self._featureEventHandler.on(listener.event, self.layerId, listener.feature, listener.handler);
            }
            else {
              self._featureEventHandler.mapOn(listener.event, listener.handler);
            }
          });
        }
      });
    }
    return self;
  }

  function remove() {
    if (self._m) {
      Object.keys(listeners).forEach(function(event) {
        if (!drag[event]) {
          listeners[event].forEach(function (listener) {
            if (listener.feature) {
              self._featureEventHandler.off(listener.event, self.layerId, listener.feature, listener.handler);
            }
            else {
              self._featureEventHandler.mapOff(listener.event, listener.handler);
            }
          });
        }
      });
      delete self._featureEventHandler;
      options.onremove();
      delete self._m;
    }
    return self;
  }

  self.on = on;
  self.off = off;
  self.fire = fire;
  self.ll = self.ll || ll;

  if (options) {
    if (options.onadd) {
      self.add = add;
    }
    if (options.onremove) {
      self.remove = remove;
    }
  }

  return self;
}
<|endoftext|>


<commit_before>// Copyright 2014, Renasar Technologies Inc.
/* jshint node: true */
'use strict';

var di = require('di'),
    // TODO: re-enable
    //_ = require('lodash'),
    express = require('express'),
    cors = require('cors'),
    onFinished = require('on-finished');

/**
 * Get remote address of the client.
 * @private
 * @param {express.Request} req from express
 * @returns {String|Undefined} either the ip of requester or undefined
 *                             if unavailable
 */
function remoteAddress(req) {
    if (req.ip) {
        return req.ip;
    }

    if (req._remoteAddress) {
        return req._remoteAddress;
    }

    if (req.connection) {
        return req.connection.remoteAddress;
    }

    return undefined;
}

module.exports = httpServiceFactory;

di.annotate(httpServiceFactory, new di.Provide('Http.Server'));
di.annotate(httpServiceFactory,
    new di.Inject(
            'Protocol.Events',
            'Services.Configuration',
            'Services.Lookup',
            'express-app',
            'Logger',
            'Tracer',
            'Q'
        )
    );

/**
 * Factory that creates the express http service
 * @private
 * @param {httpProtocol} httpProtocol
 * @param {configuration} configuration
 * @param {Protocol.Dhcp} dhcpProtocol
 * @param {express} app
 * @param Logger
 * @param {WebSocketService} websocketService
 * @returns {express}
 */
function httpServiceFactory(
    eventsProtocol,
    configuration,
    lookupService,
    app,
    Logger,
    tracer,
    Q
) {
    var logger = Logger.initialize(httpServiceFactory);
    var server;

    function httpEventMiddleware(req, res, next) {
        req._startAt = process.hrtime();
        var ipAddress = remoteAddress(req);

        onFinished(res, function () {
            if (!req._startAt) {
                return '';
            }

            var diff = process.hrtime(req._startAt),
                ms = diff[0] * 1e3 + diff[1] * 1e-6;

            lookupService.ipAddressToNode(ipAddress).then(function (nodeId) {
                logger.silly(
                    'http: ' + req.method +
                    ' ' + res.statusCode +
                    ' ' + ms.toFixed(3) +
                    ' - ' + req.originalUrl,
                    {
                        ip: ipAddress,
                        id: nodeId
                    }
                );

                eventsProtocol.publishHttpResponse(
                    nodeId || 'external',
                    {
                        address: ipAddress,
                        method: req.method,
                        url: req.originalUrl,
                        statusCode: res.statusCode,
                        time: ms.toFixed(3)
                    }
                );

            }).catch(function (error) {
                console.log(error);
                console.log(error.stack);
            });
        });

        next();
    }

    // CORS Support
    app.use(cors());
    app.options('*', cors());

    app.use(tracer.middleware());

    // Imaging Event Middleware
    app.use(httpEventMiddleware);

    // Serve Static Content
    app.use(express.static(configuration.get('httpStaticDirectory')));
    app.use(express.static(configuration.get('httpFrontendDirectory')));
    app.use("/docs", express.static(configuration.get('httpApiDocsDirectory')));

    // Extend HTTP to include web sockets
    app.listen = function() {
      server = require('http').createServer(this);
      server.on('close', function() {
          console.log("HTTP server closing");
      });
      // TODO: re-enable
      //websocketService.listen(server);
      return server.listen.apply(server, arguments);
    };

    app.close = function() {
        return Q.ninvoke(server, 'close');
    };

    return app;
}
<commit_msg>use the logger in the http service startup<commit_after>// Copyright 2014, Renasar Technologies Inc.
/* jshint node: true */
'use strict';

var di = require('di'),
    // TODO: re-enable
    //_ = require('lodash'),
    express = require('express'),
    cors = require('cors'),
    onFinished = require('on-finished');

/**
 * Get remote address of the client.
 * @private
 * @param {express.Request} req from express
 * @returns {String|Undefined} either the ip of requester or undefined
 *                             if unavailable
 */
function remoteAddress(req) {
    if (req.ip) {
        return req.ip;
    }

    if (req._remoteAddress) {
        return req._remoteAddress;
    }

    if (req.connection) {
        return req.connection.remoteAddress;
    }

    return undefined;
}

module.exports = httpServiceFactory;

di.annotate(httpServiceFactory, new di.Provide('Http.Server'));
di.annotate(httpServiceFactory,
    new di.Inject(
            'Protocol.Events',
            'Services.Configuration',
            'Services.Lookup',
            'express-app',
            'Logger',
            'Tracer',
            'Q'
        )
    );

/**
 * Factory that creates the express http service
 * @private
 * @param {httpProtocol} httpProtocol
 * @param {configuration} configuration
 * @param {Protocol.Dhcp} dhcpProtocol
 * @param {express} app
 * @param Logger
 * @param {WebSocketService} websocketService
 * @returns {express}
 */
function httpServiceFactory(
    eventsProtocol,
    configuration,
    lookupService,
    app,
    Logger,
    tracer,
    Q
) {
    var logger = Logger.initialize(httpServiceFactory);
    var server;

    function httpEventMiddleware(req, res, next) {
        req._startAt = process.hrtime();
        var ipAddress = remoteAddress(req);

        onFinished(res, function () {
            if (!req._startAt) {
                return '';
            }

            var diff = process.hrtime(req._startAt),
                ms = diff[0] * 1e3 + diff[1] * 1e-6;

            lookupService.ipAddressToNode(ipAddress).then(function (nodeId) {
                logger.silly(
                    'http: ' + req.method +
                    ' ' + res.statusCode +
                    ' ' + ms.toFixed(3) +
                    ' - ' + req.originalUrl,
                    {
                        ip: ipAddress,
                        id: nodeId
                    }
                );

                eventsProtocol.publishHttpResponse(
                    nodeId || 'external',
                    {
                        address: ipAddress,
                        method: req.method,
                        url: req.originalUrl,
                        statusCode: res.statusCode,
                        time: ms.toFixed(3)
                    }
                );

            }).catch(function (error) {
                assert.ifError(error);
            });
        });

        next();
    }

    // CORS Support
    app.use(cors());
    app.options('*', cors());

    app.use(tracer.middleware());

    // Imaging Event Middleware
    app.use(httpEventMiddleware);

    // Serve Static Content
    app.use(express.static(configuration.get('httpStaticDirectory')));
    app.use(express.static(configuration.get('httpFrontendDirectory')));
    app.use("/docs", express.static(configuration.get('httpApiDocsDirectory')));

    // Extend HTTP to include web sockets
    app.listen = function() {
      server = require('http').createServer(this);
      server.on('close', function() {
          console.log("HTTP server closing");
      });
      // TODO: re-enable
      //websocketService.listen(server);
      return server.listen.apply(server, arguments);
    };

    app.close = function() {
        return Q.ninvoke(server, 'close');
    };

    return app;
}
<|endoftext|>