<commit_before>var Boom = require('boom');
var server = require('server').hapi;
var config = require('config');
var log = require('server/helpers/logger');
var async = require('async');
var facebook = require('server/helpers/facebook');
var Token = require('server/auth/token');
var Fenix = require('fenixedu')(config.fenix);
var google = require('server/helpers/google');

server.method('auth.facebook', facebookAuth, {});
server.method('auth.fenix', fenixAuth, {});
server.method('auth.google', googleAuth, {});
server.method('auth.addFacebook', addFacebookAuth, {});
server.method('auth.refreshToken', refreshToken, {});

function facebookAuth(id, token, cb){
  facebook.debugToken(id, token, function(err, isValid) {
    if(err) {
      return cb(Boom.unauthorized(err));
    }

    if(!isValid) {
      return cb(Boom.unauthorized('nice try'));
    }

    server.methods.user.get({'facebook.id': id}, function(err, user){
      if(err) {
        if(!err.output || err.output.statusCode != 404) {
          log.error({err: err, facebook: id }, '[facebook-login] error getting user');
          return cb(err);
        }

        // This facebook id is not on db, let's find out who it belongs to
        return facebook.getMe(token, function(err, facebookUser) {
          if(err){
            log.error({err: err, id: id, token: token }, '[facebook-login] error retrieving user info from facebook');
            return cb(Boom.unauthorized('couldn\'t retrieve your info from facebook'));
          }

          var changedAttributes = {
            facebook: {
              id: facebookUser.id,
              token: token
            }
          };

          // If user does not exist, lets set the id, name and email
          changedAttributes.$setOnInsert = {
            id: Math.random().toString(36).substr(2,20), // generate random id
            name: facebookUser.name,
            // mail: facebookUser.email,
          };

          var filter = { name: facebookUser.name };
          if(facebookUser.email) {
            filter = { mail: facebookUser.email };
            changedAttributes.$setOnInsert.mail = facebookUser.email;
          }

          log.debug({facebookUser: facebookUser.id}, '[facebook-login] got facebook user');

          // Update the facebook details of the user with this email, ou create a new user if it does not exist
          return server.methods.user.update(filter, changedAttributes, {upsert: true}, function(err, result){
            if(err){
              log.error({user: {mail: facebookUser.email}, changedAttributes: changedAttributes }, '[facebook-login] error upserting user');
              return cb(err);
            }

            log.debug({id: result.id}, '[facebook-login] upserted user');

            return authenticate(result.id, null, cb);
          });
        });
      }

      var changedAttributes = { 'facebook.token': token };

      return authenticate(user.id, changedAttributes, cb);
    });
  });
}

function googleAuth(id, token, cb) {
  google.debugToken(id, token, function(err, isValid) {
      if(err) {
        return cb(Boom.unauthorized(err));
      }

      if(!isValid) {
        return cb(Boom.unauthorized('nice try'));
      }

      server.methods.user.get({'google.id': id}, function(err, user){
      if(err) {
        if(!err.output || err.output.statusCode != 404) {
          log.error({err: err, google: id }, '[google-login] error getting user');
          return cb(err);
        }

        // This google id is not on db, let's find out who it belongs to
        return google.getMe(id, function(err, googleUser) {
          if(err){
            log.error({err: err, id: id, token: token }, '[google-login] error retrieving user info from google');
            return cb(Boom.unauthorized('couldn\'t retrieve your info from google'));
          }

          console.log(googleUser);
          var changedAttributes = {
            google: {
              id: googleUser.id,
              img: googleUser.image.url + '0',
              token: token
            }
          };

          // If user does not exist, lets set the id, name and email
          changedAttributes.$setOnInsert = {
            id: Math.random().toString(36).substr(2,20), // generate random id
            name: googleUser.displayName,
          };

          google.getMail(token, function(err, mail) {
            if(err){
              log.error({err: err, id: id, token: token }, '[google-login] error retrieving user email from google');
              return cb(Boom.unauthorized('couldn\'t retrieve your info from google'));
            }

            var filter = { name: googleUser.name };

            if(mail) {
              filter = { mail: mail };
              changedAttributes.$setOnInsert.mail = mail;
            }

            log.debug({googleUser: googleUser.id}, '[google-login] got google user');

            // Update the facebook details of the user with this email, or create a new user if it does not exist
            return server.methods.user.update(filter, changedAttributes, {upsert: true}, function(err, result){
              if(err){
                log.error({user: {mail: mail}, changedAttributes: changedAttributes }, '[google-login] error upserting user');
                return cb(err);
              }

              log.debug({id: result.id}, '[google-login] upserted user');

              return authenticate(result.id, null, cb);
            });
          });
        });
      }

    var changedAttributes = { 'google.token': token };

    return authenticate(user.id, changedAttributes, cb);
  });

   });
}

function fenixAuth(code, cb){

  async.waterfall([

    function getAccessToken(cbAsync){
      Fenix.auth.getAccessToken(code, function(err, response, body) {

        var auth;

        if(err || !body){
          log.error({err: err, response: response.statusCode, body: body}, '[fenix-login] error getting access token');
          return cbAsync(Boom.unauthorized(body));
        }

        auth = {
          token: body.access_token, // jshint ignore:line
          refreshToken: body.refresh_token, // jshint ignore:line
          ttl: body.expires_in, // jshint ignore:line
          created: Date.now(),
        };

        cbAsync(null, auth);
      });
    },

    function getPerson(auth, cbAsync){
      Fenix.person.getPerson(auth.token, function(err, fenixUser){

        var user;
        var _auth = auth;

        if(err || !fenixUser) {
          log.error({err: err, user: fenixUser}, '[fenix-login] error getting person');
          return cbAsync(Boom.unauthorized());
        }

        _auth.id = fenixUser.username;
        user = {
          auth: _auth,
          name: fenixUser.name,
          email:{
            main: fenixUser.email,
            others: fenixUser.personalEmails.concat(fenixUser.workEmails)
          }
        };
        cbAsync(null, user);
      });
    },

    function compareUser(fenixUser, cbAsync){
      server.methods.user.get({'fenix.id': fenixUser.auth.id}, function(err, user){

        var changedAttributes = {};
        var query = {};

        if(err && (!err.output || err.output.statusCode != 404)){
          log.error({err: err, fenix: fenixUser.auth.id}, '[fenix-login] error getting user');
          return cbAsync(err);
        }
        if(user && user.id){
          changedAttributes = { fenix: fenixUser.auth };

          return authenticate(user.id, changedAttributes, cbAsync);
        }

        changedAttributes.fenix = fenixUser.auth;

        // If user does not exist, lets set the id, name and email
        changedAttributes.$setOnInsert = {
          id: Math.random().toString(36).substr(2,20), // generate random id
          name: fenixUser.name,
          mail: fenixUser.email.main,
        };

        fenixUser.email.others.push(fenixUser.email.main);

        query.mail = {$in: fenixUser.email.others};

        log.debug({fenixUser: fenixUser.id}, '[fenix-login] got fenix user');

        // Update the fenix details of the user with any this emails, ou create a new user if it does not exist
        server.methods.user.update(query, changedAttributes, {upsert: true}, function(err, result){
          if(err){
            log.error({query: query, changedAttributes: changedAttributes }, '[fenix-login] error upserting user');
            return cbAsync(err);
          }

          log.debug({id: result.id}, '[fenix-login] upserted user');

          return authenticate(result.id, null, cbAsync);
        });
      });
    }
  ], cb);
}

function authenticate(userId, changedAttributes, cb) {
  var newToken = Token.getJWT(userId);
  changedAttributes = changedAttributes || {};
  changedAttributes.$push = { bearer: newToken };

  server.methods.user.update({id: userId}, changedAttributes, function(err, result){
    if(err){
      log.error({user: userId, changedAttributes: changedAttributes }, '[login] error updating user');
      return cb(err);
    }
    log.info({user: userId}, '[login] user logged in ');
    return  cb(err, newToken);
  });
}

function addFacebookAuth(user, id, token, cb) {

  async.waterfall([
    function debugToken(cbAsync){
      facebook.debugToken(id, token, function(err, isValid) {
        if(err) {
          return cbAsync(Boom.unauthorized(err));
        }

        if(!isValid) {
          return cbAsync(Boom.unauthorized('nice try'));
        }
        cbAsync();
      });
    },
    function getUser(cbAsync){
      server.methods.user.get({'facebook.id': id}, function(err, _user){
        var filter = { id: user.id };
        var changedAttributes;
        if(err) {
          if(!err.output || err.output.statusCode != 404) {
            log.error({err: err, facebook: id }, '[facebook-login] error getting user');
            return cbAsync(err);
          }

          // This facebook id is not on db, let's find out who it belongs to
          return facebook.getMe(token, function(err, facebookUser) {
            if(err){
              log.error({err: err, id: id, token: token }, '[facebook-login] error retrieving user info from facebook');
              return cbAsync(Boom.unauthorized('couldn\'t retrieve your info from facebook'));
            }

            changedAttributes = {
              facebook: {
                id: facebookUser.id,
                token: token
              }
            };

            if(!user.mail && facebookUser.email) {
              changedAttributes.mail = facebookUser.email;
            }
            log.debug({facebookUser: facebookUser.id}, '[facebook-login] got facebook user');

            return cbAsync(null, filter, changedAttributes);
          });
        }

        // Merging achievements with the same id must be done before doing this, so does the points also
        //changedAttributes.achievements = (user.achievements || []).concat(_user.achievements);

        changedAttributes = {
          facebook: {
            id: _user.facebook.id,
            token: _user.facebook.token
          }
        };

        return cbAsync(null, filter, changedAttributes);
      });
    },
    function updateUser(filter, changedAttributes, cbAsync){
      // Update the facebook details of the user with this email, ou create a new user if it does not exist
      server.methods.user.update(filter, changedAttributes, function(err, result){
        if(err){
          log.error({user: user.id, changedAttributes: changedAttributes }, '[facebook-login] error upserting user');
          return cbAsync(err);
        }

        log.debug({id: result.id}, '[facebook-login] upserted user');

        return cbAsync(null, result);
      });
    }
  ], cb);
}

function refreshToken(user, token, refresh, cb){

  Token.verifyToken(user, refresh, true, function(err, decoded){
    if(err){
      return cb(err);
    }

    var newToken = Token.getJWT(user);
    var filter = { id: user, bearer: {$elemMatch: {refreshToken: refresh, token: token}}};
    var update = { $set: {'bearer.$': newToken}};

    server.methods.user.update(filter, update, function(err, result){
      if(err){
        log.error({user: user }, '[login] error updating user');
        return cb(Boom.unauthorized());
      }
      return  cb(err, newToken);
    });
  });
}<commit_msg>forgot remove dup user #27<commit_after>var Boom = require('boom');
var server = require('server').hapi;
var config = require('config');
var log = require('server/helpers/logger');
var async = require('async');
var facebook = require('server/helpers/facebook');
var Token = require('server/auth/token');
var Fenix = require('fenixedu')(config.fenix);
var google = require('server/helpers/google');

server.method('auth.facebook', facebookAuth, {});
server.method('auth.fenix', fenixAuth, {});
server.method('auth.google', googleAuth, {});
server.method('auth.addFacebook', addFacebookAuth, {});
server.method('auth.refreshToken', refreshToken, {});

