<commit_before>define([
    'underscore',
    'knockout',
    'knockout-mapping',
    'models/abstract',
    'arches'
], function(_, ko, koMapping, AbstractModel, arches) {
    return AbstractModel.extend({
        url: arches.urls.mobile_survey,

        initialize: function(options) {
            var self = this;
            self.identities = options.identities || [];
            self._mobilesurvey = ko.observable('{}');
            self.name = ko.observable('');
            self.description = ko.observable('');
            self.startdate = ko.observable(null);
            self.enddate = ko.observable(null);
            self.active = ko.observable(false);
            self.createdby = ko.observable(null);
            self.lasteditedby = ko.observable(null);
            self.users = ko.observableArray([]);
            self.groups = ko.observableArray([]);
            self.showDetails = ko.observable(false);
            self.cards = ko.observableArray([]);
            self.datadownloadconfig = koMapping.fromJS(options.source.datadownloadconfig) || ko.observable();
            self.tilecache = ko.observable('');
            self.bounds = ko.observable(self.getDefaultBounds(null));
            self.collectedResources = ko.observable(false);
            self.showCustomDataDownload = ko.observable(false);

            var getUserName = function(id) {
                var user = _.find(self.identities, function(i) {
                    return i.type === 'user' && i.id === id;
                });
                return user ? user.name : '';
            };

            self.setIdentityApproval = function() {
                var groups = ko.unwrap(this.groups)
                var users = ko.unwrap(this.users)
                _.each(this.identities, function(item) {
                    item.approved(false);
                    if ((item.type === 'group' && _.contains(groups, item.id)) ||
                        (item.type === 'user' && _.contains(users, item.id))) {
                        item.approved(true);
                    }
                })
            };

            _.each(this.identities, function(item) {
                if (item.type === 'group') {
                    _.each(item.users, function(user){
                        if (!user.expanded) {
                            user.expanded = ko.observable(false);
                        }
                    })
                }
            })

            self.createdbyName = ko.computed(function() {
                return getUserName(self.createdby());
            });

            self.lasteditedbyName = ko.computed(function() {
                return getUserName(self.lasteditedby());
            });

            self.userFilter = ko.observable('');
            self.selectedUser = ko.observable();

            self.filteredUsers = ko.computed(function() {
                var filter = self.userFilter();
                this.selected = _.filter(self.identities, function(identity) {
                    return ko.unwrap(identity.selected);
                });
                var list = []

                if (this.selected.length === 1) {
                    list = this.selected[0].users;
                    if (filter.length === 0) {
                        return list;
                    }
                }

                return _.filter(list, function(user) {
                    if (user.username.startsWith(filter)) {
                        return user
                    }
                });

            });

            self.selectedIdentity = ko.computed(function() {
                var selected = _.filter(self.identities, function(identity) {
                    return ko.unwrap(identity.selected);
                });
                if (selected.length === 1 && selected[0].users) {
                    self.selectedUser(selected[0].users.length > 0 ? selected[0].users[0] : undefined);
                }
                return selected.length > 0 ? selected[0] : undefined;
            });

            self.approvedUserNames = ko.computed(function() {
                names = [];
                _.each(self.identities, function(identity) {
                    if (identity.type === 'user' && identity.approved()) {
                        names.push(identity.name);
                    }
                }, this);
                return names;
            })

            self.approvedGroupNames = ko.computed(function() {
                names = [];
                _.each(self.identities, function(identity) {
                    if (identity.type === 'group' && identity.approved()) {
                        names.push(identity.name);
                    }
                }, this);
                return names;
            })

            self.hasIdentity = function() {
                var approved = false;
                var identity = self.selectedIdentity();
                if (identity) {
                    approved = identity.approved()
                }
                return approved
            };

            self.toggleIdentity = function() {
                var identity = self.selectedIdentity();
                if (identity) {
                    var currentIdentities = identity.type === 'user' ? self.users : self.groups;
                    if (self.hasIdentity()) {
                        currentIdentities.remove(identity.id);
                        if (identity.type === 'user') {
                            identity.approved(false);
                        } else {
                            identity.approved(false);
                            _.chain(self.identities).filter(function(id) {
                                return id.type === 'user' && _.contains(_.pluck(this.selectedIdentity().users, 'id'), id.id)
                            }, this).each(function(user) {
                                if (_.intersection(user.group_ids, self.groups()).length === 0) { // user does not belong to any accepted groups
                                    user.approved(false);
                                    self.users.remove(user.id);
                                }
                            })
                        };
                    } else {
                        if (!_.contains(currentIdentities(), identity.id)) {
                            currentIdentities.push(identity.id);
                        }
                        identity.approved(true);
                        if (identity.type === 'user') {
                            self.users.push(identity.id);
                        } else {
                            _.chain(self.identities).filter(function(id) {
                                var identity = identity;
                                return id.type === 'user'
                            }).each(function(user) {
                                if (_.intersection(user.group_ids, self.groups()).length > 0) {
                                    user.approved(true);
                                    if (!_.contains(self.users(), user.id)) {
                                        self.users.push(user.id);
                                    }
                                }
                            })
                        }
                    };
                };
            };

            self.updateResourceDownloadList = function(val){
                if (_.contains(self.datadownloadconfig.resources(), val.id)) {
                    self.datadownloadconfig.resources.remove(val.id)
                } else {
                    self.datadownloadconfig.resources.push(val.id)
                }
            }

            self.updateCards = function(cards) {
                var approvedCards = _.chain(cards)
                .filter(function(card){
                    if (card.approved()) {
                        return card.cardid
                    }
                })
                .pluck('cardid').value()
                var diff = _.difference(self.cards(), approvedCards)
                self.cards(_.union(diff, approvedCards))
            }

            self.updateApproved = function(val){
                val.item.approved(true);
                self.updateCards(val.targetParent())
            };

            self.updateUnapproved = function(val){
                val.item.approved(false);
                self.cards.remove(val.item.cardid)
            };

            self.addAllCardsByResource = function(val) {
                var resource = val.resourceList.selected()
                _.each(resource.cards(), function(card){
                    card.approved(true)
                })
                self.updateCards(resource.cards())
            }

            self.removeAllCardsByResource = function(val) {
                var resource = val.resourceList.selected()
                _.each(resource.cards(), function(card){
                    card.approved(false);
                    self.cards.remove(card.cardid)
                })
            }

            self.toggleShowDetails = function() {
                self.setIdentityApproval();
                self.showDetails(!self.showDetails())
            }

            self.parse(options.source);
            self.setIdentityApproval();

            self.json = ko.computed(function() {
                var jsObj = ko.toJS({
                    name: self.name,
                    description: self.description,
                    startdate: self.startdate,
                    enddate: self.enddate,
                    active: self.active,
                    id: self.get('id'),
                    groups: self.groups,
                    users: self.users,
                    cards: self.cards,
                    bounds: self.bounds,
                    tilecache: self.tilecache,
                    datadownloadconfig: koMapping.toJS(self.datadownloadconfig)
                });
                return JSON.stringify(_.extend(JSON.parse(self._mobilesurvey()), jsObj))
            });

            self.dirty = ko.computed(function() {
                return self.json() !== self._mobilesurvey() || !self.get('id');
            });
        },

        getDefaultBounds: function(geojson) {
            result = geojson;
            if (!geojson) {
                var fc = {"type": "FeatureCollection", "features": []}
                var geomFactory = function(coords){
                    return { "type": "Feature",
                                "geometry": {
                                    "type": "Polygon",
                                    "coordinates": coords
                                },
                                "properties": {}
                            };
                        }
                //TODO: make the true project boundry available in the arches object rather than just the hexBinBounds
                //and use the true bounds here instead of the hexBinBounds.
                var extent = arches.hexBinBounds
                var coords = [[
                    [extent[0], extent[1]],
                    [extent[2], extent[1]],
                    [extent[2], extent[3]],
                    [extent[0], extent[3]],
                    [extent[0], extent[1]]
                ]]
                fc.features.push(geomFactory(coords));
                result = fc;
            }
            return result;
        },

        parse: function(source) {
            var self = this;
            self._mobilesurvey(JSON.stringify(source));
            self.name(source.name);
            self.description(source.description);
            self.startdate(source.startdate);
            self.enddate(source.enddate);
            self.active(source.active);
            self.createdby(source.createdby_id);
            self.lasteditedby(source.lasteditedby_id);
            self.groups(source.groups);
            self.users(source.users);
            self.cards(source.cards);
            self.tilecache(source.tilecache);
            self.bounds(self.getDefaultBounds(source.bounds));
            self.set('id', source.id);
        },

        reset: function() {
            this.parse(JSON.parse(this._mobilesurvey()), self);
        },

        _getURL: function(method) {
            return this.url;
        },

        save: function(userCallback, scope) {
            var self = this;
            var method = "POST";
            var callback = function(request, status, model) {
                if (typeof userCallback === 'function') {
                    userCallback.call(this, request, status, model);
                }
                if (status === 'success') {
                    self.set('id', request.responseJSON.mobile_survey.id);
                    self.createdby(request.responseJSON.mobile_survey.createdby_id);
                    self.lasteditedby(request.responseJSON.mobile_survey.lasteditedby_id);
                    self.groups(request.responseJSON.mobile_survey.groups);
                    self.users(request.responseJSON.mobile_survey.users);
                    self.cards(request.responseJSON.mobile_survey.cards);
                    self.tilecache(request.responseJSON.mobile_survey.tilecache);
                    self.bounds(self.getDefaultBounds(request.responseJSON.mobile_survey.bounds));
                    self.datadownloadconfig.download(request.responseJSON.mobile_survey.datadownloadconfig.download);
                    self.datadownloadconfig.count(request.responseJSON.mobile_survey.datadownloadconfig.count);
                    self.datadownloadconfig.resources(request.responseJSON.mobile_survey.datadownloadconfig.resources);
                    self.datadownloadconfig.custom(request.responseJSON.mobile_survey.datadownloadconfig.custom);
                    this._mobilesurvey(this.json());
                }
            };
            this._doRequest({
                type: method,
                url: this._getURL(method),
                data: JSON.stringify(this.toJSON())
            }, callback, scope, 'save');
        },

        toJSON: function() {
            return JSON.parse(this.json());
        },

        update: function() {
            this.setIdentityApproval();
        }
    });
});
<commit_msg>lints<commit_after>define([
    'underscore',
    'knockout',
    'knockout-mapping',
    'models/abstract',
    'arches'
], function(_, ko, koMapping, AbstractModel, arches) {
    return AbstractModel.extend({
        url: arches.urls.mobile_survey,

        initialize: function(options) {
            var self = this;
            self.identities = options.identities || [];
            self._mobilesurvey = ko.observable('{}');
            self.name = ko.observable('');
            self.description = ko.observable('');
            self.startdate = ko.observable(null);
            self.enddate = ko.observable(null);
            self.active = ko.observable(false);
            self.createdby = ko.observable(null);
            self.lasteditedby = ko.observable(null);
            self.users = ko.observableArray([]);
            self.groups = ko.observableArray([]);
            self.showDetails = ko.observable(false);
            self.cards = ko.observableArray([]);
            self.datadownloadconfig = koMapping.fromJS(options.source.datadownloadconfig) || ko.observable();
            self.tilecache = ko.observable('');
            self.bounds = ko.observable(self.getDefaultBounds(null));
            self.collectedResources = ko.observable(false);
            self.showCustomDataDownload = ko.observable(false);

            var getUserName = function(id) {
                var user = _.find(self.identities, function(i) {
                    return i.type === 'user' && i.id === id;
                });
                return user ? user.name : '';
            };

            self.setIdentityApproval = function() {
                var groups = ko.unwrap(this.groups);
                var users = ko.unwrap(this.users);
                _.each(this.identities, function(item) {
                    item.approved(false);
                    if ((item.type === 'group' && _.contains(groups, item.id)) ||
                        (item.type === 'user' && _.contains(users, item.id))) {
                        item.approved(true);
                    }
                });
            };

            _.each(this.identities, function(item) {
                if (item.type === 'group') {
                    _.each(item.users, function(user){
                        if (!user.expanded) {
                            user.expanded = ko.observable(false);
                        }
                    });
                }
            });

            self.createdbyName = ko.computed(function() {
                return getUserName(self.createdby());
            });

            self.lasteditedbyName = ko.computed(function() {
                return getUserName(self.lasteditedby());
            });

            self.userFilter = ko.observable('');
            self.selectedUser = ko.observable();

            self.filteredUsers = ko.computed(function() {
                var filter = self.userFilter();
                this.selected = _.filter(self.identities, function(identity) {
                    return ko.unwrap(identity.selected);
                });
                var list = [];

                if (this.selected.length === 1) {
                    list = this.selected[0].users;
                    if (filter.length === 0) {
                        return list;
                    }
                }

                return _.filter(list, function(user) {
                    if (user.username.startsWith(filter)) {
                        return user;
                    }
                });

            });

            self.selectedIdentity = ko.computed(function() {
                var selected = _.filter(self.identities, function(identity) {
                    return ko.unwrap(identity.selected);
                });
                if (selected.length === 1 && selected[0].users) {
                    self.selectedUser(selected[0].users.length > 0 ? selected[0].users[0] : undefined);
                }
                return selected.length > 0 ? selected[0] : undefined;
            });

            self.approvedUserNames = ko.computed(function() {
                var names = [];
                _.each(self.identities, function(identity) {
                    if (identity.type === 'user' && identity.approved()) {
                        names.push(identity.name);
                    }
                }, this);
                return names;
            });

            self.approvedGroupNames = ko.computed(function() {
                var names = [];
                _.each(self.identities, function(identity) {
                    if (identity.type === 'group' && identity.approved()) {
                        names.push(identity.name);
                    }
                }, this);
                return names;
            });

            self.hasIdentity = function() {
                var approved = false;
                var identity = self.selectedIdentity();
                if (identity) {
                    approved = identity.approved();
                }
                return approved;
            };

            self.toggleIdentity = function() {
                var identity = self.selectedIdentity();
                if (identity) {
                    var currentIdentities = identity.type === 'user' ? self.users : self.groups;
                    if (self.hasIdentity()) {
                        currentIdentities.remove(identity.id);
                        if (identity.type === 'user') {
                            identity.approved(false);
                        } else {
                            identity.approved(false);
                            _.chain(self.identities).filter(function(id) {
                                return id.type === 'user' && _.contains(_.pluck(this.selectedIdentity().users, 'id'), id.id);
                            }, this).each(function(user) {
                                if (_.intersection(user.group_ids, self.groups()).length === 0) { // user does not belong to any accepted groups
                                    user.approved(false);
                                    self.users.remove(user.id);
                                }
                            });
                        }
                    } else {
                        if (!_.contains(currentIdentities(), identity.id)) {
                            currentIdentities.push(identity.id);
                        }
                        identity.approved(true);
                        if (identity.type === 'user') {
                            self.users.push(identity.id);
                        } else {
                            _.chain(self.identities).filter(function(id) {
                                var identity = identity;
                                return id.type === 'user';
                            }).each(function(user) {
                                if (_.intersection(user.group_ids, self.groups()).length > 0) {
                                    user.approved(true);
                                    if (!_.contains(self.users(), user.id)) {
                                        self.users.push(user.id);
                                    }
                                }
                            });
                        }
                    }
                }
            };

            self.updateResourceDownloadList = function(val){
                if (_.contains(self.datadownloadconfig.resources(), val.id)) {
                    self.datadownloadconfig.resources.remove(val.id);
                } else {
                    self.datadownloadconfig.resources.push(val.id);
                }
            };

            self.updateCards = function(cards) {
                var approvedCards = _.chain(cards)
                    .filter(function(card){
                        if (card.approved()) {
                            return card.cardid;
                        }
                    })
                    .pluck('cardid').value();
                var diff = _.difference(self.cards(), approvedCards);
                self.cards(_.union(diff, approvedCards));
            };

            self.updateApproved = function(val){
                val.item.approved(true);
                self.updateCards(val.targetParent());
            };

            self.updateUnapproved = function(val){
                val.item.approved(false);
                self.cards.remove(val.item.cardid);
            };

            self.addAllCardsByResource = function(val) {
                var resource = val.resourceList.selected();
                _.each(resource.cards(), function(card){
                    card.approved(true);
                });
                self.updateCards(resource.cards());
            };

            self.removeAllCardsByResource = function(val) {
                var resource = val.resourceList.selected();
                _.each(resource.cards(), function(card){
                    card.approved(false);
                    self.cards.remove(card.cardid);
                });
            };

            self.toggleShowDetails = function() {
                self.setIdentityApproval();
                self.showDetails(!self.showDetails());
            };

            self.parse(options.source);
            self.setIdentityApproval();

            self.json = ko.computed(function() {
                var jsObj = ko.toJS({
                    name: self.name,
                    description: self.description,
                    startdate: self.startdate,
                    enddate: self.enddate,
                    active: self.active,
                    id: self.get('id'),
                    groups: self.groups,
                    users: self.users,
                    cards: self.cards,
                    bounds: self.bounds,
                    tilecache: self.tilecache,
                    datadownloadconfig: koMapping.toJS(self.datadownloadconfig)
                });
                return JSON.stringify(_.extend(JSON.parse(self._mobilesurvey()), jsObj));
            });

            self.dirty = ko.computed(function() {
                return self.json() !== self._mobilesurvey() || !self.get('id');
            });
        },

        getDefaultBounds: function(geojson) {
            var result = geojson;
            if (!geojson) {
                var fc = {"type": "FeatureCollection", "features": []};
                var geomFactory = function(coords){
                    return { "type": "Feature",
                        "geometry": {
                            "type": "Polygon",
                            "coordinates": coords
                        },
                        "properties": {}
                    };
                };
                //TODO: make the true project boundry available in the arches object rather than just the hexBinBounds
                //and use the true bounds here instead of the hexBinBounds.
                var extent = arches.hexBinBounds;
                var coords = [[
                    [extent[0], extent[1]],
                    [extent[2], extent[1]],
                    [extent[2], extent[3]],
                    [extent[0], extent[3]],
                    [extent[0], extent[1]]
                ]];
                fc.features.push(geomFactory(coords));
                result = fc;
            }
            return result;
        },

        parse: function(source) {
            var self = this;
            self._mobilesurvey(JSON.stringify(source));
            self.name(source.name);
            self.description(source.description);
            self.startdate(source.startdate);
            self.enddate(source.enddate);
            self.active(source.active);
            self.createdby(source.createdby_id);
            self.lasteditedby(source.lasteditedby_id);
            self.groups(source.groups);
            self.users(source.users);
            self.cards(source.cards);
            self.tilecache(source.tilecache);
            self.bounds(self.getDefaultBounds(source.bounds));
            self.set('id', source.id);
        },

        reset: function() {
            this.parse(JSON.parse(this._mobilesurvey()), self);
        },

        _getURL: function(method) {
            return this.url;
        },

        save: function(userCallback, scope) {
            var self = this;
            var method = "POST";
            var callback = function(request, status, model) {
                if (typeof userCallback === 'function') {
                    userCallback.call(this, request, status, model);
                }
                if (status === 'success') {
                    self.set('id', request.responseJSON.mobile_survey.id);
                    self.createdby(request.responseJSON.mobile_survey.createdby_id);
                    self.lasteditedby(request.responseJSON.mobile_survey.lasteditedby_id);
                    self.groups(request.responseJSON.mobile_survey.groups);
                    self.users(request.responseJSON.mobile_survey.users);
                    self.cards(request.responseJSON.mobile_survey.cards);
                    self.tilecache(request.responseJSON.mobile_survey.tilecache);
                    self.bounds(self.getDefaultBounds(request.responseJSON.mobile_survey.bounds));
                    self.datadownloadconfig.download(request.responseJSON.mobile_survey.datadownloadconfig.download);
                    self.datadownloadconfig.count(request.responseJSON.mobile_survey.datadownloadconfig.count);
                    self.datadownloadconfig.resources(request.responseJSON.mobile_survey.datadownloadconfig.resources);
                    self.datadownloadconfig.custom(request.responseJSON.mobile_survey.datadownloadconfig.custom);
                    this._mobilesurvey(this.json());
                }
            };
            this._doRequest({
                type: method,
                url: this._getURL(method),
                data: JSON.stringify(this.toJSON())
            }, callback, scope, 'save');
        },

        toJSON: function() {
            return JSON.parse(this.json());
        },

        update: function() {
            this.setIdentityApproval();
        }
    });
});
<|endoftext|>