function facebookAuth(id, token, cb){
  facebook.debugToken(id, token, function(err, isValid) {
    if(err) {
      return cb(Boom.unauthorized(err));
    }

    if(!isValid) {
      return cb(Boom.unauthorized('nice try'));
    }

    server.methods.user.get({'facebook.id': id}, function(err, user){
      if(err) {
        if(!err.output || err.output.statusCode != 404) {
          log.error({err: err, facebook: id }, '[facebook-login] error getting user');
          return cb(err);
        }

        // This facebook id is not on db, let's find out who it belongs to
        return facebook.getMe(token, function(err, facebookUser) {
          if(err){
            log.error({err: err, id: id, token: token }, '[facebook-login] error retrieving user info from facebook');
            return cb(Boom.unauthorized('couldn\'t retrieve your info from facebook'));
          }

          var changedAttributes = {
            facebook: {
              id: facebookUser.id,
              token: token
            }
          };

          // If user does not exist, lets set the id, name and email
          changedAttributes.$setOnInsert = {
            id: Math.random().toString(36).substr(2,20), // generate random id
            name: facebookUser.name,
            // mail: facebookUser.email,
          };

          var filter = { name: facebookUser.name };
          if(facebookUser.email) {
            filter = { mail: facebookUser.email };
            changedAttributes.$setOnInsert.mail = facebookUser.email;
          }

          log.debug({facebookUser: facebookUser.id}, '[facebook-login] got facebook user');

          // Update the facebook details of the user with this email, ou create a new user if it does not exist
          return server.methods.user.update(filter, changedAttributes, {upsert: true}, function(err, result){
            if(err){
              log.error({user: {mail: facebookUser.email}, changedAttributes: changedAttributes }, '[facebook-login] error upserting user');
              return cb(err);
            }

            log.debug({id: result.id}, '[facebook-login] upserted user');

            return authenticate(result.id, null, cb);
          });
        });
      }

      var changedAttributes = { 'facebook.token': token };

      return authenticate(user.id, changedAttributes, cb);
    });
  });
}

function googleAuth(id, token, cb) {
  google.debugToken(id, token, function(err, isValid) {
      if(err) {
        return cb(Boom.unauthorized(err));
      }

      if(!isValid) {
        return cb(Boom.unauthorized('nice try'));
      }

      server.methods.user.get({'google.id': id}, function(err, user){
      if(err) {
        if(!err.output || err.output.statusCode != 404) {
          log.error({err: err, google: id }, '[google-login] error getting user');
          return cb(err);
        }

        // This google id is not on db, let's find out who it belongs to
        return google.getMe(id, function(err, googleUser) {
          if(err){
            log.error({err: err, id: id, token: token }, '[google-login] error retrieving user info from google');
            return cb(Boom.unauthorized('couldn\'t retrieve your info from google'));
          }

          console.log(googleUser);
          var changedAttributes = {
            google: {
              id: googleUser.id,
              img: googleUser.image.url + '0',
              token: token
            }
          };

          // If user does not exist, lets set the id, name and email
          changedAttributes.$setOnInsert = {
            id: Math.random().toString(36).substr(2,20), // generate random id
            name: googleUser.displayName,
          };

          google.getMail(token, function(err, mail) {
            if(err){
              log.error({err: err, id: id, token: token }, '[google-login] error retrieving user email from google');
              return cb(Boom.unauthorized('couldn\'t retrieve your info from google'));
            }

            var filter = { name: googleUser.name };

            if(mail) {
              filter = { mail: mail };
              changedAttributes.$setOnInsert.mail = mail;
            }

            log.debug({googleUser: googleUser.id}, '[google-login] got google user');

            // Update the facebook details of the user with this email, or create a new user if it does not exist
            return server.methods.user.update(filter, changedAttributes, {upsert: true}, function(err, result){
              if(err){
                log.error({user: {mail: mail}, changedAttributes: changedAttributes }, '[google-login] error upserting user');
                return cb(err);
              }

              log.debug({id: result.id}, '[google-login] upserted user');

              return authenticate(result.id, null, cb);
            });
          });
        });
      }

    var changedAttributes = { 'google.token': token };

    return authenticate(user.id, changedAttributes, cb);
  });

   });
}

function fenixAuth(code, cb){

  async.waterfall([

    function getAccessToken(cbAsync){
      Fenix.auth.getAccessToken(code, function(err, response, body) {

        var auth;

        if(err || !body){
          log.error({err: err, response: response.statusCode, body: body}, '[fenix-login] error getting access token');
          return cbAsync(Boom.unauthorized(body));
        }

        auth = {
          token: body.access_token, // jshint ignore:line
          refreshToken: body.refresh_token, // jshint ignore:line
          ttl: body.expires_in, // jshint ignore:line
          created: Date.now(),
        };

        cbAsync(null, auth);
      });
    },

    function getPerson(auth, cbAsync){
      Fenix.person.getPerson(auth.token, function(err, fenixUser){

        var user;
        var _auth = auth;

        if(err || !fenixUser) {
          log.error({err: err, user: fenixUser}, '[fenix-login] error getting person');
          return cbAsync(Boom.unauthorized());
        }

        _auth.id = fenixUser.username;
        user = {
          auth: _auth,
          name: fenixUser.name,
          email:{
            main: fenixUser.email,
            others: fenixUser.personalEmails.concat(fenixUser.workEmails)
          }
        };
        cbAsync(null, user);
      });
    },

    function compareUser(fenixUser, cbAsync){
      server.methods.user.get({'fenix.id': fenixUser.auth.id}, function(err, user){

        var changedAttributes = {};
        var query = {};

        if(err && (!err.output || err.output.statusCode != 404)){
          log.error({err: err, fenix: fenixUser.auth.id}, '[fenix-login] error getting user');
          return cbAsync(err);
        }
        if(user && user.id){
          changedAttributes = { fenix: fenixUser.auth };

          return authenticate(user.id, changedAttributes, cbAsync);
        }

        changedAttributes.fenix = fenixUser.auth;

        // If user does not exist, lets set the id, name and email
        changedAttributes.$setOnInsert = {
          id: Math.random().toString(36).substr(2,20), // generate random id
          name: fenixUser.name,
          mail: fenixUser.email.main,
        };

        fenixUser.email.others.push(fenixUser.email.main);

        query.mail = {$in: fenixUser.email.others};

        log.debug({fenixUser: fenixUser.id}, '[fenix-login] got fenix user');

        // Update the fenix details of the user with any this emails, ou create a new user if it does not exist
        server.methods.user.update(query, changedAttributes, {upsert: true}, function(err, result){
          if(err){
            log.error({query: query, changedAttributes: changedAttributes }, '[fenix-login] error upserting user');
            return cbAsync(err);
          }

          log.debug({id: result.id}, '[fenix-login] upserted user');

          return authenticate(result.id, null, cbAsync);
        });
      });
    }
  ], cb);
}

function authenticate(userId, changedAttributes, cb) {
  var newToken = Token.getJWT(userId);
  changedAttributes = changedAttributes || {};
  changedAttributes.$push = { bearer: newToken };

  server.methods.user.update({id: userId}, changedAttributes, function(err, result){
    if(err){
      log.error({user: userId, changedAttributes: changedAttributes }, '[login] error updating user');
      return cb(err);
    }
    log.info({user: userId}, '[login] user logged in ');
    return  cb(err, newToken);
  });
}

function addFacebookAuth(user, id, token, cb) {

  async.waterfall([
    function debugToken(cbAsync){
      facebook.debugToken(id, token, function(err, isValid) {
        if(err) {
          return cbAsync(Boom.unauthorized(err));
        }

        if(!isValid) {
          return cbAsync(Boom.unauthorized('nice try'));
        }
        cbAsync();
      });
    },
    function getUser(cbAsync){
      server.methods.user.get({'facebook.id': id}, function(err, _user){
        var filter = { id: user.id };
        var changedAttributes;
        if(err) {
          if(!err.output || err.output.statusCode != 404) {
            log.error({err: err, facebook: id }, '[facebook-login] error getting user');
            return cbAsync(err);
          }

          // This facebook id is not on db, let's find out who it belongs to
          return facebook.getMe(token, function(err, facebookUser) {
            if(err){
              log.error({err: err, id: id, token: token }, '[facebook-login] error retrieving user info from facebook');
              return cbAsync(Boom.unauthorized('couldn\'t retrieve your info from facebook'));
            }

            changedAttributes = {
              facebook: {
                id: facebookUser.id,
                token: token
              }
            };

            if(!user.mail && facebookUser.email) {
              changedAttributes.mail = facebookUser.email;
            }
            log.debug({facebookUser: facebookUser.id}, '[facebook-login] got facebook user');

            return cbAsync(null, filter, changedAttributes);
          });
        }

        // Merging achievements with the same id must be done before doing this, so does the points also
        //changedAttributes.achievements = (user.achievements || []).concat(_user.achievements);

        changedAttributes = {
          facebook: {
            id: _user.facebook.id,
            token: _user.facebook.token
          }
        };

        server.methods.user.remove(_user.id, function(err, result){
          if(err){
            log.error({err: err, id: id, token: token, user: _user.id}, '[facebook-login] error removing dup user');
            return cbAsync(err);
          }
          return cbAsync(null, filter, changedAttributes);
        });

      });
    },
    function updateUser(filter, changedAttributes, cbAsync){
      // Update the facebook details of the user with this email, ou create a new user if it does not exist
      server.methods.user.update(filter, changedAttributes, function(err, result){
        if(err){
          log.error({user: user.id, changedAttributes: changedAttributes }, '[facebook-login] error upserting user');
          return cbAsync(err);
        }

        log.debug({id: result.id}, '[facebook-login] upserted user');

        return cbAsync(null, result);
      });
    }
  ], cb);
}

function refreshToken(user, token, refresh, cb){

  Token.verifyToken(user, refresh, true, function(err, decoded){
    if(err){
      return cb(err);
    }

    var newToken = Token.getJWT(user);
    var filter = { id: user, bearer: {$elemMatch: {refreshToken: refresh, token: token}}};
    var update = { $set: {'bearer.$': newToken}};

    server.methods.user.update(filter, update, function(err, result){
      if(err){
        log.error({user: user }, '[login] error updating user');
        return cb(Boom.unauthorized());
      }
      return  cb(err, newToken);
    });
  });
}<|endoftext|>


<commit_before>const {asyncMiddleware} = require('../utils/middleware');
const createStandardRoutes = require('./routeWorkers/standard');
const {getIn, lastIndex, lastItem, isEmpty} = require('../utils/functionalHelpers');
const formConfig = require('./config/proposedAddress');

module.exports = ({licenceService}) => (router, audited) => {

    const standard = createStandardRoutes({formConfig, licenceService, sectionName: 'proposedAddress'});

    router.get('/proposedAddress/curfewAddress/:bookingId', getAddress);
    router.get('/proposedAddress/curfewAddress/:action/:bookingId', getAddress);

    function getAddress(req, res) {

        const {bookingId, action} = req.params;
        const addresses = getAddresses(res.locals.licence);

        if (!addresses) {
            return res.render('proposedAddress/curfewAddress', {bookingId, data: []});
        }

        if (isAddingAddress(addresses, action)) {
            return res.render('proposedAddress/curfewAddress', {bookingId, data: {}});
        }

        return res.render('proposedAddress/curfewAddress', {bookingId, data: lastItem(addresses)});
    }

    router.get('/proposedAddress/curfewAddressChoice/:bookingId', asyncMiddleware(async (req, res) => {

        const {bookingId} = req.params;
        const licence = res.locals.licence;
        const data = {decision: getCurfewAddressChoice(getIn(licence, ['licence']))};
        const viewData = {data, errorObject: {}, bookingId};

        res.render('proposedAddress/curfewAddressChoice', viewData);
    }));

    router.post('/proposedAddress/curfewAddressChoice/:bookingId', audited, asyncMiddleware(async (req, res) => {

        const {bookingId} = req.params;
        const {decision} = req.body;
        const licence = res.locals.licence;

        const proposedAddress = proposedAddressContents[decision];
        const bassReferral = getBassReferralContent(decision, licence);

        await Promise.all([
            licenceService.updateSection('proposedAddress', bookingId, proposedAddress),
            licenceService.updateSection('bassReferral', bookingId, bassReferral)
        ]);

        const nextPath = formConfig.curfewAddressChoice.nextPath[decision] || `/hdc/taskList/${bookingId}`;
        res.redirect(`${nextPath}${bookingId}`);
    }));

    router.post('/proposedAddress/curfewAddress/:action/:bookingId', audited, asyncMiddleware(async (req, res) => {
        const {bookingId, action} = req.params;
        const addresses = getAddresses(res.locals.licence);

        const rawLicence = res.locals.licence;
        const userInput = req.body;

        if (isAddingAddress(addresses, action)) {

            if (isEmptySubmission(userInput)) {
                return res.redirect(`/hdc/proposedAddress/curfewAddress/${action}/${bookingId}`);
            }

            await licenceService.addAddress({
                rawLicence,
                bookingId,
                fieldMap: formConfig.curfewAddress.fields,
                userInput
            });

        } else {
            await licenceService.updateAddress({
                rawLicence,
                bookingId,
                fieldMap: formConfig.curfewAddress.fields,
                userInput,
                index: lastIndex(addresses)
            });
        }

        const nextPath = formConfig.curfewAddress.nextPath[action] || formConfig.curfewAddress.nextPath['path'];
        res.redirect(`${nextPath}${bookingId}`);
    }));

    router.get('/proposedAddress/:formName/:bookingId', asyncMiddleware(standard.get));
    router.post('/proposedAddress/:formName/:bookingId', audited, asyncMiddleware(standard.post));

    return router;
};

function getAddresses(licence) {
    return getIn(licence, ['licence', 'proposedAddress', 'curfewAddress', 'addresses']);
}

function isAddingAddress(addresses, action) {
    return isEmpty(addresses) || ['rejected', 'add'].includes(action);
}

function isEmptySubmission(userInput) {
    const {addressLine1, addressTown, postCode} = userInput.addresses[0];
    return !addressLine1 && !addressTown && !postCode;
}

function getCurfewAddressChoice(licence) {
    if (isYes(licence, ['proposedAddress', 'optOut', 'decision'])) {
        return 'OptOut';
    }

    if (isYes(licence, ['proposedAddress', 'addressProposed', 'decision'])) {
        return 'Address';
    }

    if (isYes(licence, ['bassReferral', 'bassRequest', 'bassRequested'])) {
        return 'Bass';
    }

    return null;
}

function isYes(licence, pathSegments) {
    const answer = getIn(licence, pathSegments);
    return answer && answer === 'Yes';
}

function getBassReferralContent(decision, licence) {
    const bassReferral = getIn(licence, ['licence', 'bassReferral']);
    const bassRequest = getIn(bassReferral, ['bassRequest']);
    const bassAnswer = decision === 'Bass' ? 'Yes' : 'No';

    return {...bassReferral, bassRequest: {...bassRequest, bassRequested: bassAnswer}};
}

const proposedAddressContents = {
    OptOut: {optOut: {decision: 'Yes'}, addressProposed: {decision: 'No'}},
    Address: {optOut: {decision: 'No'}, addressProposed: {decision: 'Yes'}},
    Bass: {optOut: {decision: 'No'}, addressProposed: {decision: 'No'}}
};

<commit_msg>Don't throw away previous address data<commit_after>const {asyncMiddleware} = require('../utils/middleware');
const createStandardRoutes = require('./routeWorkers/standard');
const {getIn, lastIndex, lastItem, isEmpty, mergeWithRight} = require('../utils/functionalHelpers');
const formConfig = require('./config/proposedAddress');

module.exports = ({licenceService}) => (router, audited) => {

    const standard = createStandardRoutes({formConfig, licenceService, sectionName: 'proposedAddress'});

    router.get('/proposedAddress/curfewAddress/:bookingId', getAddress);
    router.get('/proposedAddress/curfewAddress/:action/:bookingId', getAddress);

    function getAddress(req, res) {

        const {bookingId, action} = req.params;
        const addresses = getAddresses(res.locals.licence);

        if (!addresses) {
            return res.render('proposedAddress/curfewAddress', {bookingId, data: []});
        }

        if (isAddingAddress(addresses, action)) {
            return res.render('proposedAddress/curfewAddress', {bookingId, data: {}});
        }

        return res.render('proposedAddress/curfewAddress', {bookingId, data: lastItem(addresses)});
    }

    router.get('/proposedAddress/curfewAddressChoice/:bookingId', asyncMiddleware(async (req, res) => {

        const {bookingId} = req.params;
        const licence = res.locals.licence;
        const data = {decision: getCurfewAddressChoice(getIn(licence, ['licence']))};
        const viewData = {data, errorObject: {}, bookingId};

        res.render('proposedAddress/curfewAddressChoice', viewData);
    }));

    router.post('/proposedAddress/curfewAddressChoice/:bookingId', audited, asyncMiddleware(async (req, res) => {

        const {bookingId} = req.params;
        const {decision} = req.body;
        const licence = res.locals.licence;

        const bassReferral = getBassReferralContent(decision, licence);

        const proposedAddress = getIn(licence, ['licence', 'proposedAddress']);
        const newProposedAddress = mergeWithRight(proposedAddress, proposedAddressContents[decision]);

        await Promise.all([
            licenceService.updateSection('proposedAddress', bookingId, newProposedAddress),
            licenceService.updateSection('bassReferral', bookingId, bassReferral)
        ]);

        const nextPath = formConfig.curfewAddressChoice.nextPath[decision] || `/hdc/taskList/${bookingId}`;
        res.redirect(`${nextPath}${bookingId}`);
    }));

    router.post('/proposedAddress/curfewAddress/:action/:bookingId', audited, asyncMiddleware(async (req, res) => {
        const {bookingId, action} = req.params;
        const addresses = getAddresses(res.locals.licence);

        const rawLicence = res.locals.licence;
        const userInput = req.body;

        if (isAddingAddress(addresses, action)) {

            if (isEmptySubmission(userInput)) {
                return res.redirect(`/hdc/proposedAddress/curfewAddress/${action}/${bookingId}`);
            }

            await licenceService.addAddress({
                rawLicence,
                bookingId,
                fieldMap: formConfig.curfewAddress.fields,
                userInput
            });

        } else {
            await licenceService.updateAddress({
                rawLicence,
                bookingId,
                fieldMap: formConfig.curfewAddress.fields,
                userInput,
                index: lastIndex(addresses)
            });
        }

        const nextPath = formConfig.curfewAddress.nextPath[action] || formConfig.curfewAddress.nextPath['path'];
        res.redirect(`${nextPath}${bookingId}`);
    }));

    router.get('/proposedAddress/:formName/:bookingId', asyncMiddleware(standard.get));
    router.post('/proposedAddress/:formName/:bookingId', audited, asyncMiddleware(standard.post));

    return router;
};

function getAddresses(licence) {
    return getIn(licence, ['licence', 'proposedAddress', 'curfewAddress', 'addresses']);
}

function isAddingAddress(addresses, action) {
    return isEmpty(addresses) || ['rejected', 'add'].includes(action);
}

function isEmptySubmission(userInput) {
    const {addressLine1, addressTown, postCode} = userInput.addresses[0];
    return !addressLine1 && !addressTown && !postCode;
}

function getCurfewAddressChoice(licence) {
    if (isYes(licence, ['proposedAddress', 'optOut', 'decision'])) {
        return 'OptOut';
    }

    if (isYes(licence, ['proposedAddress', 'addressProposed', 'decision'])) {
        return 'Address';
    }

    if (isYes(licence, ['bassReferral', 'bassRequest', 'bassRequested'])) {
        return 'Bass';
    }

    return null;
}

function isYes(licence, pathSegments) {
    const answer = getIn(licence, pathSegments);
    return answer && answer === 'Yes';
}

function getBassReferralContent(decision, licence) {
    const bassReferral = getIn(licence, ['licence', 'bassReferral']);
    const bassRequest = getIn(bassReferral, ['bassRequest']);
    const bassAnswer = decision === 'Bass' ? 'Yes' : 'No';

    return {...bassReferral, bassRequest: {...bassRequest, bassRequested: bassAnswer}};
}

const proposedAddressContents = {
    OptOut: {optOut: {decision: 'Yes'}, addressProposed: {decision: 'No'}},
    Address: {optOut: {decision: 'No'}, addressProposed: {decision: 'Yes'}},
    Bass: {optOut: {decision: 'No'}, addressProposed: {decision: 'No'}}
};

<|endoftext|>


<commit_before>import request from 'request-promise-native';
import { actionTypes } from '../models/ticket';
import config from '../../config/env';
import emailService from './email';
import phoneService from './phone';
import ticketService from './ticket';
import userService from './user';

const ONE_MINUTE_MILLISECONDS = 60 * 1000;

function createAlert(ticket) {
  return ticket
    .populate('group')
    .execPopulate()
    .then(pTicket => pTicket.group.populate('escalationPolicy.subscribers').execPopulate())
    .then(group => generateAllPageRequests(group, ticket))
    .then(pageRequests => sendPageRequestsToQueue(pageRequests))
    .then(() => ticketService.addAction(ticket.id, actionTypes.CREATED));
}

function generateAllPageRequests(group, ticket) {
  let pageRequests = [];
  let currentDelay = 0;
  const title = ticket.metadata.title;

  for (const user of group.escalationPolicy.subscribers) {
    const userPages = generateUserPageRequests(ticket.id, user, currentDelay, title);
    pageRequests = pageRequests.concat(userPages);
    currentDelay += group.escalationPolicy.pagingIntervalInMinutes;
  }
  return pageRequests;
}

function generateUserPageRequests(ticketId, user, delay, title) {
  const userPageRequests = [];
  let currUserDelay = 0;

  for (let i = 0; i < user.devices.length; i++) {
    if (i !== 0) {
      currUserDelay += user.delays[i - 1] || config.defaultDelay;
    }

    userPageRequests.push({
      ticketId,
      userId: user.id,
      device: user.devices[i],
      delay: (delay + currUserDelay) * ONE_MINUTE_MILLISECONDS,
      title
    });
  }
  return userPageRequests;
}

function sendPageRequestsToQueue(userPageRequests) {
  const options = {
    method: 'POST',
    uri: `${config.queueHost}/${config.queuePath}`,
    body: {
      pages: userPageRequests
    },
    json: true
  };

  return request(options)
    .then((futurePages) => {
      if (!futurePages.length) {
        return;
      }
      const ticketId = futurePages[0].data.ticketId;
      const pageIds = futurePages.map(page => page.id);
      ticketService.updateTicket(ticketId, { pageIds });
    });
}

function sendPage(ticketId, userId, device) {
  ticketService.addAction(ticketId, actionTypes.PAGE_SENT, userId, device);

  const userPromise = userService.getUser(userId);
  const ticketPromise = ticketService.getById(ticketId);

  return Promise.all([userPromise, ticketPromise])
    .then((results) => {
      const user = results[0];
      const ticket = results[1];

      switch (device.type) {
        case ('email'):
          return emailService.sendEmail(ticket, user, device);
        case ('sms'):
          return phoneService.sendMessage(ticket, user, device);
        case ('phone'):
          return phoneService.makeCall(ticket, user, device);
        default:
          return Promise.reject(new Error(`Invalid device type: ${device.type} on User: "${user.id}"`));
      }
    });
}

export default {
  createAlert,
  sendPage,
  generateUserPageRequests
};
<commit_msg>Only save action on success<commit_after>import request from 'request-promise-native';
import { actionTypes } from '../models/ticket';
import config from '../../config/env';
import emailService from './email';
import phoneService from './phone';
import ticketService from './ticket';
import userService from './user';

const ONE_MINUTE_MILLISECONDS = 60 * 1000;

function createAlert(ticket) {
  return ticket
    .populate('group')
    .execPopulate()
    .then(pTicket => pTicket.group.populate('escalationPolicy.subscribers').execPopulate())
    .then(group => generateAllPageRequests(group, ticket))
    .then(pageRequests => sendPageRequestsToQueue(pageRequests))
    .then(() => ticketService.addAction(ticket.id, actionTypes.CREATED));
}

function generateAllPageRequests(group, ticket) {
  let pageRequests = [];
  let currentDelay = 0;
  const title = ticket.metadata.title;

  for (const user of group.escalationPolicy.subscribers) {
    const userPages = generateUserPageRequests(ticket.id, user, currentDelay, title);
    pageRequests = pageRequests.concat(userPages);
    currentDelay += group.escalationPolicy.pagingIntervalInMinutes;
  }
  return pageRequests;
}