<commit_before>var express = require('express')
  , passport = require('passport')
  , util = require('util')
  , GoogleStrategy = require('passport-google-oauth').OAuth2Strategy
  , path = require('path')
  , socketio = require('socket.io')
  , db = require('./lib/frederickNorthDB');

passport.serializeUser(function(user, done) {
  done(null, user);
});

passport.deserializeUser(function(obj, done) {
  done(null, obj);
});

exports.listen = function(server) {
  var io = socketio.listen(server);
  io.set('log level', 1);
  io.sockets.on('connection', function(socket) {
    socket.on('watching', function(agent) {
      socket.emit('watching', watching(agent));
    });
  });
}

function watching(agent) {
  return {
    agent: agent,
    watching: []
  }
}

passport.use(new GoogleStrategy({
    clientID: process.env.GOOGLE_CLIENT_ID,
    clientSecret: process.env.GOOGLE_CLIENT_SECRET,
    callbackURL: 'http://' + process.env.REALM + '/auth/google/return'
  },
  function(accessToken, refereshToken, profile, done) {
    process.nextTick(function () {
      return done(null, profile);
    });
  }
));

var app = express();

app.configure(function() {
  app.set('views', path.join(__dirname, 'views'));
  app.set('view engine', 'jade');
  app.set('view options', { layout: false });
  app.use(express.logger('dev'));
  app.use(express.cookieParser());
  app.use(express.bodyParser());
  app.use(express.methodOverride());
  app.use(express.session({ secret: 'nothing to see hear' }));
  app.use(passport.initialize());
  app.use(passport.session());
  app.use(app.router);
  app.use(express.static(__dirname + '/public'));
});