function generateUserPageRequests(ticketId, user, delay, title) {
  const userPageRequests = [];
  let currUserDelay = 0;

  for (let i = 0; i < user.devices.length; i++) {
    if (i !== 0) {
      currUserDelay += user.delays[i - 1] || config.defaultDelay;
    }

    userPageRequests.push({
      ticketId,
      userId: user.id,
      device: user.devices[i],
      delay: (delay + currUserDelay) * ONE_MINUTE_MILLISECONDS,
      title
    });
  }
  return userPageRequests;
}

function sendPageRequestsToQueue(userPageRequests) {
  const options = {
    method: 'POST',
    uri: `${config.queueHost}/${config.queuePath}`,
    body: {
      pages: userPageRequests
    },
    json: true
  };

  return request(options)
    .then((futurePages) => {
      if (!futurePages.length) {
        return;
      }
      const ticketId = futurePages[0].data.ticketId;
      const pageIds = futurePages.map(page => page.id);
      ticketService.updateTicket(ticketId, { pageIds });
    });
}

function sendPage(ticketId, userId, device) {
  const userPromise = userService.getUser(userId);
  const ticketPromise = ticketService.getById(ticketId);

  return Promise.all([userPromise, ticketPromise])
    .then((results) => {
      const user = results[0];
      const ticket = results[1];

      switch (device.type) {
        case ('email'):
          return emailService.sendEmail(ticket, user, device);
        case ('sms'):
          return phoneService.sendMessage(ticket, user, device);
        case ('phone'):
          return phoneService.makeCall(ticket, user, device);
        default:
          return Promise.reject(new Error(`Invalid device type: ${device.type} on User: "${user.id}"`));
      }
    })
    .then(() => ticketService.addAction(ticketId, actionTypes.PAGE_SENT, userId, device));
}

export default {
  createAlert,
  sendPage,
  generateUserPageRequests
};
<|endoftext|>


<commit_before>// @flow

import apn from '@parse/node-apn';
import fcmAdmin from 'firebase-admin';
import invariant from 'invariant';

import { threadSubscriptions } from 'lib/types/subscription-types';
import { threadPermissions } from 'lib/types/thread-types';

import { dbQuery, SQL } from '../database/database';

let cachedAPNProvider = undefined;
async function getAPNProvider() {
  if (cachedAPNProvider !== undefined) {
    return cachedAPNProvider;
  }
  try {
    // $FlowFixMe
    const apnConfig = await import('../../secrets/apn_config');
    if (cachedAPNProvider === undefined) {
      cachedAPNProvider = new apn.Provider(apnConfig.default);
    }
  } catch {
    if (cachedAPNProvider === undefined) {
      cachedAPNProvider = null;
    }
  }
  return cachedAPNProvider;
}

let fcmAppInitialized = undefined;
async function initializeFCMApp() {
  if (fcmAppInitialized !== undefined) {
    return fcmAppInitialized;
  }
  try {
    // $FlowFixMe
    const fcmConfig = await import('../../secrets/fcm_config');
    if (fcmAppInitialized === undefined) {
      fcmAppInitialized = true;
      fcmAdmin.initializeApp({
        credential: fcmAdmin.credential.cert(fcmConfig.default),
      });
    }
  } catch {
    if (cachedAPNProvider === undefined) {
      fcmAppInitialized = false;
    }
  }
  return fcmAppInitialized;
}

async function terminateFirebaseAdmin() {
  const app = fcmAdmin.app();
  if (app) {
    app.delete();
  }
}

const fcmTokenInvalidationErrors = new Set([
  'messaging/registration-token-not-registered',
  'messaging/invalid-registration-token',
]);
const apnTokenInvalidationErrorCode = 410;
const apnBadRequestErrorCode = 400;
const apnBadTokenErrorString = 'BadDeviceToken';

async function apnPush(
  notification: apn.Notification,
  deviceTokens: $ReadOnlyArray<string>,
) {
  const apnProvider = await getAPNProvider();
  if (!apnProvider && process.env.NODE_ENV === 'dev') {
    console.log('no server/secrets/apn_config.json so ignoring notifs');
    return { success: true };
  }
  invariant(apnProvider, 'server/secrets/apn_config.json should exist');
  const result = await apnProvider.send(notification, deviceTokens);
  const errors = [];
  const invalidTokens = [];
  for (let error of result.failed) {
    errors.push(error);
    if (
      error.status == apnTokenInvalidationErrorCode ||
      (error.status == apnBadRequestErrorCode &&
        error.response.reason === apnBadTokenErrorString)
    ) {
      invalidTokens.push(error.device);
    }
  }
  if (invalidTokens.length > 0) {
    return { errors, invalidTokens };
  } else if (errors.length > 0) {
    return { errors };
  } else {
    return { success: true };
  }
}

async function fcmPush(
  notification: Object,
  deviceTokens: $ReadOnlyArray<string>,
  collapseKey: ?string,
) {
  const initialized = await initializeFCMApp();
  if (!initialized && process.env.NODE_ENV === 'dev') {
    console.log('no server/secrets/fcm_config.json so ignoring notifs');
    return { success: true };
  }
  invariant(initialized, 'server/secrets/fcm_config.json should exist');
  const options: Object = {
    priority: 'high',
  };
  if (collapseKey) {
    options.collapseKey = collapseKey;
  }
  // firebase-admin is extremely barebones and has a lot of missing or poorly
  // thought-out functionality. One of the issues is that if you send a
  // multicast messages and one of the device tokens is invalid, the resultant
  // won't explain which of the device tokens is invalid. So we're forced to
  // avoid the multicast functionality and call it once per deviceToken.
  const promises = [];
  for (let deviceToken of deviceTokens) {
    promises.push(fcmSinglePush(notification, deviceToken, options));
  }
  const pushResults = await Promise.all(promises);

  const errors = [];
  const ids = [];
  const invalidTokens = [];
  for (let i = 0; i < pushResults.length; i++) {
    const pushResult = pushResults[i];
    for (let error of pushResult.errors) {
      errors.push(error);
      if (fcmTokenInvalidationErrors.has(error.errorInfo.code)) {
        invalidTokens.push(deviceTokens[i]);
      }
    }
    for (let id of pushResult.fcmIDs) {
      ids.push(id);
    }
  }

  const result = {};
  if (ids.length > 0) {
    result.fcmIDs = ids;
  }
  if (errors.length > 0) {
    result.errors = errors;
  } else {
    result.success = true;
  }
  if (invalidTokens.length > 0) {
    result.invalidTokens = invalidTokens;
  }
  return result;
}

async function fcmSinglePush(
  notification: Object,
  deviceToken: string,
  options: Object,
) {
  try {
    const deliveryResult = await fcmAdmin
      .messaging()
      .sendToDevice(deviceToken, notification, options);
    const errors = [];
    const ids = [];
    for (let fcmResult of deliveryResult.results) {
      if (fcmResult.error) {
        errors.push(fcmResult.error);
      } else if (fcmResult.messageId) {
        ids.push(fcmResult.messageId);
      }
    }
    return { fcmIDs: ids, errors };
  } catch (e) {
    return { fcmIDs: [], errors: [e] };
  }
}

async function getUnreadCounts(
  userIDs: string[],
): Promise<{ [userID: string]: number }> {
  const visPermissionExtractString = `$.${threadPermissions.VISIBLE}.value`;
  const notificationExtractString = `$.${threadSubscriptions.home}`;
  const query = SQL`
    SELECT user, COUNT(thread) AS unread_count
    FROM memberships
    WHERE user IN (${userIDs}) AND last_message > last_read_message
      AND role > 0
      AND JSON_EXTRACT(permissions, ${visPermissionExtractString})
      AND JSON_EXTRACT(subscription, ${notificationExtractString})
    GROUP BY user
  `;
  const [result] = await dbQuery(query);
  const usersToUnreadCounts = {};
  for (let row of result) {
    usersToUnreadCounts[row.user.toString()] = row.unread_count;
  }
  for (let userID of userIDs) {
    if (usersToUnreadCounts[userID] === undefined) {
      usersToUnreadCounts[userID] = 0;
    }
  }
  return usersToUnreadCounts;
}

export { apnPush, fcmPush, getUnreadCounts, terminateFirebaseAdmin };
<commit_msg>[server] Avoid calling firebase.app() in terminateFirebaseAdmin<commit_after>// @flow

import apn from '@parse/node-apn';
import fcmAdmin from 'firebase-admin';
import invariant from 'invariant';

import { threadSubscriptions } from 'lib/types/subscription-types';
import { threadPermissions } from 'lib/types/thread-types';

import { dbQuery, SQL } from '../database/database';

let cachedAPNProvider = undefined;
async function getAPNProvider() {
  if (cachedAPNProvider !== undefined) {
    return cachedAPNProvider;
  }
  try {
    // $FlowFixMe
    const apnConfig = await import('../../secrets/apn_config');
    if (cachedAPNProvider === undefined) {
      cachedAPNProvider = new apn.Provider(apnConfig.default);
    }
  } catch {
    if (cachedAPNProvider === undefined) {
      cachedAPNProvider = null;
    }
  }
  return cachedAPNProvider;
}

let fcmAppInitialized = undefined;
async function initializeFCMApp() {
  if (fcmAppInitialized !== undefined) {
    return fcmAppInitialized;
  }
  try {
    // $FlowFixMe
    const fcmConfig = await import('../../secrets/fcm_config');
    if (fcmAppInitialized === undefined) {
      fcmAppInitialized = true;
      fcmAdmin.initializeApp({
        credential: fcmAdmin.credential.cert(fcmConfig.default),
      });
    }
  } catch {
    if (cachedAPNProvider === undefined) {
      fcmAppInitialized = false;
    }
  }
  return fcmAppInitialized;
}

async function terminateFirebaseAdmin() {
  fcmAdmin.apps?.forEach((app) => app?.delete());
}

const fcmTokenInvalidationErrors = new Set([
  'messaging/registration-token-not-registered',
  'messaging/invalid-registration-token',
]);
const apnTokenInvalidationErrorCode = 410;
const apnBadRequestErrorCode = 400;
const apnBadTokenErrorString = 'BadDeviceToken';

async function apnPush(
  notification: apn.Notification,
  deviceTokens: $ReadOnlyArray<string>,
) {
  const apnProvider = await getAPNProvider();
  if (!apnProvider && process.env.NODE_ENV === 'dev') {
    console.log('no server/secrets/apn_config.json so ignoring notifs');
    return { success: true };
  }
  invariant(apnProvider, 'server/secrets/apn_config.json should exist');
  const result = await apnProvider.send(notification, deviceTokens);
  const errors = [];
  const invalidTokens = [];
  for (let error of result.failed) {
    errors.push(error);
    if (
      error.status == apnTokenInvalidationErrorCode ||
      (error.status == apnBadRequestErrorCode &&
        error.response.reason === apnBadTokenErrorString)
    ) {
      invalidTokens.push(error.device);
    }
  }
  if (invalidTokens.length > 0) {
    return { errors, invalidTokens };
  } else if (errors.length > 0) {
    return { errors };
  } else {
    return { success: true };
  }
}