app.get('/', function(req, res){
  res.render('index', { user: req.user });
});

app.get('/account', ensureAuthenticated, function(req, res){
  res.render('account', { user: req.user });
});

app.get('/login', function(req, res){
  res.render('login', { user: req.user });
});

app.get('/index', function(req, res){
  res.render('index', { user: req.user });
});

app.get('/watching', ensureAuthenticated, function(req, res) {
  db.getLeaflets(req.user.id, function(err, leaflets) {
    if (err) {
      res.render('error', {
        err: err
      });
    }
    else {
      res.render('leafletSelector', {
        user: req.user,
        leaflets: leaflets
      });
    }
  });
});

app.get('/watching/:leafletId', ensureAuthenticated, function(req, res) {
  var leafletId = req.params.leafletId;
  console.log("Requested watching list :%s for leaflet: %s", req.user.id, leafletId);
  db.getWatching(req.user.id, leafletId, function(err, rootAreas, leafletId) {
    if (err) {
      res.render('error', {
       err: err
      });
    }
    else {
      res.render('watching', {
        user: req.user,
        rootAreas: rootAreas
      });
    }
  });
});

app.get('/leaflet', function(req, res){
  res.render('leaflet', { user: req.user });
});

app.get('/election', function(req, res){
  res.render('election', { user: req.user });
});

app.get('/purchase', function(req, res){
  res.render('purchase', { user: req.user });
});

app.get('/pl/:leafletMapId', function (req, res) {
  db.getLeafletMap(leafletMapId, function(err, leafletMap) {
    if (err) {
      res.render('error', {err: err});
    }
    else {
      res.render('routeDelivered', { leafletMap: leafletMap });
    }
  });
});

app.get('/auth/google',
  passport.authenticate('google', {scope: 'profile'}));

app.get('/auth/google/return',
  passport.authenticate('google', { failureRedirect: '/login' }),
  function(req, res) {
    res.redirect('/');
  });

app.get('/logout', function(req, res){
  req.logout();
  res.redirect('/');
});

db.connect(function() {
  console.log("Launching site on port: " + process.env.SITE_PORT);
  app.listen(process.env.SITE_PORT);
});

function ensureAuthenticated(req, res, next) {
  if (req.isAuthenticated()) { return next(); }
  res.redirect('/auth/google')
}
<commit_msg>Fixed refernce to id param in /pl route<commit_after>var express = require('express')
  , passport = require('passport')
  , util = require('util')
  , GoogleStrategy = require('passport-google-oauth').OAuth2Strategy
  , path = require('path')
  , socketio = require('socket.io')
  , db = require('./lib/frederickNorthDB');

passport.serializeUser(function(user, done) {
  done(null, user);
});

passport.deserializeUser(function(obj, done) {
  done(null, obj);
});

exports.listen = function(server) {
  var io = socketio.listen(server);
  io.set('log level', 1);
  io.sockets.on('connection', function(socket) {
    socket.on('watching', function(agent) {
      socket.emit('watching', watching(agent));
    });
  });
}

function watching(agent) {
  return {
    agent: agent,
    watching: []
  }
}

passport.use(new GoogleStrategy({
    clientID: process.env.GOOGLE_CLIENT_ID,
    clientSecret: process.env.GOOGLE_CLIENT_SECRET,
    callbackURL: 'http://' + process.env.REALM + '/auth/google/return'
  },
  function(accessToken, refereshToken, profile, done) {
    process.nextTick(function () {
      return done(null, profile);
    });
  }
));

var app = express();

app.configure(function() {
  app.set('views', path.join(__dirname, 'views'));
  app.set('view engine', 'jade');
  app.set('view options', { layout: false });
  app.use(express.logger('dev'));
  app.use(express.cookieParser());
  app.use(express.bodyParser());
  app.use(express.methodOverride());
  app.use(express.session({ secret: 'nothing to see hear' }));
  app.use(passport.initialize());
  app.use(passport.session());
  app.use(app.router);
  app.use(express.static(__dirname + '/public'));
});

app.get('/', function(req, res){
  res.render('index', { user: req.user });
});

app.get('/account', ensureAuthenticated, function(req, res){
  res.render('account', { user: req.user });
});

app.get('/login', function(req, res){
  res.render('login', { user: req.user });
});

app.get('/index', function(req, res){
  res.render('index', { user: req.user });
});

app.get('/watching', ensureAuthenticated, function(req, res) {
  db.getLeaflets(req.user.id, function(err, leaflets) {
    if (err) {
      res.render('error', {
        err: err
      });
    }
    else {
      res.render('leafletSelector', {
        user: req.user,
        leaflets: leaflets
      });
    }
  });
});

app.get('/watching/:leafletId', ensureAuthenticated, function(req, res) {
  var leafletId = req.params.leafletId;
  console.log("Requested watching list :%s for leaflet: %s", req.user.id, leafletId);
  db.getWatching(req.user.id, leafletId, function(err, rootAreas, leafletId) {
    if (err) {
      res.render('error', {
       err: err
      });
    }
    else {
      res.render('watching', {
        user: req.user,
        rootAreas: rootAreas
      });
    }
  });
});

app.get('/leaflet', function(req, res){
  res.render('leaflet', { user: req.user });
});

app.get('/election', function(req, res){
  res.render('election', { user: req.user });
});

app.get('/purchase', function(req, res){
  res.render('purchase', { user: req.user });
});

app.get('/pl/:leafletMapId', function (req, res) {
  db.getLeafletMap(req.params.leafletMapId, function(err, leafletMap) {
    if (err) {
      res.render('error', {err: err});
    }
    else {
      res.render('routeDelivered', { leafletMap: leafletMap });
    }
  });
});

app.get('/auth/google',
  passport.authenticate('google', {scope: 'profile'}));

app.get('/auth/google/return',
  passport.authenticate('google', { failureRedirect: '/login' }),
  function(req, res) {
    res.redirect('/');
  });

app.get('/logout', function(req, res){
  req.logout();
  res.redirect('/');
});

db.connect(function() {
  console.log("Launching site on port: " + process.env.SITE_PORT);
  app.listen(process.env.SITE_PORT);
});

function ensureAuthenticated(req, res, next) {
  if (req.isAuthenticated()) { return next(); }
  res.redirect('/auth/google')
}
<|endoftext|>


<commit_before>NANO.search.customize("each", "tickets", function(ticket_proto){
  ticket_proto.current_info = function(){
    var filter = this.tickets.filtration_params;
    var gates  = [];
    if(filter && filter.gates){
      var gates_filter = filter.gates;
      _.each(this.gates(), function(gate){
        if(gates_filter[gate.id]){
          gates.push(gate);
        }
      });
    }else{
      gates = this.gates();
    }
    return {
      gates: gates,
      price: gates[0].default_currency_price.value
    };
  };
  ticket_proto.filter_data = function(){
    var direct_flights = this.direct_flights;
    var return_flights = this.return_flights;
    var get_flights_data = function(func_field){
      var  results = [direct_flights[func_field]()];
      if(return_flights){
        results.push(return_flights[func_field]());
      }
      return _.flatten(results);
    };
    var filter_data = {
      direct_flight_time: direct_flights.depart_timestamp(),
      return_flight_time: return_flights ? return_flights.depart_timestamp() : null,
      flights_duration: get_flights_data("duration"),
      stops_airports: get_flights_data("stops_iatas"),
      stops_count: get_flights_data("stops_count"),
      stops_duration: get_flights_data("stops_durations"),
      airlines: get_flights_data("airlines"),
      origin: this.direct_flights.origin(),
      destination: this.direct_flights.destination()
    };

    this.filter_data = function(){
      var current_info  = this.current_info();
      filter_data.price = current_info.price;
      filter_data.gates = _.map(current_info.gates, function(gate){
        return gate.id;
      });
      return filter_data;
    };
    return this.filter_data();
  };
});

NANO.search.customize("collection", "tickets", function(proto){
  var utils = {
    strict_check_list_param: function(list, values){
      var result = _.map(values, function(value){
        return !!list[value];
      }) || [];
      return !_.include(result, false);
    },
    check_list_param: function(list, values){
      var result = _.detect(values, function(value){
        return !!list[value];
      });
      return !_.isUndefined(result);
    },
    is_in_list: function(list, value){
      return list[value] || false;
    },
    is_in_range: function(range, value){
      var number_value = +value;
      return _.isNumber(number_value) ? (range.min <= value) && (value <= range.max) : false;
    },
    is_includes_range: function(range, edges){
      return utils.is_in_range(range, edges[0]) && utils.is_in_range(range, edges[1]);
    }
  };
  var filter = {
    apply: function(tickets, filters){
      var self = this;
      var matchers = this.matchers;
      var failed_filter = null;
      var ticket_filter_data = null;
      return _.select(tickets, function(ticket){
        ticket_filter_data = ticket.filter_data();
        failed_filter = _.detect(filters, function(state, name){
          return matchers[name] && !matchers[name](state, ticket_filter_data[name]);
        }) || null;
        return failed_filter === null;
      });
    },
    matchers: {
      price: utils.is_in_range,
      direct_flight_time: utils.is_in_range,
      return_flight_time: utils.is_in_range,
      flights_duration: utils.is_includes_range,
      stops_duration: function(ticket_stops_durations, filter_stops_duration){
        var match = false;
        if(_.isEmpty(_.compact(ticket_stops_durations))){
          return true;
        }
        var matches = [];
        _.each(ticket_stops_durations, function(index, duration){
          matches.push(utils.is_in_range(filter_stops_duration, duration));
        });
        return !_.include(matches, false);
      },
      stops_count: utils.check_list_param,
      // alliances: utils.check_list_param,
      airlines: utils.strict_check_list_param,
      gates: utils.check_list_param,
      origin: utils.is_in_list,
      destination: utils.is_in_list,
      stops_airports: function(filter_airports, ticket_airports){
        return ticket_airports.length !== 0 ? utils.strict_check_list_param(filter_airports, ticket_airports) : true;
      }
    }
  };

  var numeric_fields = ['price', 'stops_duration', 'direct_flight_time',
    'return_flight_time', 'flights_duration'];
  var string_fields = ['origin', 'destination', 'stops_airports',
    'airlines', 'alliances', 'gates', 'stops_count'];
    var all_fields = _.keys(filter.matchers);

  var filterData = function (tickets_filter_data){
    var filter_data = {};
    var temp = {};

    _.each(all_fields, function (field){
      temp[field] = [];
    });
    _.each(tickets_filter_data, function (tickets_filter_data){
      _.each(all_fields, function (field){
        temp[field].push(tickets_filter_data[field]);
      });
    });

    _.each(all_fields, function (field){
      temp[field] = _.flatten(temp[field]);
    });

    _.each(numeric_fields, function (field){
      var min = _.min(temp[field]);
      var max = _.max(temp[field]);
      if (min !== max) {
        filter_data[field] = {'min': min, 'max': max};
      }
    });

    _.each(string_fields, function (field){
      _.each(_.uniq(temp[field]), function (value){
        if(!filter_data[field]){
          filter_data[field] = {};
        }
        filter_data[field][value] = true;
      });
    });
    return filter_data;
  };

  proto.init_stack.push(function create_filter_data(){
    this.filter_data = filterData(_.map(this.list, function(ticket){
      return ticket.filter_data();
    }));
  });

  proto._diff_filter_params = function(params){
    var filters = {};
    var filter_data = this.filter_data;
    _.each(params, function(param, name){
      filters[name] = _.extend({}, filter_data[name], param);
    });
    return filters;
  };

  proto.filter = function (filter_param) {
    var filters = this._diff_filter_params(filter_param);
    var filtered_tickets = filter.apply(this.list, filters);
    return this._self(filtered_tickets);
  };
});
<commit_msg>fixed current gates info<commit_after>NANO.search.customize("each", "tickets", function(ticket_proto){
  ticket_proto.current_info = function(){
    var filter = this.tickets.filter_state;
    var gates  = [];
    if(filter && filter.gates){
      var gates_filter = filter.gates;
      _.each(this.gates(), function(gate){
        if(gates_filter[gate.id]){
          gates.push(gate);
        }
      });
    }else{
      gates = this.gates();
    }
    return {
      gates: gates,
      price: gates[0].default_currency_price.value
    };
  };
  ticket_proto.filter_data = function(){
    var direct_flights = this.direct_flights;
    var return_flights = this.return_flights;
    var get_flights_data = function(func_field){
      var  results = [direct_flights[func_field]()];
      if(return_flights){
        results.push(return_flights[func_field]());
      }
      return _.flatten(results);
    };
    var filter_data = {
      direct_flight_time: direct_flights.depart_timestamp(),
      return_flight_time: return_flights ? return_flights.depart_timestamp() : null,
      flights_duration: get_flights_data("duration"),
      stops_airports: get_flights_data("stops_iatas"),
      stops_count: get_flights_data("stops_count"),
      stops_duration: get_flights_data("stops_durations"),
      airlines: get_flights_data("airlines"),
      origin: this.direct_flights.origin(),
      destination: this.direct_flights.destination()
    };

    this.filter_data = function(){
      var current_info  = this.current_info();
      filter_data.price = current_info.price;
      filter_data.gates = _.map(current_info.gates, function(gate){
        return gate.id;
      });
      return filter_data;
    };
    return this.filter_data();
  };
});