async function fcmPush(
  notification: Object,
  deviceTokens: $ReadOnlyArray<string>,
  collapseKey: ?string,
) {
  const initialized = await initializeFCMApp();
  if (!initialized && process.env.NODE_ENV === 'dev') {
    console.log('no server/secrets/fcm_config.json so ignoring notifs');
    return { success: true };
  }
  invariant(initialized, 'server/secrets/fcm_config.json should exist');
  const options: Object = {
    priority: 'high',
  };
  if (collapseKey) {
    options.collapseKey = collapseKey;
  }
  // firebase-admin is extremely barebones and has a lot of missing or poorly
  // thought-out functionality. One of the issues is that if you send a
  // multicast messages and one of the device tokens is invalid, the resultant
  // won't explain which of the device tokens is invalid. So we're forced to
  // avoid the multicast functionality and call it once per deviceToken.
  const promises = [];
  for (let deviceToken of deviceTokens) {
    promises.push(fcmSinglePush(notification, deviceToken, options));
  }
  const pushResults = await Promise.all(promises);

  const errors = [];
  const ids = [];
  const invalidTokens = [];
  for (let i = 0; i < pushResults.length; i++) {
    const pushResult = pushResults[i];
    for (let error of pushResult.errors) {
      errors.push(error);
      if (fcmTokenInvalidationErrors.has(error.errorInfo.code)) {
        invalidTokens.push(deviceTokens[i]);
      }
    }
    for (let id of pushResult.fcmIDs) {
      ids.push(id);
    }
  }

  const result = {};
  if (ids.length > 0) {
    result.fcmIDs = ids;
  }
  if (errors.length > 0) {
    result.errors = errors;
  } else {
    result.success = true;
  }
  if (invalidTokens.length > 0) {
    result.invalidTokens = invalidTokens;
  }
  return result;
}

async function fcmSinglePush(
  notification: Object,
  deviceToken: string,
  options: Object,
) {
  try {
    const deliveryResult = await fcmAdmin
      .messaging()
      .sendToDevice(deviceToken, notification, options);
    const errors = [];
    const ids = [];
    for (let fcmResult of deliveryResult.results) {
      if (fcmResult.error) {
        errors.push(fcmResult.error);
      } else if (fcmResult.messageId) {
        ids.push(fcmResult.messageId);
      }
    }
    return { fcmIDs: ids, errors };
  } catch (e) {
    return { fcmIDs: [], errors: [e] };
  }
}

async function getUnreadCounts(
  userIDs: string[],
): Promise<{ [userID: string]: number }> {
  const visPermissionExtractString = `$.${threadPermissions.VISIBLE}.value`;
  const notificationExtractString = `$.${threadSubscriptions.home}`;
  const query = SQL`
    SELECT user, COUNT(thread) AS unread_count
    FROM memberships
    WHERE user IN (${userIDs}) AND last_message > last_read_message
      AND role > 0
      AND JSON_EXTRACT(permissions, ${visPermissionExtractString})
      AND JSON_EXTRACT(subscription, ${notificationExtractString})
    GROUP BY user
  `;
  const [result] = await dbQuery(query);
  const usersToUnreadCounts = {};
  for (let row of result) {
    usersToUnreadCounts[row.user.toString()] = row.unread_count;
  }
  for (let userID of userIDs) {
    if (usersToUnreadCounts[userID] === undefined) {
      usersToUnreadCounts[userID] = 0;
    }
  }
  return usersToUnreadCounts;
}

export { apnPush, fcmPush, getUnreadCounts, terminateFirebaseAdmin };
<|endoftext|>


<commit_before>/*
  base.js - Javascript wrapper for REST Interface to socialfarm.org and other js that should probably go elsewhere

*/

var revision_cache = {};
var do_nothing = function(){};
var user = null;
var DEBUG = true;

function LOG(msg) {
    if (DEBUG)
        console.log(msg);
}

//needs to be fixed, currently returning a global var
function get_user() {
    return user
}

function set_user(val) {
    user = val;
}

function set_user_access_token(token) {
    if (user != null){
        user.AccessToken = token;
    } else {
        sf_login();
    }
}

function warn(msg){
    $('#alert_content').text(msg);
    $('#alert').show();
    $('#alert').focus();
}

function build_readonly_li(label, for_attr, name_attr, type_attr, value_attr){
    return '
' +
           '' + label + '' +
           '<input type="' + type_attr + '" name="' + name_attr + '" id="' + for_attr + '" value = "' + value_attr + '"readonly/>' +
           '
';
}

function build_task_json(){
	var task = Object();
	var data_items = Object();

	$.each($('#task_form #data_items li'), function(item) {
		$.each(this.children, function (child) {
			if (! $(this).is('label') && (! $(this).is('button')) ){
				data_items[jQuery.trim(''+this.id)] = jQuery.trim(''+ this.value) ;
			}
		});
	});

	$.each($('#task_form #task_info li'), function(item) {
		$.each(this.children, function (child) {
			if (! $(this).is('label') && (! $(this).is('button')) ){
				task[jQuery.trim(''+this.id)] = jQuery.trim(''+ this.value) ;
			}
		});
	});
	task.data_items = data_items;
	LOG(task);
	return task;

}

function save_task(){
	var task = build_task_json();
	var url = $('#task_form').attr("action")
	var data = JSON.stringify(task);
	post_json(url, data, null, null);
}

function forward_task(){
	var task = build_task_json();
	var url = $('#task_form').attr("action");
	task.state = 'finished';
	var data = JSON.stringify(task);
	post_json(url, data, null, null);
}

function setHeader(xhr) {
    if (user != null){
        xhr.setRequestHeader('AccessToken', user.accessToken);
    }
}

function get_json(url, successcb, failurecb){
    if (! url in revision_cache) {
        $.ajax({
            url: url,
            type: 'GET',
            dataType: 'json',
            success: function (response){ revision_cache[url] = reponse; successcb();},
            error: failurecb,
            beforeSend: setHeader
        });
    } else {
        LOG('returning cached data...');
        successcb();
    }
}

function put_json(url, data, successcb, failurecb){
        if (url in revision_cache && data == revision_cache[url]){
            // trying to put the same object
        } else {
        $.ajax({
            url: url,
            type: 'PUT',
            dataType: 'json',
            data : data,
            success: function (response){ revision_cache[url] = data; successcb();},
            error: failurecb,
            beforeSend: setHeader
        });
    }
}

function post_json(url, data, successcb, failurecb){
    if (url in revision_cache && data == revision_cache[url]){
        // trying to post the same object
    } else {
        $.ajax({
        url: url,
        type: 'POST',
        dataType: 'json',
        data : data,
        success: function (response){ revision_cache[url] = data; successcb();},
        error: failurecb,
        beforeSend: setHeader
        });
    }
}