NANO.search.customize("collection", "tickets", function(proto){
  var utils = {
    strict_check_list_param: function(list, values){
      var result = _.map(values, function(value){
        return !!list[value];
      }) || [];
      return !_.include(result, false);
    },
    check_list_param: function(list, values){
      var result = _.detect(values, function(value){
        return !!list[value];
      });
      return !_.isUndefined(result);
    },
    is_in_list: function(list, value){
      return list[value] || false;
    },
    is_in_range: function(range, value){
      var number_value = +value;
      return _.isNumber(number_value) ? (range.min <= value) && (value <= range.max) : false;
    },
    is_includes_range: function(range, edges){
      return utils.is_in_range(range, edges[0]) && utils.is_in_range(range, edges[1]);
    }
  };
  var filter = {
    apply: function(tickets, filters){
      var self = this;
      var matchers = this.matchers;
      var failed_filter = null;
      var ticket_filter_data = null;
      return _.select(tickets, function(ticket){
        ticket_filter_data = ticket.filter_data();
        failed_filter = _.detect(filters, function(state, name){
          return matchers[name] && !matchers[name](state, ticket_filter_data[name]);
        }) || null;
        return failed_filter === null;
      });
    },
    matchers: {
      price: utils.is_in_range,
      direct_flight_time: utils.is_in_range,
      return_flight_time: utils.is_in_range,
      flights_duration: utils.is_includes_range,
      stops_duration: function(ticket_stops_durations, filter_stops_duration){
        var match = false;
        if(_.isEmpty(_.compact(ticket_stops_durations))){
          return true;
        }
        var matches = [];
        _.each(ticket_stops_durations, function(index, duration){
          matches.push(utils.is_in_range(filter_stops_duration, duration));
        });
        return !_.include(matches, false);
      },
      stops_count: utils.check_list_param,
      // alliances: utils.check_list_param,
      airlines: utils.strict_check_list_param,
      gates: utils.check_list_param,
      origin: utils.is_in_list,
      destination: utils.is_in_list,
      stops_airports: function(filter_airports, ticket_airports){
        return ticket_airports.length !== 0 ? utils.strict_check_list_param(filter_airports, ticket_airports) : true;
      }
    }
  };

  var numeric_fields = ['price', 'stops_duration', 'direct_flight_time',
    'return_flight_time', 'flights_duration'];
  var string_fields = ['origin', 'destination', 'stops_airports',
    'airlines', 'alliances', 'gates', 'stops_count'];
    var all_fields = _.keys(filter.matchers);

  var filterData = function (tickets_filter_data){
    var filter_data = {};
    var temp = {};

    _.each(all_fields, function (field){
      temp[field] = [];
    });
    _.each(tickets_filter_data, function (tickets_filter_data){
      _.each(all_fields, function (field){
        temp[field].push(tickets_filter_data[field]);
      });
    });

    _.each(all_fields, function (field){
      temp[field] = _.flatten(temp[field]);
    });

    _.each(numeric_fields, function (field){
      var min = _.min(temp[field]);
      var max = _.max(temp[field]);
      if (min !== max) {
        filter_data[field] = {'min': min, 'max': max};
      }
    });

    _.each(string_fields, function (field){
      _.each(_.uniq(temp[field]), function (value){
        if(!filter_data[field]){
          filter_data[field] = {};
        }
        filter_data[field][value] = true;
      });
    });
    return filter_data;
  };

  proto.init_stack.push(function create_filter_data(){
    this.filter_data = filterData(_.map(this.list, function(ticket){
      return ticket.filter_data();
    }));
  });

  proto._diff_filter_params = function(params){
    var filters = {};
    var filter_data = this.filter_data;
    _.each(params, function(param, name){
      filters[name] = _.extend({}, filter_data[name], param);
    });
    return filters;
  };

  proto.filter = function (filter_param) {
    var filters = this._diff_filter_params(filter_param);
    var filtered_tickets = filter.apply(this.list, filters);
    var tickets = this._self(filtered_tickets, {
      filter_state: filters
    });
    return tickets;
  };
});
<|endoftext|>


<commit_before>/*
 * This program is part of the OpenLMIS logistics management information system platform software.
 * Copyright © 2017 VillageReach
 *
 * This program is free software: you can redistribute it and/or modify it under the terms
 * of the GNU Affero General Public License as published by the Free Software Foundation, either
 * version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Affero General Public License for more details. You should have received a copy of
 * the GNU Affero General Public License along with this program. If not, see
 * http://www.gnu.org/licenses.  For additional information contact info@OpenLMIS.org.
 */

(function() {

    'use strict';

    /**
     * @ngdoc controller
     * @name requisition-batch-approval.controller:RequisitionBatchApprovalController
     *
     * @description
     * Controller for approval list of requisitions.
     */
    angular
        .module('requisition-batch-approval')
        .controller('RequisitionBatchApprovalController', controller);

    controller.$inject = [
        'requisitions', 'calculationFactory', 'stateTrackerService', 'loadingModalService', 'messageService',
            'alertService', 'confirmService', 'notificationService', 'requisitionBatchSaveFactory',
            'requisitionBatchApproveFactory', 'offlineService', 'RequisitionWatcher', '$scope', '$filter', 'REQUISITION_STATUS',
            'localStorageFactory', '$state', '$stateParams', 'requisitionBatchDisplayFactory'
    ];

    function controller(requisitions, calculationFactory, stateTrackerService, loadingModalService,
                        messageService, alertService, confirmService, notificationService, requisitionBatchSaveFactory,
                        requisitionBatchApproveFactory, offlineService, RequisitionWatcher, $scope, $filter, REQUISITION_STATUS,
                        localStorageFactory, $state, $stateParams, requisitionBatchDisplayFactory) {

        var vm = this;

        vm.$onInit = onInit;
        vm.updateLineItem = updateLineItem;
        vm.revert = revert;
        vm.sync = sync;
        vm.approve = approve;
        vm.updateRequisitions = updateRequisitions;
        vm.areRequisitionsOutdated = areRequisitionsOutdated;
        vm.isOffline = offlineService.isOffline;

        /**
         * @ngdoc property
         * @propertyOf requisition-batch-approval.controller:RequisitionBatchApprovalController
         * @name requisitions
         * @type {Array}
         *
         * @description
         * Holds requisitions that can be approved on the view.
         */
        vm.requisitions = undefined;

        /**
         * @ngdoc property
         * @propertyOf requisition-batch-approval.controller:RequisitionBatchApprovalController
         * @name errors
         * @type {Array}
         *
         * @description
         * Keeps a list of all current errors on the view.
         */
        vm.errors = [];

        /**
         * @ngdoc property
         * @propertyOf requisition-batch-approval.controller:RequisitionBatchApprovalController
         * @name products
         * @type {Array}
         *
         * @description
         * Holds all products that should be displayed in the table. Each product contains code, name, information
         * about total cost and quantity across all facilities,
         * and an ID array of requisitions that contain line item for this product.
         */
        vm.products = undefined;

        /**
         * @ngdoc property
         * @propertyOf requisition-batch-approval.controller:RequisitionBatchApprovalController
         * @name lineItems
         * @type {Array}
         *
         * @description
         * Holds all line items from all requisitions - each line item is identified by requisition id and product id.
         */
        vm.lineItems = undefined;

        /**
         * @ngdoc property
         * @propertyOf requisition-batch-approval.controller:RequisitionBatchApprovalController
         * @name totalCost
         * @type {Number}
         *
         * @description
         * Holds total cost of all products from all requisitions.
         */
        vm.totalCost = undefined;

        /**
         * @ngdoc property
         * @propertyOf requisition-batch-approval.controller:RequisitionBatchApprovalController
         * @name requisitionsCopy
         * @type {Array}
         *
         * @description
         * Holds copy of original requisitions that can be approved on the view.
         * It is used to provide 'revert' functionality.
         */
        vm.requisitionsCopy = undefined;

        /**
         * @ngdoc property
         * @propertyOf requisition-batch-approval.controller:RequisitionBatchApprovalController
         * @name columns
         * @type {Array}
         *
         * @description
         * Holds columns that will be displayed in the batch approval table.
         */
        vm.columns = [];

        /**
         * @ngdoc method
         * @methodOf requisition-batch-approval.controller:RequisitionBatchApprovalController
         * @name $onInit
         *
         * @description
         * Initialization method called after the controller has been created. Responsible for
         * setting data to be available on the view.
         */
        function onInit() {
            prepareDataToDisplay(requisitions);
        }

        /**
         * @ngdoc method
         * @methodOf requisition-batch-approval.controller:RequisitionBatchApprovalController
         * @name updateLineItem
         *
         * @description
         * Updates cost of line item, total cost of each product for all facilities
         * and total cost of all products for all facilities.
         */
        function updateLineItem(lineItem, requisition) {
            lineItem.totalCost = calculationFactory.totalCost(lineItem, requisition);
            updateTotalValues(lineItem.orderable.id);
            requisition.$totalCost = requisitionBatchDisplayFactory.calculateRequisitionTotalCost(requisition);
        }

        /**
         * @ngdoc method
         * @methodOf requisition-batch-approval.controller:RequisitionBatchApprovalController
         * @name revert
         *
         * @description
         * Replaces all values manually entered by user with the values the page displayed when originally loaded.
         */
        function revert() {
            confirmService.confirm('requisitionBatchApproval.revertConfirm', 'requisitionBatchApproval.revert').then(function() {
                prepareDataToDisplay(vm.requisitionsCopy);
            });

        }

        /**
         * @ngdoc method
         * @methodOf requisition-batch-approval.controller:RequisitionBatchApprovalController
         * @name sync
         *
         * @description
         * Responsible for syncing requisitions with the server.
         */
        function sync() {
            loadingModalService.open();

            requisitionBatchSaveFactory.saveRequisitions(vm.requisitions)
            .then(function(savedRequisitions){
                //successfully saved all requisitions
                var successMessage = messageService.get("requisitionBatchApproval.syncSuccess", {
                    successCount: savedRequisitions.length
                });

                $stateParams.errors = {};
                prepareDataToDisplay(savedRequisitions);
                notificationService.success(successMessage);

            }, function(savedRequisitions) {
                //at least one requisition failed to save
                var errors = {},
                    successCount = savedRequisitions ? savedRequisitions.length : 0,
                    errorMessage = messageService.get("requisitionBatchApproval.syncError", {
                        errorCount: vm.requisitions.length - successCount
                    });

                angular.forEach(vm.requisitions, function(requisition){
                    var savedRequisition = $filter('filter')(savedRequisitions, {id: requisition.id});
                    if(savedRequisition[0] !== undefined) { // if successful requisition
                        requisition = savedRequisition[0];
                    } else {
                        errors[requisition.id] = requisition.$error;
                    }
                });

                notificationService.error(errorMessage);
                $state.go($state.current, {errors: errors}, {reload: true});
            })
            .finally(loadingModalService.close);
        }

        /**
         * @ngdoc method
         * @methodOf requisition-batch-approval.controller:RequisitionBatchApprovalController
         * @name approve
         *
         * @description
         * Approves all displayed requisitions.
         */
        function approve() {
            confirmService.confirm('requisitionBatchApproval.approvalConfirm').then(function(){
                loadingModalService.open();

                // Using slice to make copy of array, so scope changes at end only
                requisitionBatchApproveFactory.batchApprove(vm.requisitions.slice())
                .then(handleApprove, handleApprove)
                .catch(function() {
                    loadingModalService.close();
                });
            });
        }

        /**
         * @ngdoc method
         * @methodOf requisition-batch-approval.controller:RequisitionBatchApprovalController
         * @name updateRequisitions
         *
         * @description
         * After confirming with the user, the outdated offline requisitions are removed,
         * and the state is reloaded. This will fetch a fresh version of the
         * requisitions.
         *
         * If the browser is offline, an error will be thrown, and nothing will
         * change.
         *
         */
        function updateRequisitions() {
            if(vm.isOffline()) {
                alertService.error('requisitionBatchApproval.updateOffline');
                return;
            }

            confirmService.confirm('requisitionBatchApproval.updateWarning', 'requisitionBatchApproval.update')
                .then(function(){
                    var offlineBatchRequisitions = localStorageFactory('batchApproveRequisitions'),
                        offlineRequisitions = localStorageFactory('requisitions');

                    angular.forEach(vm.requisitions, function(requisition) {
                        offlineBatchRequisitions.removeBy('id', requisition.id);
                        offlineRequisitions.removeBy('id', requisition.id)
                    });

                    $state.reload();
                });
        }

        /**
         * @ngdoc method
         * @methodOf requisition-batch-approval.controller:RequisitionBatchApprovalController
         * @name areRequisitionsOutdated
         *
         * @return {boolean} true if at least one requisition is outdated, false otherwise
         *
         * @description
         * Checks if there are outdated offline requisitions displayed on the page in
         * order to display proper warning.
         *
         */
        function areRequisitionsOutdated() {
            return $filter('filter')(vm.requisitions, {$outdated: true}).length > 0;
        }

        function handleApprove(successfulRequisitions) {

            if(successfulRequisitions.length < vm.requisitions.length) {
                var errors = {},
                    requisitionIds = [];

                vm.requisitions.forEach(function(requisition) {
                    if (!isFoundInSuccessfulRequisitions(requisition, successfulRequisitions)) {
                        requisitionIds.push(requisition.id);
                        errors[requisition.id] = messageService.get("requisitionBatchApproval.invalidRequisition");
                    }
                });

                if (successfulRequisitions.length > 0) {
                    notificationService.success(
                        messageService.get("requisitionBatchApproval.approvalSuccess", {
                            successCount: successfulRequisitions.length
                        })
                    );
                }

                notificationService.error(
                    messageService.get("requisitionBatchApproval.approvalError", {
                        errorCount: requisitionIds.length
                    })
                );

                // Reload state to display page without approved notifications and to update outdated ones
                $state.go($state.current, {errors: errors, ids: requisitionIds.join(',')}, {reload: true});

            } else {

                //All requisitions got approved, display notification and go back to approval list
                notificationService.success(
                    messageService.get("requisitionBatchApproval.approvalSuccess", {
                        successCount: successfulRequisitions.length
                    })
                );

                stateTrackerService.goToPreviousState('openlmis.requisitions.approvalList');
            }
        }

        function updateTotalValues(productId) {
            vm.products[productId].totalCost = 0;
            vm.products[productId].totalQuantity = 0;
            vm.totalCost = 0;

            angular.forEach(vm.requisitions, function(requisition) {
                angular.forEach(requisition.requisitionLineItems, function(lineItem) {
                    vm.totalCost += lineItem.totalCost;
                    if (lineItem.orderable.id === productId) {
                        vm.products[productId].totalCost += lineItem.totalCost;
                        vm.products[productId].totalQuantity += lineItem.approvedQuantity;
                    }
                });
            });
        }

        function prepareDataToDisplay(requisitions) {
            var dataToDisplay = requisitionBatchDisplayFactory.prepareDataToDisplay(requisitions);

            vm.totalCost = dataToDisplay.totalCost;
            vm.requisitions = dataToDisplay.requisitions;

            vm.products = dataToDisplay.products;
            vm.lineItems = dataToDisplay.lineItems;
            vm.errors = dataToDisplay.errors;
            vm.columns = dataToDisplay.columns;
            vm.requisitionsCopy = dataToDisplay.requisitionsCopy;

            angular.forEach(requisitions, function(requisition) {
                //set errors to be displayed (needed when state is reloaded to update outdated requisitions)
                if ($stateParams.errors[requisition.id]) {
                    requisition.$error = $stateParams.errors[requisition.id];
                }

                new RequisitionWatcher($scope, requisition, localStorageFactory('batchApproveRequisitions'));
            });
        }

        function isFoundInSuccessfulRequisitions(requisition, successfulRequisitions) {
            return successfulRequisitions.filter(function(r) {
                return r.id == requisition.id;
            }).length > 0;
        }
    }

})();
<commit_msg>OLMIS-3294 FEOLMIS-1964: Simplified loading modal call<commit_after>/*
 * This program is part of the OpenLMIS logistics management information system platform software.
 * Copyright © 2017 VillageReach
 *
 * This program is free software: you can redistribute it and/or modify it under the terms
 * of the GNU Affero General Public License as published by the Free Software Foundation, either
 * version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Affero General Public License for more details. You should have received a copy of
 * the GNU Affero General Public License along with this program. If not, see
 * http://www.gnu.org/licenses.  For additional information contact info@OpenLMIS.org.
 */