function load_my_tasks(){
    if (user != null){
        id = user.id

        var business_tasks;
        var success = function(tasks) {
            $.each(tasks.rows, function(task){
                $(business_tasks).append('
' +  this.id + '
');
            });
        };
        var failure = function(tasks) {};

        //asynchronously load tasks via api
        $.each( $('.business_tasks') , function(index, value) {
            business_tasks = this;
            var url = "/api/business/" + this.id + "/tasks/" + id ;
            get_json(url, success, failure);
        });
    }
}

function add_user_to_socialfarm(){
    if (get_user() != null){
        var url = "/api/person/" + get_user().id
        var data = JSON.stringify({"type":"person", "businesses": Array()}) ;

        //add person to socialfarm db
        put_json(url, data, do_nothing, do_nothing);
    }

}

<commit_msg>fixing join business form<commit_after>/*
  base.js - Javascript wrapper for REST Interface to socialfarm.org and other js that should probably go elsewhere

*/

var revision_cache = {};
var do_nothing = function(){};
var user = null;
var DEBUG = true;

function LOG(msg) {
    if (DEBUG)
        console.log(msg);
}

//needs to be fixed, currently returning a global var
function get_user() {
    return user
}

function set_user(val) {
    user = val;
}

function set_user_access_token(token) {
    if (user != null){
        user.AccessToken = token;
    } else {
        sf_login();
    }
}

function warn(msg){
    $('#alert_content').text(msg);
    $('#alert').show();
    $('#alert').focus();
}

function build_readonly_li(label, for_attr, name_attr, type_attr, value_attr){
    return '
' +
           '' + label + '' +
           '<input type="' + type_attr + '" name="' + name_attr + '" id="' + for_attr + '" value = "' + value_attr + '"readonly/>' +
           '
';
}

function build_task_json(){
	var task = Object();
	var data_items = Object();

	$.each($('#task_form #data_items li'), function(item) {
		$.each(this.children, function (child) {
			if (! $(this).is('label') && (! $(this).is('button')) ){
				data_items[jQuery.trim(''+this.id)] = jQuery.trim(''+ this.value) ;
			}
		});
	});

	$.each($('#task_form #task_info li'), function(item) {
		$.each(this.children, function (child) {
			if (! $(this).is('label') && (! $(this).is('button')) ){
				task[jQuery.trim(''+this.id)] = jQuery.trim(''+ this.value) ;
			}
		});
	});
	task.data_items = data_items;
	LOG(task);
	return task;

}

function save_task(){
	var task = build_task_json();
	var url = $('#task_form').attr("action")
	var data = JSON.stringify(task);
	post_json(url, data, null, null);
}

function forward_task(){
	var task = build_task_json();
	var url = $('#task_form').attr("action");
	task.state = 'finished';
	var data = JSON.stringify(task);
	post_json(url, data, null, null);
}

function setHeader(xhr) {
    if (user != null){
        xhr.setRequestHeader('AccessToken', user.accessToken);
    }
}

function get_json(url, successcb, failurecb){
    if (! url in revision_cache) {
        $.ajax({
            url: url,
            type: 'GET',
            dataType: 'json',
            success: function (response){ revision_cache[url] = reponse; successcb();},
            error: failurecb,
            beforeSend: setHeader
        });
    } else {
        LOG('returning cached data...');
        successcb();
    }
}

function put_json(url, data, successcb, failurecb){
        if (url in revision_cache && data == revision_cache[url]){
            // trying to put the same object
        } else {
        $.ajax({
            url: url,
            type: 'PUT',
            dataType: 'json',
            data : data,
            success: function (response){ revision_cache[url] = data; successcb();},
            error: failurecb,
            beforeSend: setHeader
        });
    }
}

function post_json(url, data, successcb, failurecb){
    if (url in revision_cache && data == revision_cache[url]){
        // trying to post the same object
    } else {
        $.ajax({
        url: url,
        type: 'POST',
        dataType: 'json',
        data : data,
        success: function (response){ revision_cache[url] = data; successcb();},
        error: failurecb,
        beforeSend: setHeader
        });
    }
}

function load_my_tasks(){
    if (user != null){
        id = user.id

        var business_tasks;
        var success = function(tasks) {
            $.each(tasks.rows, function(task){
                $(business_tasks).append('
' +  this.id + '
');
            });
        };
        var failure = function(tasks) {};

        //asynchronously load tasks via api
        $.each( $('.business_tasks') , function(index, value) {
            business_tasks = this;
            var url = "/api/business/" + this.id + "/tasks/" + id ;
            get_json(url, success, failure);
        });
    }
}

function add_user_to_socialfarm(){
    if (get_user() != null){
        var url = "/api/person/" + get_user().id
        var person = Object();
        person.type = 'person';
        person.businesses =  Array()
        var data = JSON.stringify(person) ;

        //add person to socialfarm db
        put_json(url, data, do_nothing, do_nothing);
    }

}

<|endoftext|>


<commit_before>import Exception from "../exception";

export function Compiler() {}

// the foundHelper register will disambiguate helper lookup from finding a
// function in a context. This is necessary for mustache compatibility, which
// requires that context functions in blocks are evaluated by blockHelperMissing,
// and then proceed as if the resulting value was provided to blockHelperMissing.

Compiler.prototype = {
  compiler: Compiler,

  disassemble: function() {
    var opcodes = this.opcodes, opcode, out = [], params, param;

    for (var i=0, l=opcodes.length; i<l; i++) {
      opcode = opcodes[i];

      if (opcode.opcode === 'DECLARE') {
        out.push("DECLARE " + opcode.name + "=" + opcode.value);
      } else {
        params = [];
        for (var j=0; j<opcode.args.length; j++) {
          param = opcode.args[j];
          if (typeof param === "string") {
            param = "\"" + param.replace("\n", "\\n") + "\"";
          }
          params.push(param);
        }
        out.push(opcode.opcode + " " + params.join(" "));
      }
    }

    return out.join("\n");
  },

  equals: function(other) {
    var len = this.opcodes.length;
    if (other.opcodes.length !== len) {
      return false;
    }

    for (var i = 0; i < len; i++) {
      var opcode = this.opcodes[i],
          otherOpcode = other.opcodes[i];
      if (opcode.opcode !== otherOpcode.opcode || opcode.args.length !== otherOpcode.args.length) {
        return false;
      }
      for (var j = 0; j < opcode.args.length; j++) {
        if (opcode.args[j] !== otherOpcode.args[j]) {
          return false;
        }
      }
    }

    len = this.children.length;
    if (other.children.length !== len) {
      return false;
    }
    for (i = 0; i < len; i++) {
      if (!this.children[i].equals(other.children[i])) {
        return false;
      }
    }

    return true;
  },

  guid: 0,

  compile: function(program, options) {
    this.opcodes = [];
    this.children = [];
    this.depths = {list: []};
    this.options = options;
    this.stringParams = options.stringParams;

    // These changes will propagate to the other compiler components
    var knownHelpers = this.options.knownHelpers;
    this.options.knownHelpers = {
      'helperMissing': true,
      'blockHelperMissing': true,
      'each': true,
      'if': true,
      'unless': true,
      'with': true,
      'log': true
    };
    if (knownHelpers) {
      for (var name in knownHelpers) {
        this.options.knownHelpers[name] = knownHelpers[name];
      }
    }

    return this.accept(program);
  },

  accept: function(node) {
    var strip = node.strip || {},
        ret;
    if (strip.left) {
      this.opcode('strip');
    }

    ret = this[node.type](node);

    if (strip.right) {
      this.opcode('strip');
    }

    return ret;
  },

  program: function(program) {
    var statements = program.statements;

    for(var i=0, l=statements.length; i<l; i++) {
      this.accept(statements[i]);
    }
    this.isSimple = l === 1;

    this.depths.list = this.depths.list.sort(function(a, b) {
      return a - b;
    });

    return this;
  },

  compileProgram: function(program) {
    var result = new this.compiler().compile(program, this.options);
    var guid = this.guid++, depth;

    this.usePartial = this.usePartial || result.usePartial;

    this.children[guid] = result;

    for(var i=0, l=result.depths.list.length; i<l; i++) {
      depth = result.depths.list[i];

      if(depth < 2) { continue; }
      else { this.addDepth(depth - 1); }
    }

    return guid;
  },

  block: function(block) {
    var mustache = block.mustache,
        program = block.program,
        inverse = block.inverse;

    if (program) {
      program = this.compileProgram(program);
    }

    if (inverse) {
      inverse = this.compileProgram(inverse);
    }

    var sexpr = mustache.sexpr;
    var type = this.classifySexpr(sexpr);

    if (type === "helper") {
      this.helperSexpr(sexpr, program, inverse);
    } else if (type === "simple") {
      this.simpleSexpr(sexpr);

      // now that the simple mustache is resolved, we need to
      // evaluate it by executing `blockHelperMissing`
      this.opcode('pushProgram', program);
      this.opcode('pushProgram', inverse);
      this.opcode('emptyHash');
      this.opcode('blockValue');
    } else {
      this.ambiguousSexpr(sexpr, program, inverse);

      // now that the simple mustache is resolved, we need to
      // evaluate it by executing `blockHelperMissing`
      this.opcode('pushProgram', program);
      this.opcode('pushProgram', inverse);
      this.opcode('emptyHash');
      this.opcode('ambiguousBlockValue');
    }

    this.opcode('append');
  },

  hash: function(hash) {
    var pairs = hash.pairs, pair, val;

    this.opcode('pushHash');

    for(var i=0, l=pairs.length; i<l; i++) {
      pair = pairs[i];
      val  = pair[1];

      if (this.stringParams) {
        if(val.depth) {
          this.addDepth(val.depth);
        }
        this.opcode('getContext', val.depth || 0);
        this.opcode('pushStringParam', val.stringModeValue, val.type);

        if (val.type === 'sexpr') {
          // Subexpressions get evaluated and passed in
          // in string params mode.
          this.sexpr(val);
        }
      } else {
        this.accept(val);
      }

      this.opcode('assignToHash', pair[0]);
    }
    this.opcode('popHash');
  },

  partial: function(partial) {
    var partialName = partial.partialName;
    this.usePartial = true;

    if(partial.context) {
      this.ID(partial.context);
    } else {
      this.opcode('push', 'depth0');
    }

    this.opcode('invokePartial', partialName.name);
    this.opcode('append');
  },

  content: function(content) {
    this.opcode('appendContent', content.string);
  },

  mustache: function(mustache) {
    this.sexpr(mustache.sexpr);

    if(mustache.escaped && !this.options.noEscape) {
      this.opcode('appendEscaped');
    } else {
      this.opcode('append');
    }
  },

  ambiguousSexpr: function(sexpr, program, inverse) {
    var id = sexpr.id,
        name = id.parts[0],
        isBlock = program != null || inverse != null;

    this.opcode('getContext', id.depth);

    this.opcode('pushProgram', program);
    this.opcode('pushProgram', inverse);

    this.opcode('invokeAmbiguous', name, isBlock);
  },

  simpleSexpr: function(sexpr) {
    var id = sexpr.id;

    if (id.type === 'DATA') {
      this.DATA(id);
    } else if (id.parts.length) {
      this.ID(id);
    } else {
      // Simplified ID for `this`
      this.addDepth(id.depth);
      this.opcode('getContext', id.depth);
      this.opcode('pushContext');
    }

    this.opcode('resolvePossibleLambda');
  },

  helperSexpr: function(sexpr, program, inverse) {
    var params = this.setupFullMustacheParams(sexpr, program, inverse),
        name = sexpr.id.parts[0];

    if (this.options.knownHelpers[name]) {
      this.opcode('invokeKnownHelper', params.length, name);
    } else if (this.options.knownHelpersOnly) {
      throw new Exception("You specified knownHelpersOnly, but used the unknown helper " + name, sexpr);
    } else {
      this.opcode('invokeHelper', params.length, name, sexpr.isRoot);
    }
  },

  sexpr: function(sexpr) {
    var type = this.classifySexpr(sexpr);

    if (type === "simple") {
      this.simpleSexpr(sexpr);
    } else if (type === "helper") {
      this.helperSexpr(sexpr);
    } else {
      this.ambiguousSexpr(sexpr);
    }
  },

  ID: function(id) {
    this.addDepth(id.depth);
    this.opcode('getContext', id.depth);

    var name = id.parts[0];
    if (!name) {
      this.opcode('pushContext');
    } else {
      this.opcode('lookupOnContext', id.parts[0]);
    }

    for(var i=1, l=id.parts.length; i<l; i++) {
      this.opcode('lookup', id.parts[i]);
    }
  },

  DATA: function(data) {
    this.options.data = true;
    if (data.id.isScoped || data.id.depth) {
      throw new Exception('Scoped data references are not supported: ' + data.original, data);
    }

    this.opcode('lookupData');
    var parts = data.id.parts;
    for(var i=0, l=parts.length; i<l; i++) {
      this.opcode('lookup', parts[i]);
    }
  },

  STRING: function(string) {
    this.opcode('pushString', string.string);
  },

  INTEGER: function(integer) {
    this.opcode('pushLiteral', integer.integer);
  },

  BOOLEAN: function(bool) {
    this.opcode('pushLiteral', bool.bool);
  },

  comment: function() {},

  // HELPERS
  opcode: function(name) {
    this.opcodes.push({ opcode: name, args: [].slice.call(arguments, 1) });
  },

  declare: function(name, value) {
    this.opcodes.push({ opcode: 'DECLARE', name: name, value: value });
  },

  addDepth: function(depth) {
    if(depth === 0) { return; }

    if(!this.depths[depth]) {
      this.depths[depth] = true;
      this.depths.list.push(depth);
    }
  },

  classifySexpr: function(sexpr) {
    var isHelper   = sexpr.isHelper;
    var isEligible = sexpr.eligibleHelper;
    var options    = this.options;

    // if ambiguous, we can possibly resolve the ambiguity now
    // An eligible helper is one that does not have a complex path, i.e. `this.foo`, `../foo` etc.
    if (isEligible && !isHelper) {
      var name = sexpr.id.parts[0];

      if (options.knownHelpers[name]) {
        isHelper = true;
      } else if (options.knownHelpersOnly) {
        isEligible = false;
      }
    }

    if (isHelper) { return "helper"; }
    else if (isEligible) { return "ambiguous"; }
    else { return "simple"; }
  },

  pushParams: function(params) {
    var i = params.length, param;

    while(i--) {
      param = params[i];

      if(this.stringParams) {
        if(param.depth) {
          this.addDepth(param.depth);
        }

        this.opcode('getContext', param.depth || 0);
        this.opcode('pushStringParam', param.stringModeValue, param.type);

        if (param.type === 'sexpr') {
          // Subexpressions get evaluated and passed in
          // in string params mode.
          this.sexpr(param);
        }
      } else {
        this[param.type](param);
      }
    }
  },

  setupFullMustacheParams: function(sexpr, program, inverse) {
    var params = sexpr.params;
    this.pushParams(params);

    this.opcode('pushProgram', program);
    this.opcode('pushProgram', inverse);

    if (sexpr.hash) {
      this.hash(sexpr.hash);
    } else {
      this.opcode('emptyHash');
    }

    return params;
  }
};

export function precompile(input, options, env) {
  if (input == null || (typeof input !== 'string' && input.constructor !== env.AST.ProgramNode)) {
    throw new Exception("You must pass a string or Handlebars AST to Handlebars.precompile. You passed " + input);
  }

  options = options || {};
  if (!('data' in options)) {
    options.data = true;
  }

  var ast = env.parse(input);
  var environment = new env.Compiler().compile(ast, options);
  return new env.JavaScriptCompiler().compile(environment, options);
}

export function compile(input, options, env) {
  if (input == null || (typeof input !== 'string' && input.constructor !== env.AST.ProgramNode)) {
    throw new Exception("You must pass a string or Handlebars AST to Handlebars.compile. You passed " + input);
  }

  options = options || {};

  if (!('data' in options)) {
    options.data = true;
  }

  var compiled;

  function compileInput() {
    var ast = env.parse(input);
    var environment = new env.Compiler().compile(ast, options);
    var templateSpec = new env.JavaScriptCompiler().compile(environment, options, undefined, true);
    return env.template(templateSpec);
  }

  // Template is only compiled on first use and cached after that point.
  return function(context, options) {
    if (!compiled) {
      compiled = compileInput();
    }
    return compiled.call(this, context, options);
  };
}
<commit_msg>Refactor out pushParams method<commit_after>import Exception from "../exception";

export function Compiler() {}

// the foundHelper register will disambiguate helper lookup from finding a
// function in a context. This is necessary for mustache compatibility, which
// requires that context functions in blocks are evaluated by blockHelperMissing,
// and then proceed as if the resulting value was provided to blockHelperMissing.

Compiler.prototype = {
  compiler: Compiler,

  disassemble: function() {
    var opcodes = this.opcodes, opcode, out = [], params, param;

    for (var i=0, l=opcodes.length; i<l; i++) {
      opcode = opcodes[i];

      if (opcode.opcode === 'DECLARE') {
        out.push("DECLARE " + opcode.name + "=" + opcode.value);
      } else {
        params = [];
        for (var j=0; j<opcode.args.length; j++) {
          param = opcode.args[j];
          if (typeof param === "string") {
            param = "\"" + param.replace("\n", "\\n") + "\"";
          }
          params.push(param);
        }
        out.push(opcode.opcode + " " + params.join(" "));
      }
    }

    return out.join("\n");
  },

  equals: function(other) {
    var len = this.opcodes.length;
    if (other.opcodes.length !== len) {
      return false;
    }

    for (var i = 0; i < len; i++) {
      var opcode = this.opcodes[i],
          otherOpcode = other.opcodes[i];
      if (opcode.opcode !== otherOpcode.opcode || opcode.args.length !== otherOpcode.args.length) {
        return false;
      }
      for (var j = 0; j < opcode.args.length; j++) {
        if (opcode.args[j] !== otherOpcode.args[j]) {
          return false;
        }
      }
    }

    len = this.children.length;
    if (other.children.length !== len) {
      return false;
    }
    for (i = 0; i < len; i++) {
      if (!this.children[i].equals(other.children[i])) {
        return false;
      }
    }

    return true;
  },

  guid: 0,

  compile: function(program, options) {
    this.opcodes = [];
    this.children = [];
    this.depths = {list: []};
    this.options = options;
    this.stringParams = options.stringParams;

    // These changes will propagate to the other compiler components
    var knownHelpers = this.options.knownHelpers;
    this.options.knownHelpers = {
      'helperMissing': true,
      'blockHelperMissing': true,
      'each': true,
      'if': true,
      'unless': true,
      'with': true,
      'log': true
    };
    if (knownHelpers) {
      for (var name in knownHelpers) {
        this.options.knownHelpers[name] = knownHelpers[name];
      }
    }

    return this.accept(program);
  },

  accept: function(node) {
    var strip = node.strip || {},
        ret;
    if (strip.left) {
      this.opcode('strip');
    }

    ret = this[node.type](node);

    if (strip.right) {
      this.opcode('strip');
    }

    return ret;
  },

  program: function(program) {
    var statements = program.statements;

    for(var i=0, l=statements.length; i<l; i++) {
      this.accept(statements[i]);
    }
    this.isSimple = l === 1;

    this.depths.list = this.depths.list.sort(function(a, b) {
      return a - b;
    });

    return this;
  },

  compileProgram: function(program) {
    var result = new this.compiler().compile(program, this.options);
    var guid = this.guid++, depth;

    this.usePartial = this.usePartial || result.usePartial;

    this.children[guid] = result;

    for(var i=0, l=result.depths.list.length; i<l; i++) {
      depth = result.depths.list[i];

      if(depth < 2) { continue; }
      else { this.addDepth(depth - 1); }
    }

    return guid;
  },

  block: function(block) {
    var mustache = block.mustache,
        program = block.program,
        inverse = block.inverse;

    if (program) {
      program = this.compileProgram(program);
    }

    if (inverse) {
      inverse = this.compileProgram(inverse);
    }

    var sexpr = mustache.sexpr;
    var type = this.classifySexpr(sexpr);

    if (type === "helper") {
      this.helperSexpr(sexpr, program, inverse);
    } else if (type === "simple") {
      this.simpleSexpr(sexpr);

      // now that the simple mustache is resolved, we need to
      // evaluate it by executing `blockHelperMissing`
      this.opcode('pushProgram', program);
      this.opcode('pushProgram', inverse);
      this.opcode('emptyHash');
      this.opcode('blockValue');
    } else {
      this.ambiguousSexpr(sexpr, program, inverse);

      // now that the simple mustache is resolved, we need to
      // evaluate it by executing `blockHelperMissing`
      this.opcode('pushProgram', program);
      this.opcode('pushProgram', inverse);
      this.opcode('emptyHash');
      this.opcode('ambiguousBlockValue');
    }

    this.opcode('append');
  },

  hash: function(hash) {
    var pairs = hash.pairs, pair;

    this.opcode('pushHash');

    for(var i=0, l=pairs.length; i<l; i++) {
      pair = pairs[i];

      this.pushParam(pair[1]);
      this.opcode('assignToHash', pair[0]);
    }
    this.opcode('popHash');
  },

  partial: function(partial) {
    var partialName = partial.partialName;
    this.usePartial = true;

    if(partial.context) {
      this.ID(partial.context);
    } else {
      this.opcode('push', 'depth0');
    }

    this.opcode('invokePartial', partialName.name);
    this.opcode('append');
  },

  content: function(content) {
    this.opcode('appendContent', content.string);
  },

  mustache: function(mustache) {
    this.sexpr(mustache.sexpr);

    if(mustache.escaped && !this.options.noEscape) {
      this.opcode('appendEscaped');
    } else {
      this.opcode('append');
    }
  },

  ambiguousSexpr: function(sexpr, program, inverse) {
    var id = sexpr.id,
        name = id.parts[0],
        isBlock = program != null || inverse != null;

    this.opcode('getContext', id.depth);

    this.opcode('pushProgram', program);
    this.opcode('pushProgram', inverse);

    this.opcode('invokeAmbiguous', name, isBlock);
  },

  simpleSexpr: function(sexpr) {
    var id = sexpr.id;

    if (id.type === 'DATA') {
      this.DATA(id);
    } else if (id.parts.length) {
      this.ID(id);
    } else {
      // Simplified ID for `this`
      this.addDepth(id.depth);
      this.opcode('getContext', id.depth);
      this.opcode('pushContext');
    }

    this.opcode('resolvePossibleLambda');
  },

  helperSexpr: function(sexpr, program, inverse) {
    var params = this.setupFullMustacheParams(sexpr, program, inverse),
        name = sexpr.id.parts[0];

    if (this.options.knownHelpers[name]) {
      this.opcode('invokeKnownHelper', params.length, name);
    } else if (this.options.knownHelpersOnly) {
      throw new Exception("You specified knownHelpersOnly, but used the unknown helper " + name, sexpr);
    } else {
      this.opcode('invokeHelper', params.length, name, sexpr.isRoot);
    }
  },

  sexpr: function(sexpr) {
    var type = this.classifySexpr(sexpr);

    if (type === "simple") {
      this.simpleSexpr(sexpr);
    } else if (type === "helper") {
      this.helperSexpr(sexpr);
    } else {
      this.ambiguousSexpr(sexpr);
    }
  },

  ID: function(id) {
    this.addDepth(id.depth);
    this.opcode('getContext', id.depth);

    var name = id.parts[0];
    if (!name) {
      this.opcode('pushContext');
    } else {
      this.opcode('lookupOnContext', id.parts[0]);
    }

    for(var i=1, l=id.parts.length; i<l; i++) {
      this.opcode('lookup', id.parts[i]);
    }
  },

  DATA: function(data) {
    this.options.data = true;
    if (data.id.isScoped || data.id.depth) {
      throw new Exception('Scoped data references are not supported: ' + data.original, data);
    }

    this.opcode('lookupData');
    var parts = data.id.parts;
    for(var i=0, l=parts.length; i<l; i++) {
      this.opcode('lookup', parts[i]);
    }
  },

  STRING: function(string) {
    this.opcode('pushString', string.string);
  },

  INTEGER: function(integer) {
    this.opcode('pushLiteral', integer.integer);
  },

  BOOLEAN: function(bool) {
    this.opcode('pushLiteral', bool.bool);
  },

  comment: function() {},

  // HELPERS
  opcode: function(name) {
    this.opcodes.push({ opcode: name, args: [].slice.call(arguments, 1) });
  },

  declare: function(name, value) {
    this.opcodes.push({ opcode: 'DECLARE', name: name, value: value });
  },

  addDepth: function(depth) {
    if(depth === 0) { return; }

    if(!this.depths[depth]) {
      this.depths[depth] = true;
      this.depths.list.push(depth);
    }
  },

  classifySexpr: function(sexpr) {
    var isHelper   = sexpr.isHelper;
    var isEligible = sexpr.eligibleHelper;
    var options    = this.options;

    // if ambiguous, we can possibly resolve the ambiguity now
    // An eligible helper is one that does not have a complex path, i.e. `this.foo`, `../foo` etc.
    if (isEligible && !isHelper) {
      var name = sexpr.id.parts[0];

      if (options.knownHelpers[name]) {
        isHelper = true;
      } else if (options.knownHelpersOnly) {
        isEligible = false;
      }
    }

    if (isHelper) { return "helper"; }
    else if (isEligible) { return "ambiguous"; }
    else { return "simple"; }
  },

  pushParams: function(params) {
    var i = params.length;

    while(i--) {
      this.pushParam(params[i]);
    }
  },

  pushParam: function(val) {
    if (this.stringParams) {
      if(val.depth) {
        this.addDepth(val.depth);
      }
      this.opcode('getContext', val.depth || 0);
      this.opcode('pushStringParam', val.stringModeValue, val.type);

      if (val.type === 'sexpr') {
        // Subexpressions get evaluated and passed in
        // in string params mode.
        this.sexpr(val);
      }
    } else {
      this.accept(val);
    }
  },

  setupFullMustacheParams: function(sexpr, program, inverse) {
    var params = sexpr.params;
    this.pushParams(params);

    this.opcode('pushProgram', program);
    this.opcode('pushProgram', inverse);

    if (sexpr.hash) {
      this.hash(sexpr.hash);
    } else {
      this.opcode('emptyHash');
    }

    return params;
  }
};

export function precompile(input, options, env) {
  if (input == null || (typeof input !== 'string' && input.constructor !== env.AST.ProgramNode)) {
    throw new Exception("You must pass a string or Handlebars AST to Handlebars.precompile. You passed " + input);
  }

  options = options || {};
  if (!('data' in options)) {
    options.data = true;
  }

  var ast = env.parse(input);
  var environment = new env.Compiler().compile(ast, options);
  return new env.JavaScriptCompiler().compile(environment, options);
}

export function compile(input, options, env) {
  if (input == null || (typeof input !== 'string' && input.constructor !== env.AST.ProgramNode)) {
    throw new Exception("You must pass a string or Handlebars AST to Handlebars.compile. You passed " + input);
  }

  options = options || {};

  if (!('data' in options)) {
    options.data = true;
  }

  var compiled;

  function compileInput() {
    var ast = env.parse(input);
    var environment = new env.Compiler().compile(ast, options);
    var templateSpec = new env.JavaScriptCompiler().compile(environment, options, undefined, true);
    return env.template(templateSpec);
  }

  // Template is only compiled on first use and cached after that point.
  return function(context, options) {
    if (!compiled) {
      compiled = compileInput();
    }
    return compiled.call(this, context, options);
  };
}
<|endoftext|>


<commit_before>// ==========================================================================
// Project:   SproutCore Costello - Property Observing Library
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple, Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

/*globals module test ok equals same CoreTest */

sc_require('debug/test_suites/array/base');

// temporary fix until we decide where to put test-only support code
if (window.CoreTest) {

SC.ArraySuite.define(function(T) {

  var observer, obj ;

  module(T.desc("removeAt"), {
    setup: function() {
      obj = T.newObject();
      observer = T.observer(obj);
    }
  });

  test("[X].removeAt(0) => [] + notify", function() {

    var before = T.expected(1);
    obj.replace(0,0, before);
    observer.observe('[]', 'length') ;

    obj.removeAt(0) ;
    T.validateAfter(obj, [], observer, YES);
  });

  test("[].removeAt(200,X) => OUT_OF_RANGE_EXCEPTION exception", function() {
    var didThrow = NO ;
    try {
      obj.removeAt(200, T.expected(1));
    } catch (e) {
      equals(e, SC.OUT_OF_RANGE_EXCEPTION, 'should throw SC.OUT_OF_RANGE_EXCEPTION');
      didThrow = YES ;
    }
    ok(didThrow, 'should raise exception');
  });

  test("[A,B].removeAt(0) => [B] + notify", function() {
    var before = T.expected(2),
        after   = [before[1]];

    obj.replace(0,0,before);
    observer.observe('[]', 'length') ;

    obj.removeAt(0);
    T.validateAfter(obj, after, observer, YES);
  });

  test("[A,B].removeAt(1) => [A] + notify", function() {
    var before = T.expected(2),
        after   = [before[0]];

    obj.replace(0,0,before);
    observer.observe('[]', 'length') ;

    obj.removeAt(1);
    T.validateAfter(obj, after, observer, YES);
  });

  test("[A,B,C].removeAt(1) => [A,C] + notify", function() {
    var before = T.expected(3),
        after   = [before[0], before[2]];

    obj.replace(0,0,before);
    observer.observe('[]', 'length') ;

    obj.removeAt(1);
    T.validateAfter(obj, after, observer, YES);
  });

});

}
<commit_msg>Add unit tests for new removeAt() support<commit_after>// ==========================================================================
// Project:   SproutCore Costello - Property Observing Library
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple, Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

/*globals module test ok equals same CoreTest */

sc_require('debug/test_suites/array/base');

// temporary fix until we decide where to put test-only support code
if (window.CoreTest) {

SC.ArraySuite.define(function(T) {

  var observer, obj ;

  module(T.desc("removeAt"), {
    setup: function() {
      obj = T.newObject();
      observer = T.observer(obj);
    }
  });

  test("[X].removeAt(0) => [] + notify", function() {

    var before = T.expected(1);
    obj.replace(0,0, before);
    observer.observe('[]', 'length') ;

    obj.removeAt(0) ;
    T.validateAfter(obj, [], observer, YES);
  });

  test("[].removeAt(200) => OUT_OF_RANGE_EXCEPTION exception", function() {
    var didThrow = NO ;
    try {
      obj.removeAt(200);
    } catch (e) {
      equals(e, SC.OUT_OF_RANGE_EXCEPTION, 'should throw SC.OUT_OF_RANGE_EXCEPTION');
      didThrow = YES ;
    }
    ok(didThrow, 'should raise exception');
  });

  test("[A,B].removeAt(0) => [B] + notify", function() {
    var before = T.expected(2),
        after   = [before[1]];

    obj.replace(0,0,before);
    observer.observe('[]', 'length') ;

    obj.removeAt(0);
    T.validateAfter(obj, after, observer, YES);
  });

  test("[A,B].removeAt(1) => [A] + notify", function() {
    var before = T.expected(2),
        after   = [before[0]];

    obj.replace(0,0,before);
    observer.observe('[]', 'length') ;

    obj.removeAt(1);
    T.validateAfter(obj, after, observer, YES);
  });

  test("[A,B,C].removeAt(1) => [A,C] + notify", function() {
    var before = T.expected(3),
        after   = [before[0], before[2]];

    obj.replace(0,0,before);
    observer.observe('[]', 'length') ;

    obj.removeAt(1);
    T.validateAfter(obj, after, observer, YES);
  });

  test("[A,B,C,D].removeAt(1,2) => [A,D] + notify", function() {
    var before = T.expected(4),
        after   = [before[0], before[3]];

    obj.replace(0,0,before);
    observer.observe('[]', 'length') ;

    obj.removeAt(1,2);
    T.validateAfter(obj, after, observer, YES);
  });

  test("[A,B,C,D].removeAt(IndexSet<0,2-3>) => [B] + notify", function() {
    var before = T.expected(4),
        after   = [before[1]];

    obj.replace(0,0,before);
    observer.observe('[]', 'length') ;

    obj.removeAt(SC.IndexSet.create(0).add(2,2));
    T.validateAfter(obj, after, observer, YES);
  });

});

}
<|endoftext|>


<commit_before>(function(jQuery) {
	"use strict";

	var app = angular.module('RbsChangeApp');

	function rbsMediaPictograms() {
		return {
			restrict: 'A',
			templateUrl: '/rbsMediaPictograms.tpl',
			scope: {
				pictograms: '=rbsMediaPictograms'
			},
			link: function(scope, elm, attrs) {
				scope.format = angular.isString(attrs['pictogramFormat']) ? attrs['pictogramFormat'] : 'pictogram';

				scope.position = {
					vertical: 'bottom',
					horizontal: 'right'
				};

				var positionParts = angular.isString(attrs['pictogramPosition']) ? attrs['pictogramPosition'].split(' ') : [];
				for (var i = 0; i < positionParts.length; i++) {
					switch (positionParts[i]) {
						case 'top':
						case 'bottom':
							scope.position.vertical = positionParts[i];
							break;

						case 'left':
						case 'center':
						case 'right':
							scope.position.horizontal = positionParts[i];
							break;
					}
				}

				scope.ngClasses = {
					main: {},
					size: {},
					maxSize: {}
				};
				scope.ngClasses.main['media-pictograms-' + scope.position.vertical] = true;
				scope.ngClasses.main['media-pictograms-' + scope.position.horizontal] = true;
				scope.ngClasses.main['media-pictograms-format-' + scope.format] = true;
				scope.ngClasses.size['image-format-' + scope.format + '-size'] = true;
				scope.ngClasses.maxSize['image-format-' + scope.format + '-max-size'] = true;
			}
		}
	}

	app.directive('rbsMediaPictograms', rbsMediaPictograms);

	function rbsMediaVisuals() {
		return {
			restrict: 'A',
			templateUrl: '/rbsMediaVisuals.tpl',
			scope: {
				visuals: '=rbsMediaVisuals',
				pictograms: '='
			},
			link: function(scope, elm, attrs) {
				scope.visualFormat = angular.isString(attrs['visualFormat']) ? attrs['visualFormat'] : 'detail';
				scope.thumbnailFormat = angular.isString(attrs['thumbnailFormat']) ? attrs['thumbnailFormat'] : 'detailThumbnail';
				scope.thumbnailPosition = angular.isString(attrs['thumbnailPosition']) ? attrs['thumbnailPosition'] : 'right';
				scope.pictogramFormat = angular.isString(attrs['pictogramFormat']) ? attrs['pictogramFormat'] : 'pictogram';
				scope.pictogramPosition = angular.isString(attrs['pictogramPosition']) ? attrs['pictogramPosition'] : null;

				scope.shownIndex = 0;
				scope.zoom = {
					enabled: attrs['enableZoom'] && attrs['enableZoom'] != 'false',
					shown: false
				};

				scope.ngClasses = {
					main: {}
				};
				if (angular.isArray(scope.visuals) && scope.visuals.length >= 1) {
					scope.ngClasses.main['media-visuals-multiple'] = true;
					scope.ngClasses.main['media-visuals-multiple-' + scope.thumbnailPosition] = true;
				}
				else {
					scope.ngClasses.main['media-visuals-single'] = true;
				}
				scope.ngClasses.main['media-visuals-format-' + scope.visualFormat + '-' + scope.thumbnailFormat] = true;

				scope.showVisual = function(event, index) {
					scope.shownIndex = index;
					event.preventDefault();
				};

				scope.startZoom = function() {
					scope.zoom.shown = true;

					var linkNode = elm.find('.media-visuals-main [data-index=' + scope.shownIndex + '] a');
					var image = linkNode.find('img');

					var zoomDiv = elm.find('.media-visuals-zoom');
					var bigImage = jQuery('');
					zoomDiv.append(bigImage);

					image.mousemove(function(event) {
						var scaleX = (bigImage.width() / image.width());
						var scaleY = (bigImage.height() / image.height());
						var offset = image.offset();
						bigImage.css({
							top: Math.max(zoomDiv.height() - bigImage.height(),
								Math.min(0, zoomDiv.height() / 2 - (event.pageY - offset.top) * scaleY)),
							left: Math.max(zoomDiv.width() - bigImage.width(),
								Math.min(0, zoomDiv.width() / 2 - (event.pageX - offset.left) * scaleX))
						});
					});

					image.mouseout(function() {
						scope.zoom.shown = false;
						bigImage.remove();
						scope.$digest();

						image.unbind('mousemove');
						image.unbind('mouseout');
					});

					// Disable the link on the image.
					linkNode.click(function(event) { event.preventDefault(); });
				};
			}
		}
	}

	app.directive('rbsMediaVisuals', rbsMediaVisuals);
})(jQuery);<commit_msg>Fix thumbnail zone display in rbsMediaVisuals directive.<commit_after>(function(jQuery) {
	"use strict";

	var app = angular.module('RbsChangeApp');

	function rbsMediaPictograms() {
		return {
			restrict: 'A',
			templateUrl: '/rbsMediaPictograms.tpl',
			scope: {
				pictograms: '=rbsMediaPictograms'
			},
			link: function(scope, elm, attrs) {
				scope.format = angular.isString(attrs['pictogramFormat']) ? attrs['pictogramFormat'] : 'pictogram';

				scope.position = {
					vertical: 'bottom',
					horizontal: 'right'
				};

				var positionParts = angular.isString(attrs['pictogramPosition']) ? attrs['pictogramPosition'].split(' ') : [];
				for (var i = 0; i < positionParts.length; i++) {
					switch (positionParts[i]) {
						case 'top':
						case 'bottom':
							scope.position.vertical = positionParts[i];
							break;

						case 'left':
						case 'center':
						case 'right':
							scope.position.horizontal = positionParts[i];
							break;
					}
				}

				scope.ngClasses = {
					main: {},
					size: {},
					maxSize: {}
				};
				scope.ngClasses.main['media-pictograms-' + scope.position.vertical] = true;
				scope.ngClasses.main['media-pictograms-' + scope.position.horizontal] = true;
				scope.ngClasses.main['media-pictograms-format-' + scope.format] = true;
				scope.ngClasses.size['image-format-' + scope.format + '-size'] = true;
				scope.ngClasses.maxSize['image-format-' + scope.format + '-max-size'] = true;
			}
		}
	}

	app.directive('rbsMediaPictograms', rbsMediaPictograms);

	function rbsMediaVisuals() {
		return {
			restrict: 'A',
			templateUrl: '/rbsMediaVisuals.tpl',
			scope: {
				visuals: '=rbsMediaVisuals',
				pictograms: '='
			},
			link: function(scope, elm, attrs) {
				scope.visualFormat = angular.isString(attrs['visualFormat']) ? attrs['visualFormat'] : 'detail';
				scope.thumbnailFormat = angular.isString(attrs['thumbnailFormat']) ? attrs['thumbnailFormat'] : 'detailThumbnail';
				scope.thumbnailPosition = angular.isString(attrs['thumbnailPosition']) ? attrs['thumbnailPosition'] : 'right';
				scope.pictogramFormat = angular.isString(attrs['pictogramFormat']) ? attrs['pictogramFormat'] : 'pictogram';
				scope.pictogramPosition = angular.isString(attrs['pictogramPosition']) ? attrs['pictogramPosition'] : null;

				scope.shownIndex = 0;
				scope.zoom = {
					enabled: attrs['enableZoom'] && attrs['enableZoom'] != 'false',
					shown: false
				};

				scope.ngClasses = {
					main: {}
				};
				if (angular.isArray(scope.visuals) && scope.visuals.length > 1) {
					scope.ngClasses.main['media-visuals-multiple'] = true;
					scope.ngClasses.main['media-visuals-multiple-' + scope.thumbnailPosition] = true;
				}
				else {
					scope.ngClasses.main['media-visuals-single'] = true;
				}
				scope.ngClasses.main['media-visuals-format-' + scope.visualFormat + '-' + scope.thumbnailFormat] = true;

				scope.showVisual = function(event, index) {
					scope.shownIndex = index;
					event.preventDefault();
				};

				scope.startZoom = function() {
					scope.zoom.shown = true;

					var linkNode = elm.find('.media-visuals-main [data-index=' + scope.shownIndex + '] a');
					var image = linkNode.find('img');

					var zoomDiv = elm.find('.media-visuals-zoom');
					var bigImage = jQuery('');
					zoomDiv.append(bigImage);

					image.mousemove(function(event) {
						var scaleX = (bigImage.width() / image.width());
						var scaleY = (bigImage.height() / image.height());
						var offset = image.offset();
						bigImage.css({
							top: Math.max(zoomDiv.height() - bigImage.height(),
								Math.min(0, zoomDiv.height() / 2 - (event.pageY - offset.top) * scaleY)),
							left: Math.max(zoomDiv.width() - bigImage.width(),
								Math.min(0, zoomDiv.width() / 2 - (event.pageX - offset.left) * scaleX))
						});
					});

					image.mouseout(function() {
						scope.zoom.shown = false;
						bigImage.remove();
						scope.$digest();

						image.unbind('mousemove');
						image.unbind('mouseout');
					});

					// Disable the link on the image.
					linkNode.click(function(event) { event.preventDefault(); });
				};
			}
		}
	}

	app.directive('rbsMediaVisuals', rbsMediaVisuals);
})(jQuery);<|endoftext|>