(function() {

    'use strict';

    /**
     * @ngdoc controller
     * @name requisition-batch-approval.controller:RequisitionBatchApprovalController
     *
     * @description
     * Controller for approval list of requisitions.
     */
    angular
        .module('requisition-batch-approval')
        .controller('RequisitionBatchApprovalController', controller);

    controller.$inject = [
        'requisitions', 'calculationFactory', 'stateTrackerService', 'loadingModalService', 'messageService',
            'alertService', 'confirmService', 'notificationService', 'requisitionBatchSaveFactory',
            'requisitionBatchApproveFactory', 'offlineService', 'RequisitionWatcher', '$scope', '$filter', 'REQUISITION_STATUS',
            'localStorageFactory', '$state', '$stateParams', 'requisitionBatchDisplayFactory'
    ];

    function controller(requisitions, calculationFactory, stateTrackerService, loadingModalService,
                        messageService, alertService, confirmService, notificationService, requisitionBatchSaveFactory,
                        requisitionBatchApproveFactory, offlineService, RequisitionWatcher, $scope, $filter, REQUISITION_STATUS,
                        localStorageFactory, $state, $stateParams, requisitionBatchDisplayFactory) {

        var vm = this;

        vm.$onInit = onInit;
        vm.updateLineItem = updateLineItem;
        vm.revert = revert;
        vm.sync = sync;
        vm.approve = approve;
        vm.updateRequisitions = updateRequisitions;
        vm.areRequisitionsOutdated = areRequisitionsOutdated;
        vm.isOffline = offlineService.isOffline;

        /**
         * @ngdoc property
         * @propertyOf requisition-batch-approval.controller:RequisitionBatchApprovalController
         * @name requisitions
         * @type {Array}
         *
         * @description
         * Holds requisitions that can be approved on the view.
         */
        vm.requisitions = undefined;

        /**
         * @ngdoc property
         * @propertyOf requisition-batch-approval.controller:RequisitionBatchApprovalController
         * @name errors
         * @type {Array}
         *
         * @description
         * Keeps a list of all current errors on the view.
         */
        vm.errors = [];

        /**
         * @ngdoc property
         * @propertyOf requisition-batch-approval.controller:RequisitionBatchApprovalController
         * @name products
         * @type {Array}
         *
         * @description
         * Holds all products that should be displayed in the table. Each product contains code, name, information
         * about total cost and quantity across all facilities,
         * and an ID array of requisitions that contain line item for this product.
         */
        vm.products = undefined;

        /**
         * @ngdoc property
         * @propertyOf requisition-batch-approval.controller:RequisitionBatchApprovalController
         * @name lineItems
         * @type {Array}
         *
         * @description
         * Holds all line items from all requisitions - each line item is identified by requisition id and product id.
         */
        vm.lineItems = undefined;

        /**
         * @ngdoc property
         * @propertyOf requisition-batch-approval.controller:RequisitionBatchApprovalController
         * @name totalCost
         * @type {Number}
         *
         * @description
         * Holds total cost of all products from all requisitions.
         */
        vm.totalCost = undefined;

        /**
         * @ngdoc property
         * @propertyOf requisition-batch-approval.controller:RequisitionBatchApprovalController
         * @name requisitionsCopy
         * @type {Array}
         *
         * @description
         * Holds copy of original requisitions that can be approved on the view.
         * It is used to provide 'revert' functionality.
         */
        vm.requisitionsCopy = undefined;

        /**
         * @ngdoc property
         * @propertyOf requisition-batch-approval.controller:RequisitionBatchApprovalController
         * @name columns
         * @type {Array}
         *
         * @description
         * Holds columns that will be displayed in the batch approval table.
         */
        vm.columns = [];

        /**
         * @ngdoc method
         * @methodOf requisition-batch-approval.controller:RequisitionBatchApprovalController
         * @name $onInit
         *
         * @description
         * Initialization method called after the controller has been created. Responsible for
         * setting data to be available on the view.
         */
        function onInit() {
            prepareDataToDisplay(requisitions);
        }

        /**
         * @ngdoc method
         * @methodOf requisition-batch-approval.controller:RequisitionBatchApprovalController
         * @name updateLineItem
         *
         * @description
         * Updates cost of line item, total cost of each product for all facilities
         * and total cost of all products for all facilities.
         */
        function updateLineItem(lineItem, requisition) {
            lineItem.totalCost = calculationFactory.totalCost(lineItem, requisition);
            updateTotalValues(lineItem.orderable.id);
            requisition.$totalCost = requisitionBatchDisplayFactory.calculateRequisitionTotalCost(requisition);
        }

        /**
         * @ngdoc method
         * @methodOf requisition-batch-approval.controller:RequisitionBatchApprovalController
         * @name revert
         *
         * @description
         * Replaces all values manually entered by user with the values the page displayed when originally loaded.
         */
        function revert() {
            confirmService.confirm('requisitionBatchApproval.revertConfirm', 'requisitionBatchApproval.revert').then(function() {
                prepareDataToDisplay(vm.requisitionsCopy);
            });

        }

        /**
         * @ngdoc method
         * @methodOf requisition-batch-approval.controller:RequisitionBatchApprovalController
         * @name sync
         *
         * @description
         * Responsible for syncing requisitions with the server.
         */
        function sync() {
            loadingModalService.open();

            requisitionBatchSaveFactory.saveRequisitions(vm.requisitions)
            .then(function(savedRequisitions){
                //successfully saved all requisitions
                var successMessage = messageService.get("requisitionBatchApproval.syncSuccess", {
                    successCount: savedRequisitions.length
                });

                $stateParams.errors = {};
                prepareDataToDisplay(savedRequisitions);
                notificationService.success(successMessage);

            }, function(savedRequisitions) {
                //at least one requisition failed to save
                var errors = {},
                    successCount = savedRequisitions ? savedRequisitions.length : 0,
                    errorMessage = messageService.get("requisitionBatchApproval.syncError", {
                        errorCount: vm.requisitions.length - successCount
                    });

                angular.forEach(vm.requisitions, function(requisition){
                    var savedRequisition = $filter('filter')(savedRequisitions, {id: requisition.id});
                    if(savedRequisition[0] !== undefined) { // if successful requisition
                        requisition = savedRequisition[0];
                    } else {
                        errors[requisition.id] = requisition.$error;
                    }
                });

                notificationService.error(errorMessage);
                $state.go($state.current, {errors: errors}, {reload: true});
            })
            .finally(loadingModalService.close);
        }

        /**
         * @ngdoc method
         * @methodOf requisition-batch-approval.controller:RequisitionBatchApprovalController
         * @name approve
         *
         * @description
         * Approves all displayed requisitions.
         */
        function approve() {
            confirmService.confirm('requisitionBatchApproval.approvalConfirm').then(function(){
                loadingModalService.open();

                // Using slice to make copy of array, so scope changes at end only
                requisitionBatchApproveFactory.batchApprove(vm.requisitions.slice())
                .then(handleApprove, handleApprove)
                .catch(loadingModalService.close);
            });
        }

        /**
         * @ngdoc method
         * @methodOf requisition-batch-approval.controller:RequisitionBatchApprovalController
         * @name updateRequisitions
         *
         * @description
         * After confirming with the user, the outdated offline requisitions are removed,
         * and the state is reloaded. This will fetch a fresh version of the
         * requisitions.
         *
         * If the browser is offline, an error will be thrown, and nothing will
         * change.
         *
         */
        function updateRequisitions() {
            if(vm.isOffline()) {
                alertService.error('requisitionBatchApproval.updateOffline');
                return;
            }

            confirmService.confirm('requisitionBatchApproval.updateWarning', 'requisitionBatchApproval.update')
                .then(function(){
                    var offlineBatchRequisitions = localStorageFactory('batchApproveRequisitions'),
                        offlineRequisitions = localStorageFactory('requisitions');

                    angular.forEach(vm.requisitions, function(requisition) {
                        offlineBatchRequisitions.removeBy('id', requisition.id);
                        offlineRequisitions.removeBy('id', requisition.id)
                    });

                    $state.reload();
                });
        }

        /**
         * @ngdoc method
         * @methodOf requisition-batch-approval.controller:RequisitionBatchApprovalController
         * @name areRequisitionsOutdated
         *
         * @return {boolean} true if at least one requisition is outdated, false otherwise
         *
         * @description
         * Checks if there are outdated offline requisitions displayed on the page in
         * order to display proper warning.
         *
         */
        function areRequisitionsOutdated() {
            return $filter('filter')(vm.requisitions, {$outdated: true}).length > 0;
        }

        function handleApprove(successfulRequisitions) {

            if(successfulRequisitions.length < vm.requisitions.length) {
                var errors = {},
                    requisitionIds = [];

                vm.requisitions.forEach(function(requisition) {
                    if (!isFoundInSuccessfulRequisitions(requisition, successfulRequisitions)) {
                        requisitionIds.push(requisition.id);
                        errors[requisition.id] = messageService.get("requisitionBatchApproval.invalidRequisition");
                    }
                });

                if (successfulRequisitions.length > 0) {
                    notificationService.success(
                        messageService.get("requisitionBatchApproval.approvalSuccess", {
                            successCount: successfulRequisitions.length
                        })
                    );
                }

                notificationService.error(
                    messageService.get("requisitionBatchApproval.approvalError", {
                        errorCount: requisitionIds.length
                    })
                );

                // Reload state to display page without approved notifications and to update outdated ones
                $state.go($state.current, {errors: errors, ids: requisitionIds.join(',')}, {reload: true});

            } else {

                //All requisitions got approved, display notification and go back to approval list
                notificationService.success(
                    messageService.get("requisitionBatchApproval.approvalSuccess", {
                        successCount: successfulRequisitions.length
                    })
                );

                stateTrackerService.goToPreviousState('openlmis.requisitions.approvalList');
            }
        }

        function updateTotalValues(productId) {
            vm.products[productId].totalCost = 0;
            vm.products[productId].totalQuantity = 0;
            vm.totalCost = 0;

            angular.forEach(vm.requisitions, function(requisition) {
                angular.forEach(requisition.requisitionLineItems, function(lineItem) {
                    vm.totalCost += lineItem.totalCost;
                    if (lineItem.orderable.id === productId) {
                        vm.products[productId].totalCost += lineItem.totalCost;
                        vm.products[productId].totalQuantity += lineItem.approvedQuantity;
                    }
                });
            });
        }

        function prepareDataToDisplay(requisitions) {
            var dataToDisplay = requisitionBatchDisplayFactory.prepareDataToDisplay(requisitions);

            vm.totalCost = dataToDisplay.totalCost;
            vm.requisitions = dataToDisplay.requisitions;

            vm.products = dataToDisplay.products;
            vm.lineItems = dataToDisplay.lineItems;
            vm.errors = dataToDisplay.errors;
            vm.columns = dataToDisplay.columns;
            vm.requisitionsCopy = dataToDisplay.requisitionsCopy;

            angular.forEach(requisitions, function(requisition) {
                //set errors to be displayed (needed when state is reloaded to update outdated requisitions)
                if ($stateParams.errors[requisition.id]) {
                    requisition.$error = $stateParams.errors[requisition.id];
                }

                new RequisitionWatcher($scope, requisition, localStorageFactory('batchApproveRequisitions'));
            });
        }

        function isFoundInSuccessfulRequisitions(requisition, successfulRequisitions) {
            return successfulRequisitions.filter(function(r) {
                return r.id == requisition.id;
            }).length > 0;
        }
    }

})();
<|endoftext|>


<commit_before>'use strict';

const { test } = require('ava');
const UnleashClientMetrics = require('./index');
const sinon = require('sinon');

const { EventEmitter } = require('events');

const appName = 'appName';
const instanceId = 'instanceId';

test('should work without state', (t) => {
    const store = new EventEmitter();
    const metrics = new UnleashClientMetrics(store);

    t.truthy(metrics.getAppsWithToggles());
    t.truthy(metrics.getTogglesMetrics());

    metrics.destroy();
});

test.cb('data should expire', (t) => {
    const clock = sinon.useFakeTimers();

    const store = new EventEmitter();
    const metrics = new UnleashClientMetrics(store);

    metrics.addPayload({
        appName,
        instanceId,
        bucket: {
            start: Date.now() - 2000,
            stop: Date.now() - 1000,
            toggles: {
                toggleX: {
                    yes: 123,
                    no: 0,
                },
            },
        },
    });

    let lastHourExpires = 0;
    metrics.lastHourList.on('expire', () => {
        lastHourExpires++;
    });

    let lastMinExpires = 0;
    metrics.lastMinuteList.on('expire', () => {
        lastMinExpires++;
    });

    clock.tick(60 * 1000);
    t.true(lastMinExpires === 1);
    t.true(lastHourExpires === 0);

    clock.tick(60 * 60 * 1000);
    t.true(lastMinExpires === 1);
    t.true(lastHourExpires === 1);

    sinon.restore();
    t.end();
});

test('should listen to metrics from store', t => {
    const store = new EventEmitter();
    const metrics = new UnleashClientMetrics(store);
    store.emit('metrics', {
        appName,
        instanceId,
        bucket: {
            start: new Date(),
            stop: new Date(),
            toggles: {
                toggleX: {
                    yes: 123,
                    no: 0,
                },
            },
        },
    });

    t.truthy(metrics.apps[appName].count === 123);
    t.truthy(metrics.globalCount === 123);

    t.deepEqual(metrics.getTogglesMetrics().lastHour.toggleX, { yes: 123, no: 0 });
    t.deepEqual(metrics.getTogglesMetrics().lastMinute.toggleX, { yes: 123, no: 0 });

    metrics.addPayload({
        appName,
        instanceId,
        bucket: {
            start: new Date(),
            stop: new Date(),
            toggles: {
                toggleX: {
                    yes: 10,
                    no: 10,
                },
            },
        },
    });

    t.truthy(metrics.globalCount === 143);
    t.deepEqual(metrics.getTogglesMetrics().lastHour.toggleX, { yes: 133, no: 10 });
    t.deepEqual(metrics.getTogglesMetrics().lastMinute.toggleX, { yes: 133, no: 10 });

    metrics.destroy();
});

test('should build up list of seend toggles when new metrics arrives', t => {
    const store = new EventEmitter();
    const metrics = new UnleashClientMetrics(store);
    store.emit('metrics', {
        appName,
        instanceId,
        bucket: {
            start: new Date(),
            stop: new Date(),
            toggles: {
                toggleX: {
                    yes: 123,
                    no: 0,
                },
                toggleY: {
                    yes: 50,
                    no: 50,
                },
            },
        },
    });

    const appToggles = metrics.getAppsWithToggles();
    const togglesForApp = metrics.getSeenTogglesByAppName(appName);

    t.truthy(appToggles.length === 1);
    t.truthy(appToggles[0].seenToggles.length === 2);
    t.truthy(appToggles[0].seenToggles.includes('toggleX'));
    t.truthy(appToggles[0].seenToggles.includes('toggleY'));

    t.truthy(togglesForApp.length === 2);
    t.truthy(togglesForApp.includes('toggleX'));
    t.truthy(togglesForApp.includes('toggleY'));
    metrics.destroy();
});

test('should handle a lot of toggles', t => {
    const store = new EventEmitter();
    const metrics = new UnleashClientMetrics(store);

    const toggleCounts = {};
    for (let i = 0; i < 100; i++) {
        toggleCounts[`toggle${i}`] = { yes: i, no: i };
    }

    store.emit('metrics', {
        appName,
        instanceId,
        bucket: {
            start: new Date(),
            stop: new Date(),
            toggles: toggleCounts,
        },
    });

    const seenToggles = metrics.getSeenTogglesByAppName(appName);

    t.truthy(seenToggles.length === 100);
    metrics.destroy();
});
<commit_msg>fix typ0 that broke time.now<commit_after>'use strict';

const { test } = require('ava');
const UnleashClientMetrics = require('./index');
const sinon = require('sinon');

const { EventEmitter } = require('events');

const appName = 'appName';
const instanceId = 'instanceId';

test('should work without state', (t) => {
    const store = new EventEmitter();
    const metrics = new UnleashClientMetrics(store);

    t.truthy(metrics.getAppsWithToggles());
    t.truthy(metrics.getTogglesMetrics());

    metrics.destroy();
});

test.cb('data should expire', (t) => {
    const clock = sinon.useFakeTimers();

    const store = new EventEmitter();
    const metrics = new UnleashClientMetrics(store);

    metrics.addPayload({
        appName,
        instanceId,
        bucket: {
            start: Date.now() - 2000,
            stop: Date.now() - 1000,
            toggles: {
                toggleX: {
                    yes: 123,
                    no: 0,
                },
            },
        },
    });

    let lastHourExpires = 0;
    metrics.lastHourList.on('expire', () => {
        lastHourExpires++;
    });

    let lastMinExpires = 0;
    metrics.lastMinuteList.on('expire', () => {
        lastMinExpires++;
    });

    clock.tick(60 * 1000);
    t.true(lastMinExpires === 1);
    t.true(lastHourExpires === 0);

    clock.tick(60 * 60 * 1000);
    t.true(lastMinExpires === 1);
    t.true(lastHourExpires === 1);

    clock.restore();
    t.end();
});

test('should listen to metrics from store', t => {
    const store = new EventEmitter();
    const metrics = new UnleashClientMetrics(store);
    store.emit('metrics', {
        appName,
        instanceId,
        bucket: {
            start: new Date(),
            stop: new Date(),
            toggles: {
                toggleX: {
                    yes: 123,
                    no: 0,
                },
            },
        },
    });

    t.truthy(metrics.apps[appName].count === 123);
    t.truthy(metrics.globalCount === 123);

    t.deepEqual(metrics.getTogglesMetrics().lastHour.toggleX, { yes: 123, no: 0 });
    t.deepEqual(metrics.getTogglesMetrics().lastMinute.toggleX, { yes: 123, no: 0 });

    metrics.addPayload({
        appName,
        instanceId,
        bucket: {
            start: new Date(),
            stop: new Date(),
            toggles: {
                toggleX: {
                    yes: 10,
                    no: 10,
                },
            },
        },
    });

    t.truthy(metrics.globalCount === 143);
    t.deepEqual(metrics.getTogglesMetrics().lastHour.toggleX, { yes: 133, no: 10 });
    t.deepEqual(metrics.getTogglesMetrics().lastMinute.toggleX, { yes: 133, no: 10 });

    metrics.destroy();
});

test('should build up list of seend toggles when new metrics arrives', t => {
    const store = new EventEmitter();
    const metrics = new UnleashClientMetrics(store);
    store.emit('metrics', {
        appName,
        instanceId,
        bucket: {
            start: new Date(),
            stop: new Date(),
            toggles: {
                toggleX: {
                    yes: 123,
                    no: 0,
                },
                toggleY: {
                    yes: 50,
                    no: 50,
                },
            },
        },
    });

    const appToggles = metrics.getAppsWithToggles();
    const togglesForApp = metrics.getSeenTogglesByAppName(appName);

    t.truthy(appToggles.length === 1);
    t.truthy(appToggles[0].seenToggles.length === 2);
    t.truthy(appToggles[0].seenToggles.includes('toggleX'));
    t.truthy(appToggles[0].seenToggles.includes('toggleY'));

    t.truthy(togglesForApp.length === 2);
    t.truthy(togglesForApp.includes('toggleX'));
    t.truthy(togglesForApp.includes('toggleY'));
    metrics.destroy();
});

test('should handle a lot of toggles', t => {
    const store = new EventEmitter();
    const metrics = new UnleashClientMetrics(store);

    const toggleCounts = {};
    for (let i = 0; i < 100; i++) {
        toggleCounts[`toggle${i}`] = { yes: i, no: i };
    }

    store.emit('metrics', {
        appName,
        instanceId,
        bucket: {
            start: new Date(),
            stop: new Date(),
            toggles: toggleCounts,
        },
    });

    const seenToggles = metrics.getSeenTogglesByAppName(appName);

    t.truthy(seenToggles.length === 100);
    metrics.destroy();
});
<|endoftext|>