<commit_before>'use strict';

var util = require('util');
var _ = require('lodash');
var async = require('async');
var slug = require('slug');
var shortid = require('shortid');
var crypto = require('crypto');
var randomstring = require('randomstring');
var fs = require('fs');
var moment = require('moment');
var pg = require('pg');

var google = require('googleapis');
var admin = google.admin('directory_v1');
var path = require('path');
var fs = require('fs');
var DEFAULT_LANG = 'en_US';

module.exports = function (options) {
  var seneca = this;
  var plugin = 'cd-dojos';
  var ENTITY_NS = 'cd/dojos';
  var USER_DOJO_ENTITY_NS = "cd/usersdojos";
  var STATS_ENTITY_NS = "cd/stats";
  var DOJO_LEADS_ENTITY_NS = "cd/dojoleads";
  var CDF_ADMIN = 'cdf-admin';
  var DEFAULT_INVITE_USER_TYPE = 'mentor';
  var setupDojoSteps = require('./data/setup_dojo_steps');
  var dojoConfig = require('./data/dojos_config');
  var so = seneca.options();

  seneca.add({role: plugin, cmd: 'search'}, cmd_search);
  seneca.add({role: plugin, cmd: 'list'}, cmd_list);
  seneca.add({role: plugin, cmd: 'load'}, cmd_load);
  seneca.add({role: plugin, cmd: 'find'}, cmd_find);
  seneca.add({role: plugin, cmd: 'create'}, wrapCheckRateLimitCreateDojo(cmd_create));
  seneca.add({role: plugin, cmd: 'update'}, wrapDojoExists(wrapDojoPermissions(cmd_update)));
  seneca.add({role: plugin, cmd: 'delete'}, wrapDojoExists(wrapDojoPermissions(cmd_delete)));
  seneca.add({role: plugin, cmd: 'my_dojos'}, cmd_my_dojos);
  seneca.add({role: plugin, cmd: 'dojos_count'}, cmd_dojos_count);
  seneca.add({role: plugin, cmd: 'dojos_by_country'}, cmd_dojos_by_country);
  seneca.add({role: plugin, cmd: 'dojos_state_count'}, cmd_dojos_state_count);
  seneca.add({role: plugin, cmd: 'bulk_update'}, cmd_bulk_update);
  seneca.add({role: plugin, cmd: 'bulk_delete'}, cmd_bulk_delete);
  seneca.add({role: plugin, cmd: 'get_stats'}, wrapCheckCDFAdmin(cmd_get_stats));
  seneca.add({role: plugin, cmd: 'save_dojo_lead'}, cmd_save_dojo_lead);
  seneca.add({role: plugin, cmd: 'update_dojo_lead'}, cmd_save_dojo_lead);
  seneca.add({role: plugin, cmd: 'load_user_dojo_lead'}, cmd_load_user_dojo_lead);
  seneca.add({role: plugin, cmd: 'load_dojo_lead'}, cmd_load_dojo_lead);
  seneca.add({role: plugin, cmd: 'load_setup_dojo_steps'}, cmd_load_setup_dojo_steps);
  seneca.add({role: plugin, cmd: 'load_usersdojos'}, cmd_load_users_dojos);
  seneca.add({role: plugin, cmd: 'load_dojo_users'}, cmd_load_dojo_users);
  seneca.add({role: plugin, cmd: 'send_email'}, cmd_send_email);
  seneca.add({role: plugin, cmd: 'generate_user_invite_token'}, cmd_generate_user_invite_token);
  seneca.add({role: plugin, cmd: 'accept_user_invite'}, cmd_accept_user_invite);
  seneca.add({role: plugin, cmd: 'request_user_invite'}, cmd_request_user_invite);
  seneca.add({role: plugin, cmd: 'load_dojo_champion'}, cmd_load_dojo_champion);
  seneca.add({role: plugin, cmd: 'accept_user_request'}, cmd_accept_user_request);
  seneca.add({role: plugin, cmd: 'dojos_for_user'}, cmd_dojos_for_user);
  seneca.add({role: plugin, cmd: 'save_usersdojos'}, cmd_save_usersdojos);
  seneca.add({role: plugin, cmd: 'remove_usersdojos'}, cmd_remove_usersdojos);
  seneca.add({role: plugin, cmd: 'get_user_types'}, cmd_get_user_types);
  seneca.add({role: plugin, cmd: 'get_user_permissions'}, cmd_get_user_permissions);
  seneca.add({role: plugin, cmd: 'create_dojo_email'}, cmd_create_dojo_email);
  seneca.add({role: plugin, cmd: 'search_dojo_leads'}, cmd_search_dojo_leads);
  seneca.add({role: plugin, cmd: 'uncompleted_dojos'}, cmd_uncompleted_dojos);
  seneca.add({role: plugin, cmd: 'get_dojo_config'}, cmd_get_dojo_config);
  seneca.add({role: plugin, cmd: 'load_dojo_admins'}, cmd_load_dojo_admins);
  seneca.add({role: plugin, cmd: 'update_founder'}, cmd_update_dojo_founder);
  seneca.add({role: plugin, cmd: 'search_nearest_dojos'}, cmd_search_nearest_dojos);
  seneca.add({role: plugin, cmd: 'search_bounding_box'}, cmd_search_bounding_box);
  seneca.add({role: plugin, cmd: 'list_query'}, cmd_list_query);

  function cmd_update_dojo_founder(args, done){
    var founder = args.founder;
    var seneca = this;
    if(_.isEmpty(founder)){
      return done(new Error('Founder is empty'));
    }

    async.waterfall([
      isCDFAdmin,
      getPreviousFounderUserDojo,
      updatePreviousFounderUserDojo,
      getCurrentFounderUserDojo,
      updateOrCreateUserDojo
      ],done);

    function isCDFAdmin(done){
      var userId = args.user.id;

      seneca.act({role: 'cd-users', cmd: 'load', id: userId}, function(err, user){
        if(err){
          return done(err);
        }

        if(!_.contains(user.roles, 'cdf-admin')){
          return done(new Error('Unauthorized'));
        }

        return done();
      });
    }

    function getPreviousFounderUserDojo(done){
      var query = {};

      query.userId = founder.previousFounderId;
      query.owner = 1;
      query.dojoId = founder.dojoId;

      seneca.act({role: 'cd-dojos', cmd: 'load_usersdojos', query: query}, function(err, usersDojos){
        if(err){
          return done(err);
        }

        var userDojo = usersDojos[0];

        if(_.isEmpty(userDojo)){
          return done(new Error('Cannot find previous founder'));
        }

        return done(null, userDojo);

      });
    }

    function updatePreviousFounderUserDojo(userDojo, done){
      if(_.isEmpty(userDojo)){
        return done();
      }

      userDojo.owner = 0;

      seneca.act({role: 'cd-dojos', cmd: 'save_usersdojos', userDojo: userDojo}, done);
    }

    function getCurrentFounderUserDojo(prevFoundersUserDojo, done){
      var query = {};

      query.userId = founder.id;
      query.dojoId = founder.dojoId;

      seneca.act({role: 'cd-dojos', cmd: 'load_usersdojos', query: query}, function(err, currentFounder){
        if(err){
          return done(err);
        }

        return done(null, currentFounder[0]);
      });
    }

    function updateOrCreateUserDojo(userDojo, done){
      if(_.isEmpty(userDojo)){
        userDojo = {};
        userDojo.dojoId = founder.dojoId;
        userDojo.userId = founder.id;
      }

      if(!userDojo.userTypes){
        userDojo.userTypes = [];
      }

      if(!_.contains(userDojo.userTypes, 'champion')){
        userDojo.userTypes.push('champion');
      }

      userDojo.owner = 1;

      seneca.act({role: 'cd-dojos', cmd: 'save_usersdojos', userDojo: userDojo}, done);
    }
  }

  function cmd_create_dojo_email(args, done) {
    if (!args.dojo) {
      return done('Dojo data is missing.');
    }

    if(options['google-api'].enabled === false) {
      return done();
    }

    //check if Google API private key file exists
    if (!fs.existsSync(options['google-api'].keyFile)) {
      return done("Google API private key not found", null);
    }

    var jwt = new google.auth.JWT(
      options['google-api'].email,
      options['google-api'].keyFile,
      "",
      options['google-api'].scopes,
      options['google-api'].subject
    );

    jwt.authorize(function (err, data) {
      if (err) {
        return done(err)
      }

      getGoogleUserData(args.dojo, function (err, res) {

        var googleNewAccountData = res.userData;
        var tempPass = googleNewAccountData.tempPass;
        googleNewAccountData = _.omit(googleNewAccountData, 'tempPass');
        var dojo = res.dojo;

        // Insert user
        admin.users.insert({
          resource: googleNewAccountData,
          auth: jwt
        }, function (err, data) {
          if (err) {
            return done(err)
          }

          seneca.act({role: 'cd-users', cmd: 'load', id: dojo.creator}, function (err, dojoCreator) {
            if (err) {
              return done(err)
            }

            var locality = args.locality || 'en_us';
            var code = 'google-email-pass-' + locality.toLowerCase();

            var templates = {};

            try {
              templates.html = fs.statSync(path.join(so.mail.folder, code, 'html.ejs'));
              templates.text = fs.statSync(path.join(so.mail.folder, code, 'text.ejs'));
            } catch(err){
              code = 'google-email-pass-' + DEFAULT_LANG;
            }

            //send dojo creator an email with dojo's newly created email address and it's temp password
            var payload = {
              to: dojoCreator.email,
              code: code,
              content: {temp_pass: <PASSWORD>, dojo: dojo.name, email: googleNewAccountData.primaryEmail}
            };
            seneca.act({role: plugin, cmd: 'send_email', payload: payload}, function (err, res) {
              if (err) {
                return done(err)
              }

              done(null, data);
            });
          });
        });
      });
    });
  }

  function getGoogleUserData(dojo, done) {
    var userData = {};

    seneca.act('role:cd-dojos,cmd:load', {id: dojo.id}, function (err, res) {
      if (err) {
        return done(err)
      }

      dojo = res.data$();

      //this can look something like this: <EMAIL>
      var primaryEmail = _.last(dojo.urlSlug.split('/')).concat('.', dojo.alpha2.toLowerCase(), '@coderdojo.com');

      if (process.env.ENVIRONMENT === 'development') {
        primaryEmail = 'dev-' + primaryEmail;
      }

      //required user data
      userData.name = {
        familyName: dojo.name, //this can be changed with the champion name & family name
        givenName: dojo.placeName || dojo.address1
      };
      var pass = <PASSWORD>.<PASSWORD>(8);
      userData.tempPass = <PASSWORD>;
      userData.password = <PASSWORD>(pass);//use default pass for now; replace this with random pass when finished
      userData.hashFunction = "SHA-1";
      userData.primaryEmail = primaryEmail;
      userData.changePasswordAtNextLogin = true;
      userData.emails = [
        {
          "address": "<EMAIL>",
          "type": "other",
          "customType": "",
          "primary": false
        }
      ];
      userData.organizations = [
        {
          "name": "nearform_test",
          "title": "champion",
          "primary": true,
          "type": "school",
          "description": "new test dojo",
          domain: 'coderdojo.org'
        }
      ];

      return done(null, {userData: userData, dojo: dojo});
    });
  }

  function sha1sum(input){
    return crypto.createHash('sha1').update(JSON.stringify(input)).digest('hex')
  }

  function cmd_search_dojo_leads(args, done){
    var dojoLeadsEntity = seneca.make$(DOJO_LEADS_ENTITY_NS);
    dojoLeadsEntity.list$(args.query, done);
  }

  function cmd_uncompleted_dojos(args, done) {
    var query = {creator: args.user.id, deleted: 0};
    seneca.act({role: plugin, cmd: 'search', query: query}, function(err, dojos){
      if(err){ return done(err) }
      if(dojos.length > 0) {
        var uncompletedDojos = [];
        async.each(dojos, function (dojo, cb) {
          //check if dojo "setup dojo step is completed"
          seneca.act({role: plugin, cmd: 'load_dojo_lead', id: dojo.dojoLeadId}, function (err, dojoLead) {
            if(err) return cb(err);
            if (dojoLead) {
              var isCompleted = checkSetupYourDojoIsCompleted(dojoLead);
              if (!isCompleted) {
                uncompletedDojos.push(dojo);
              }
            }
            return cb();
          });
        }, function (err) {
          if (err) return done(err);
          return done(null, uncompletedDojos);
        });
      } else return done(null);
    });
  }

  function checkSetupYourDojoIsCompleted(dojoLead){
    var isDojoCompleted = true;

    if(dojoLead.application.setupYourDojo) {
      var setupYourDojo = dojoLead.application.setupYourDojo;
      var checkboxes = _.flatten(_.pluck(setupDojoSteps, 'checkboxes'));

      _.each(checkboxes, function(checkbox){
        if (!setupYourDojo[checkbox.name]) {
          isDojoCompleted = false;
        }

        if(checkbox.textField){
          if(!setupYourDojo[checkbox.name + "Text"]) {
            isDojoCompleted = false;
          }
        }
      });
    }

    return isDojoCompleted;
  }

  function isUserChampionAndDojoAdmin(query, requestingUser, done) {

    if(_.contains(requestingUser.roles, 'cdf-admin')) {
      return done(null, true);
    }

    seneca.act({role:plugin, cmd:'load_usersdojos', query:query}, function (err, response) {
      if(err) return done(err);
      var userDojo = response[0];
      var isDojoChampion  = _.contains(userDojo.userTypes, 'champion');
      var isDojoAdmin = _.find(userDojo.userPermissions, function(userPermission) {
                      return userPermission.name === 'dojo-admin';
                    });
      if(isDojoChampion && isDojoAdmin) return done(null, true);
      return done(null, false);
    });
  }

  function cmd_search(args, done) {
    var usersdojos_ent = seneca.make$(USER_DOJO_ENTITY_NS);
    async.waterfall([
      function (done) {
        var query = args.query;
        if(query.name) query.name = new RegExp(query.name, 'i');
        if(query.email) query.email = new RegExp(query.email, 'i');
        if(query.creatorEmail) query.creatorEmail = new RegExp(query.creatorEmail, 'i');
        seneca.act({role: plugin, cmd: 'list_query', query: query}, done);
      },
      function (searchResult, done) {
        var dojos = searchResult;
        async.each(dojos, function (dojo, cb){
          seneca.act({role: plugin, cmd: 'load_usersdojos', query: {dojoId: dojo.id, owner: 1}},
            function(err, userDojos){
              if(err) return cb(err);
              if(userDojos.length < 1) return cb();
              var userIds = _.pluck(userDojos, 'userId');
              seneca.act({role: 'cd-users', cmd: 'list', ids: userIds}, function(err, users){
                if(err) return cb(err);
                dojo.creators = _.map(users, function(user){
                  return {email: user.email, id: user.id};
                });
                return cb(null, dojo);
              });
            });
          }, function (err) {
            if(err) return done(err);
            return done(null, searchResult);
          });
      },
      function (searchResult, done) {
        var userIds = _.chain(searchResult).pluck('creators').flatten().pluck('id').uniq().value();
        async.waterfall([
          function(done) {
            seneca.act({role:'cd-agreements', cmd:'list', userIds: userIds}, done);
          },
          function(agreements, done) {
            agreements = _.indexBy(agreements, 'userId');
            _.each(searchResult, function(dojo) {
              dojo.agreements = [];
              _.each(dojo.creators, function(creator){
                creator.agreements = [];
                if (agreements[creator.id]) {
                  creator.agreements = agreements[creator.id].agreements;
                }
              });
            });
            return done(null, searchResult);
          }
        ], done);
      },
      function(searchResult, done) {
        return done(null, searchResult);
      }
    ], function(err, res) {
      if (err) {
        return done(err);
      }
      return done(null, res);
    });
  }

  function cmd_dojos_state_count(args, done) {
    var country = args.country;
    var countData = {};

    seneca.make$(ENTITY_NS).list$({limit$:'NULL', alpha2:country, deleted: 0, verified: 1}, function(err, response) {
      if(err) return done(err);
      countData[country] = {};
      _.each(response, function(dojo) {
        if(dojo.coordinates && dojo.stage !== 4) {
          if(!countData[dojo.alpha2][dojo.admin1Name]) countData[dojo.alpha2][dojo.admin1Name] = {total:0};
          countData[dojo.alpha2][dojo.admin1Name].total += 1;
          countData[dojo.alpha2][dojo.admin1Name].latitude = dojo.coordinates.split(',')[0];
          countData[dojo.alpha2][dojo.admin1Name].longitude = dojo.coordinates.split(',')[1];
        }
      });
      done(null, countData);
    });

  }

  function cmd_dojos_by_country(args, done) {
    var countries = args.countries;
    var dojos = [];
    async.each(Object.keys(countries), function(country, cb) {
      seneca.act({role:plugin, cmd:'list'}, {query:{alpha2:country}}, function(err, response) {
        if(err) return done(err);
        dojos.push(response);
        cb();
      });
    }, function() {
      dojos = _.sortBy(dojos, function(dojo) { return Object.keys(dojo)[0]; });
      done(null, dojos);
    });
  }

  function cmd_dojos_count(args, done) {
    async.waterfall([
      getDojos,
      getCountries,
      getDojoCount
    ], done);

    function getDojos(done) {
      var dojos = [];
      var query = {limit$:'NULL'};
      query.deleted = 0;
      query.verified = 1;
      seneca.make$(ENTITY_NS).list$(query, function(err, response) {
        if(err) return response;
        async.each(response, function(dojo, cb) {
          if(dojo.stage !== 4) {
            dojos.push(dojo);
          }

          cb();
        }, function() {
          done(null, dojos);
        });
      });
    }

    function getCountries(dojos, done) {
      seneca.act({role:'cd-countries', cmd:'countries_continents'}, function(err, response) {
        if(err) return done(err);
        done(null, dojos, response);
      });
    }

    function getDojoCount(dojos, countries, done) {
      var countData = {dojos:{continents:{}}};
      _.each(dojos, function(dojo) {
        if(countries.countries[dojo.alpha2]) {
          var continent = countries.countries[dojo.alpha2].continent;
          if(!countData.dojos.continents[continent]) countData.dojos.continents[continent] = {total:0, countries:{}};
          countData.dojos.continents[continent].total += 1;
          if(!countData.dojos.continents[continent].countries[dojo.alpha2]) countData.dojos.continents[continent].countries[dojo.alpha2] = {total:0};
          countData.dojos.continents[continent].countries[dojo.alpha2].total += 1;
        }
      });
      done(null, countData);
    }
  }

  function cmd_list(args, done) {
    var query = args.query || {};
    query.limit$ = 'NULL';
    query.verified = 1;
    query.deleted = 0;
    seneca.make$(ENTITY_NS).list$(query, function(err, response) {
      if(err) return done(err);

      var dojosByCountry = {};
      response = _.sortBy(response, 'countryName');
      _.each(response, function(dojo) {
        if(dojo.stage !== 4) {
          if(!dojosByCountry[dojo.countryName]) {
            dojosByCountry[dojo.countryName] = {};
            dojosByCountry[dojo.countryName].states = {};
            if(!dojosByCountry[dojo.countryName].states[dojo.admin1Name]) dojosByCountry[dojo.countryName].states[dojo.admin1Name] = [];
            dojosByCountry[dojo.countryName].states[dojo.admin1Name].push(dojo);
          } else {
            if(!dojosByCountry[dojo.countryName].states[dojo.admin1Name]) dojosByCountry[dojo.countryName].states[dojo.admin1Name] = [];
            dojosByCountry[dojo.countryName].states[dojo.admin1Name].push(dojo);
          }
        }
      });

      var countries = Object.keys(dojosByCountry);
      _.each(countries, function(countryName) {
        var states = Object.keys(dojosByCountry[countryName].states);
        _.each(states, function(state) {
          dojosByCountry[countryName].states[state] = _.sortBy(dojosByCountry[countryName].states[state], function(dojo) { return dojo.name; } );
        });
      });

      done(null, dojosByCountry);
    });
  }

  function cmd_load(args, done) {
    seneca.make$(ENTITY_NS).load$(args.id, function(err, response) {
      if(err) return done(err);
      done(null, response);
    });
  }

  function cmd_find(args, done) {
    if(args.query){ args.query.deleted = 0 }
    seneca.make$(ENTITY_NS).load$(args.query, function(err, response) {
      if(err) return done(err);
      done(null, response);
    });
  }

  // user can only create X number of dojos
  function wrapCheckRateLimitCreateDojo(f) {
    return function(args, done) {
      seneca.make$(USER_DOJO_ENTITY_NS).list$({userId: args.user.id}, function(err, data) {
        if (err) return done(err);
        if (data.length >= options.limits.maxUserDojos) {
          return done(null, {ok: false, why: 'Rate limit exceeded, you have already created ' + data.length + ' dojos, the maximum allowed is ' + options.limits.maxUserDojos});
        }

        return f(args, done);
      });
    }
  };

  function cmd_create(args, done){
    var dojo = args.dojo, baseSlug;
    delete dojo.emailSubject;
    var usersDojosEntity = seneca.make$(USER_DOJO_ENTITY_NS);
    var user = args.user;
    var userDojo = {};

    dojo.creator = user.id;
    dojo.creatorEmail = user.email;
    dojo.created = new Date();
    dojo.verified = 0;

    var slugify = function(name) {
      return slug(name);
    };

    if (!dojo.geoPoint && dojo.coordinates) {
      var pair = dojo.coordinates.split(',').map(parseFloat);
      if (pair.length === 2 && _.isFinite(pair[0]) && _.isFinite(pair[1])) {
        dojo.geoPoint = {
          lat: pair[0],
          lon: pair[1]
        }
      }
    }

    baseSlug = _.chain([
      dojo.alpha2, dojo.admin1Name, dojo.placeName, dojo.name
    ]).compact().map(slugify).value().join('/').toLowerCase();

    async.waterfall([
      function (cb){
        if(dojo.geoPoint){
          seneca.act({role: 'cd-countries', cmd: 'reverse_geocode', coords: dojo.geoPoint}, function(err, res){
            if(err) console.error(err);
            if(!res) return cb();

            res = res[0];
            dojo.address1 = (res.streetNumber || '') + ' ' + (res.streetName || '');
            dojo.place = {'name': res.city};
            dojo.place = {'toponymName': res.city};
            dojo.state = {"toponymName": res.administrativeLevels.level1long};
            dojo.country = {"countryName": res.country, "alpha2": res.countryCode};
            dojo.admin1_code = res.administrativeLevels.level1short;
            dojo.admin1_name = res.administrativeLevels.level1long;
            dojo.admin2_code = res.administrativeLevels.level2short;
            dojo.admin2_name = res.administrativeLevels.level2long;
            cb()
          });
        } else {
          cb();
        }
      },
      function (cb){
        var urlSlug = {urlSlug: new RegExp('^' + baseSlug,  'i')};
        seneca.make$(ENTITY_NS).list$(urlSlug,function(err, dojos){
          if(err){
            return cb(err);
          }
          var urlSlugs = {};
          if(_.isEmpty(dojos)){
            return cb(null, baseSlug);
          }

          urlSlugs = _.pluck(dojos, 'urlSlug');
          var urlSlug = baseSlug;
          for (var idx = 1; urlSlugs.indexOf(urlSlug) !=  -1; urlSlug = baseSlug + '-' + idx, idx++);

          cb(null, urlSlug);
        });
      }, function (urlSlug, cb){
        dojo.urlSlug = urlSlug;
        seneca.make$(ENTITY_NS).save$(dojo, cb);
      }, function (dojo, cb){
        userDojo.owner = 1;
        userDojo.userTypes = ['champion'];
        //add user type from users profile.
        seneca.act({role: 'cd-profiles', cmd: 'list_query', query:{userId: user.id}}, function (err, response) {
          if(err) return cb(err);
          var profile = response[0];
          userDojo.userTypes.push(profile.userType);
          userDojo.userTypes = _.uniq(userDojo.userTypes);
          userDojo.userPermissions = [
            {title:'Dojo Admin', name:'dojo-admin'},
            {title:'Ticketing Admin', name:'ticketing-admin'}
          ];
          userDojo.deleted = 0;
          userDojo.userId = user.id;
          userDojo.dojoId = dojo.id;
          usersDojosEntity.save$(userDojo, function (err, userDojo) {
            if(err) return cb(err);
            cb(null, dojo);
          });
        });
      }], done);
  }

  function cmd_update(args, done){
    var dojo = args.dojo;
    var emailSubject = dojo.emailSubject || null;
    delete dojo.emailSubject;

    //load dojo before saving to get it's current state
    var dojoEnt = seneca.make$(ENTITY_NS);
    var dojoLeadsEnt = seneca.make$(DOJO_LEADS_ENTITY_NS);

    async.waterfall([
      function (done) {
        dojoEnt.load$(dojo.id, done);
      },
      /**
       * set 'Verfication' related stuff when verified changed, as follows:
       * - if verified changed to true, set verifiedAt and verifiedBy
       *      * when verified === 1 and if the dojo has no email set, create a new
       *        CD Organization(@<EMAIL>) email address for it
       * - if verified changed to false, clear verifiedAt and verifiedBy
      */
      function (currentDojoState, done) {

        if(dojo.coordinates && dojo.coordinates != currentDojoState.coordinates){
          var pair = dojo.coordinates.split(',').map(parseFloat);
          if (pair.length === 2 && _.isFinite(pair[0]) && _.isFinite(pair[1])) {
            dojo.geoPoint = {
              lat: pair[0],
              lon: pair[1]
            }
          }
          seneca.act({role: 'cd-countries', cmd: 'reverse_geocode', coords: dojo.geoPoint}, function(err, res){
            if(err) console.error(err);
            if(!res) return updateLogic();

            res = res[0];
            dojo.address1 = (res.streetNumber || '') + ' ' + (res.streetName || '');
            dojo.place = {'name': res.city};
            dojo.place = {'toponymName': res.city};
            dojo.state = {"toponymName": res.administrativeLevels.level1long};
            dojo.country = {"countryName": res.country, "alpha2": res.countryCode};
            dojo.admin1_code = res.administrativeLevels.level1short;
            dojo.admin1_name = res.administrativeLevels.level1long;
            dojo.admin2_code = res.administrativeLevels.level2short;
            dojo.admin2_name = res.administrativeLevels.level2long;
            updateLogic();
          });
        } else {
          updateLogic();
        }

        function updateLogic(){
          if (dojo.verified && dojo.verified ===1) {
            dojo.verifiedAt = new Date();
            dojo.verifiedBy = args.user.id;
            if(!dojo.dojoLeadId) return done(null, dojo);
            dojoLeadsEnt.load$(dojo.dojoLeadId, function(err, dojoLead) {
              if (err) {
                return done(err)
              }
              dojoLead = dojoLead.data$();
              dojoLead.completed = true;
              dojoLead.currentStep = 5;  // salesforce trigger to set the Dojo Listing Verified...
              //update dojoLead
              seneca.act({role: plugin, cmd: 'save_dojo_lead', dojoLead: dojoLead, dojoAction: "verify"}, function (err, dojoLead) {<commit_msg>handle geocoding errors more gracefully<commit_after>'use strict';

var util = require('util');
var _ = require('lodash');
var async = require('async');
var slug = require('slug');
var shortid = require('shortid');
var crypto = require('crypto');
var randomstring = require('randomstring');
var fs = require('fs');
var moment = require('moment');
var pg = require('pg');

var google = require('googleapis');
var admin = google.admin('directory_v1');
var path = require('path');
var fs = require('fs');
var DEFAULT_LANG = 'en_US';

module.exports = function (options) {
  var seneca = this;
  var plugin = 'cd-dojos';
  var ENTITY_NS = 'cd/dojos';
  var USER_DOJO_ENTITY_NS = "cd/usersdojos";
  var STATS_ENTITY_NS = "cd/stats";
  var DOJO_LEADS_ENTITY_NS = "cd/dojoleads";
  var CDF_ADMIN = 'cdf-admin';
  var DEFAULT_INVITE_USER_TYPE = 'mentor';
  var setupDojoSteps = require('./data/setup_dojo_steps');
  var dojoConfig = require('./data/dojos_config');
  var so = seneca.options();

  seneca.add({role: plugin, cmd: 'search'}, cmd_search);
  seneca.add({role: plugin, cmd: 'list'}, cmd_list);
  seneca.add({role: plugin, cmd: 'load'}, cmd_load);
  seneca.add({role: plugin, cmd: 'find'}, cmd_find);
  seneca.add({role: plugin, cmd: 'create'}, wrapCheckRateLimitCreateDojo(cmd_create));
  seneca.add({role: plugin, cmd: 'update'}, wrapDojoExists(wrapDojoPermissions(cmd_update)));
  seneca.add({role: plugin, cmd: 'delete'}, wrapDojoExists(wrapDojoPermissions(cmd_delete)));
  seneca.add({role: plugin, cmd: 'my_dojos'}, cmd_my_dojos);
  seneca.add({role: plugin, cmd: 'dojos_count'}, cmd_dojos_count);
  seneca.add({role: plugin, cmd: 'dojos_by_country'}, cmd_dojos_by_country);
  seneca.add({role: plugin, cmd: 'dojos_state_count'}, cmd_dojos_state_count);
  seneca.add({role: plugin, cmd: 'bulk_update'}, cmd_bulk_update);
  seneca.add({role: plugin, cmd: 'bulk_delete'}, cmd_bulk_delete);
  seneca.add({role: plugin, cmd: 'get_stats'}, wrapCheckCDFAdmin(cmd_get_stats));
  seneca.add({role: plugin, cmd: 'save_dojo_lead'}, cmd_save_dojo_lead);
  seneca.add({role: plugin, cmd: 'update_dojo_lead'}, cmd_save_dojo_lead);
  seneca.add({role: plugin, cmd: 'load_user_dojo_lead'}, cmd_load_user_dojo_lead);
  seneca.add({role: plugin, cmd: 'load_dojo_lead'}, cmd_load_dojo_lead);
  seneca.add({role: plugin, cmd: 'load_setup_dojo_steps'}, cmd_load_setup_dojo_steps);
  seneca.add({role: plugin, cmd: 'load_usersdojos'}, cmd_load_users_dojos);
  seneca.add({role: plugin, cmd: 'load_dojo_users'}, cmd_load_dojo_users);
  seneca.add({role: plugin, cmd: 'send_email'}, cmd_send_email);
  seneca.add({role: plugin, cmd: 'generate_user_invite_token'}, cmd_generate_user_invite_token);
  seneca.add({role: plugin, cmd: 'accept_user_invite'}, cmd_accept_user_invite);
  seneca.add({role: plugin, cmd: 'request_user_invite'}, cmd_request_user_invite);
  seneca.add({role: plugin, cmd: 'load_dojo_champion'}, cmd_load_dojo_champion);
  seneca.add({role: plugin, cmd: 'accept_user_request'}, cmd_accept_user_request);
  seneca.add({role: plugin, cmd: 'dojos_for_user'}, cmd_dojos_for_user);
  seneca.add({role: plugin, cmd: 'save_usersdojos'}, cmd_save_usersdojos);
  seneca.add({role: plugin, cmd: 'remove_usersdojos'}, cmd_remove_usersdojos);
  seneca.add({role: plugin, cmd: 'get_user_types'}, cmd_get_user_types);
  seneca.add({role: plugin, cmd: 'get_user_permissions'}, cmd_get_user_permissions);
  seneca.add({role: plugin, cmd: 'create_dojo_email'}, cmd_create_dojo_email);
  seneca.add({role: plugin, cmd: 'search_dojo_leads'}, cmd_search_dojo_leads);
  seneca.add({role: plugin, cmd: 'uncompleted_dojos'}, cmd_uncompleted_dojos);
  seneca.add({role: plugin, cmd: 'get_dojo_config'}, cmd_get_dojo_config);
  seneca.add({role: plugin, cmd: 'load_dojo_admins'}, cmd_load_dojo_admins);
  seneca.add({role: plugin, cmd: 'update_founder'}, cmd_update_dojo_founder);
  seneca.add({role: plugin, cmd: 'search_nearest_dojos'}, cmd_search_nearest_dojos);
  seneca.add({role: plugin, cmd: 'search_bounding_box'}, cmd_search_bounding_box);
  seneca.add({role: plugin, cmd: 'list_query'}, cmd_list_query);

  function cmd_update_dojo_founder(args, done){
    var founder = args.founder;
    var seneca = this;
    if(_.isEmpty(founder)){
      return done(new Error('Founder is empty'));
    }

    async.waterfall([
      isCDFAdmin,
      getPreviousFounderUserDojo,
      updatePreviousFounderUserDojo,
      getCurrentFounderUserDojo,
      updateOrCreateUserDojo
      ],done);

    function isCDFAdmin(done){
      var userId = args.user.id;

      seneca.act({role: 'cd-users', cmd: 'load', id: userId}, function(err, user){
        if(err){
          return done(err);
        }

        if(!_.contains(user.roles, 'cdf-admin')){
          return done(new Error('Unauthorized'));
        }

        return done();
      });
    }

    function getPreviousFounderUserDojo(done){
      var query = {};

      query.userId = founder.previousFounderId;
      query.owner = 1;
      query.dojoId = founder.dojoId;

      seneca.act({role: 'cd-dojos', cmd: 'load_usersdojos', query: query}, function(err, usersDojos){
        if(err){
          return done(err);
        }

        var userDojo = usersDojos[0];

        if(_.isEmpty(userDojo)){
          return done(new Error('Cannot find previous founder'));
        }

        return done(null, userDojo);

      });
    }

    function updatePreviousFounderUserDojo(userDojo, done){
      if(_.isEmpty(userDojo)){
        return done();
      }

      userDojo.owner = 0;

      seneca.act({role: 'cd-dojos', cmd: 'save_usersdojos', userDojo: userDojo}, done);
    }

    function getCurrentFounderUserDojo(prevFoundersUserDojo, done){
      var query = {};

      query.userId = founder.id;
      query.dojoId = founder.dojoId;

      seneca.act({role: 'cd-dojos', cmd: 'load_usersdojos', query: query}, function(err, currentFounder){
        if(err){
          return done(err);
        }

        return done(null, currentFounder[0]);
      });
    }

    function updateOrCreateUserDojo(userDojo, done){
      if(_.isEmpty(userDojo)){
        userDojo = {};
        userDojo.dojoId = founder.dojoId;
        userDojo.userId = founder.id;
      }

      if(!userDojo.userTypes){
        userDojo.userTypes = [];
      }

      if(!_.contains(userDojo.userTypes, 'champion')){
        userDojo.userTypes.push('champion');
      }

      userDojo.owner = 1;

      seneca.act({role: 'cd-dojos', cmd: 'save_usersdojos', userDojo: userDojo}, done);
    }
  }

  function cmd_create_dojo_email(args, done) {
    if (!args.dojo) {
      return done('Dojo data is missing.');
    }

    if(options['google-api'].enabled === false) {
      return done();
    }

    //check if Google API private key file exists
    if (!fs.existsSync(options['google-api'].keyFile)) {
      return done("Google API private key not found", null);
    }

    var jwt = new google.auth.JWT(
      options['google-api'].email,
      options['google-api'].keyFile,
      "",
      options['google-api'].scopes,
      options['google-api'].subject
    );

    jwt.authorize(function (err, data) {
      if (err) {
        return done(err)
      }

      getGoogleUserData(args.dojo, function (err, res) {

        var googleNewAccountData = res.userData;
        var tempPass = googleNewAccountData.tempPass;
        googleNewAccountData = _.omit(googleNewAccountData, 'tempPass');
        var dojo = res.dojo;

        // Insert user
        admin.users.insert({
          resource: googleNewAccountData,
          auth: jwt
        }, function (err, data) {
          if (err) {
            return done(err)
          }

          seneca.act({role: 'cd-users', cmd: 'load', id: dojo.creator}, function (err, dojoCreator) {
            if (err) {
              return done(err)
            }

            var locality = args.locality || 'en_us';
            var code = 'google-email-pass-' + locality.toLowerCase();

            var templates = {};

            try {
              templates.html = fs.statSync(path.join(so.mail.folder, code, 'html.ejs'));
              templates.text = fs.statSync(path.join(so.mail.folder, code, 'text.ejs'));
            } catch(err){
              code = 'google-email-pass-' + DEFAULT_LANG;
            }

            //send dojo creator an email with dojo's newly created email address and it's temp password
            var payload = {
              to: dojoCreator.email,
              code: code,
              content: {temp_pass: <PASSWORD>, dojo: dojo.name, email: googleNewAccountData.primaryEmail}
            };
            seneca.act({role: plugin, cmd: 'send_email', payload: payload}, function (err, res) {
              if (err) {
                return done(err)
              }

              done(null, data);
            });
          });
        });
      });
    });
  }

  function getGoogleUserData(dojo, done) {
    var userData = {};

    seneca.act('role:cd-dojos,cmd:load', {id: dojo.id}, function (err, res) {
      if (err) {
        return done(err)
      }

      dojo = res.data$();

      //this can look something like this: <EMAIL>
      var primaryEmail = _.last(dojo.urlSlug.split('/')).concat('.', dojo.alpha2.toLowerCase(), '@coderdojo.com');

      if (process.env.ENVIRONMENT === 'development') {
        primaryEmail = 'dev-' + primaryEmail;
      }

      //required user data
      userData.name = {
        familyName: dojo.name, //this can be changed with the champion name & family name
        givenName: dojo.placeName || dojo.address1
      };
      var pass = <PASSWORD>(8);
      userData.tempPass = pass;
      userData.password = <PASSWORD>(<PASSWORD>);//use default pass for now; replace this with random pass when finished
      userData.hashFunction = "SHA-1";
      userData.primaryEmail = primaryEmail;
      userData.changePasswordAtNextLogin = true;
      userData.emails = [
        {
          "address": "<EMAIL>",
          "type": "other",
          "customType": "",
          "primary": false
        }
      ];
      userData.organizations = [
        {
          "name": "nearform_test",
          "title": "champion",
          "primary": true,
          "type": "school",
          "description": "new test dojo",
          domain: 'coderdojo.org'
        }
      ];

      return done(null, {userData: userData, dojo: dojo});
    });
  }

  function sha1sum(input){
    return crypto.createHash('sha1').update(JSON.stringify(input)).digest('hex')
  }

  function cmd_search_dojo_leads(args, done){
    var dojoLeadsEntity = seneca.make$(DOJO_LEADS_ENTITY_NS);
    dojoLeadsEntity.list$(args.query, done);
  }

  function cmd_uncompleted_dojos(args, done) {
    var query = {creator: args.user.id, deleted: 0};
    seneca.act({role: plugin, cmd: 'search', query: query}, function(err, dojos){
      if(err){ return done(err) }
      if(dojos.length > 0) {
        var uncompletedDojos = [];
        async.each(dojos, function (dojo, cb) {
          //check if dojo "setup dojo step is completed"
          seneca.act({role: plugin, cmd: 'load_dojo_lead', id: dojo.dojoLeadId}, function (err, dojoLead) {
            if(err) return cb(err);
            if (dojoLead) {
              var isCompleted = checkSetupYourDojoIsCompleted(dojoLead);
              if (!isCompleted) {
                uncompletedDojos.push(dojo);
              }
            }
            return cb();
          });
        }, function (err) {
          if (err) return done(err);
          return done(null, uncompletedDojos);
        });
      } else return done(null);
    });
  }

  function checkSetupYourDojoIsCompleted(dojoLead){
    var isDojoCompleted = true;

    if(dojoLead.application.setupYourDojo) {
      var setupYourDojo = dojoLead.application.setupYourDojo;
      var checkboxes = _.flatten(_.pluck(setupDojoSteps, 'checkboxes'));

      _.each(checkboxes, function(checkbox){
        if (!setupYourDojo[checkbox.name]) {
          isDojoCompleted = false;
        }

        if(checkbox.textField){
          if(!setupYourDojo[checkbox.name + "Text"]) {
            isDojoCompleted = false;
          }
        }
      });
    }

    return isDojoCompleted;
  }

  function isUserChampionAndDojoAdmin(query, requestingUser, done) {

    if(_.contains(requestingUser.roles, 'cdf-admin')) {
      return done(null, true);
    }

    seneca.act({role:plugin, cmd:'load_usersdojos', query:query}, function (err, response) {
      if(err) return done(err);
      var userDojo = response[0];
      var isDojoChampion  = _.contains(userDojo.userTypes, 'champion');
      var isDojoAdmin = _.find(userDojo.userPermissions, function(userPermission) {
                      return userPermission.name === 'dojo-admin';
                    });
      if(isDojoChampion && isDojoAdmin) return done(null, true);
      return done(null, false);
    });
  }

  function cmd_search(args, done) {
    var usersdojos_ent = seneca.make$(USER_DOJO_ENTITY_NS);
    async.waterfall([
      function (done) {
        var query = args.query;
        if(query.name) query.name = new RegExp(query.name, 'i');
        if(query.email) query.email = new RegExp(query.email, 'i');
        if(query.creatorEmail) query.creatorEmail = new RegExp(query.creatorEmail, 'i');
        seneca.act({role: plugin, cmd: 'list_query', query: query}, done);
      },
      function (searchResult, done) {
        var dojos = searchResult;
        async.each(dojos, function (dojo, cb){
          seneca.act({role: plugin, cmd: 'load_usersdojos', query: {dojoId: dojo.id, owner: 1}},
            function(err, userDojos){
              if(err) return cb(err);
              if(userDojos.length < 1) return cb();
              var userIds = _.pluck(userDojos, 'userId');
              seneca.act({role: 'cd-users', cmd: 'list', ids: userIds}, function(err, users){
                if(err) return cb(err);
                dojo.creators = _.map(users, function(user){
                  return {email: user.email, id: user.id};
                });
                return cb(null, dojo);
              });
            });
          }, function (err) {
            if(err) return done(err);
            return done(null, searchResult);
          });
      },
      function (searchResult, done) {
        var userIds = _.chain(searchResult).pluck('creators').flatten().pluck('id').uniq().value();
        async.waterfall([
          function(done) {
            seneca.act({role:'cd-agreements', cmd:'list', userIds: userIds}, done);
          },
          function(agreements, done) {
            agreements = _.indexBy(agreements, 'userId');
            _.each(searchResult, function(dojo) {
              dojo.agreements = [];
              _.each(dojo.creators, function(creator){
                creator.agreements = [];
                if (agreements[creator.id]) {
                  creator.agreements = agreements[creator.id].agreements;
                }
              });
            });
            return done(null, searchResult);
          }
        ], done);
      },
      function(searchResult, done) {
        return done(null, searchResult);
      }
    ], function(err, res) {
      if (err) {
        return done(err);
      }
      return done(null, res);
    });
  }

  function cmd_dojos_state_count(args, done) {
    var country = args.country;
    var countData = {};

    seneca.make$(ENTITY_NS).list$({limit$:'NULL', alpha2:country, deleted: 0, verified: 1}, function(err, response) {
      if(err) return done(err);
      countData[country] = {};
      _.each(response, function(dojo) {
        if(dojo.coordinates && dojo.stage !== 4) {
          if(!countData[dojo.alpha2][dojo.admin1Name]) countData[dojo.alpha2][dojo.admin1Name] = {total:0};
          countData[dojo.alpha2][dojo.admin1Name].total += 1;
          countData[dojo.alpha2][dojo.admin1Name].latitude = dojo.coordinates.split(',')[0];
          countData[dojo.alpha2][dojo.admin1Name].longitude = dojo.coordinates.split(',')[1];
        }
      });
      done(null, countData);
    });

  }

  function cmd_dojos_by_country(args, done) {
    var countries = args.countries;
    var dojos = [];
    async.each(Object.keys(countries), function(country, cb) {
      seneca.act({role:plugin, cmd:'list'}, {query:{alpha2:country}}, function(err, response) {
        if(err) return done(err);
        dojos.push(response);
        cb();
      });
    }, function() {
      dojos = _.sortBy(dojos, function(dojo) { return Object.keys(dojo)[0]; });
      done(null, dojos);
    });
  }

  function cmd_dojos_count(args, done) {
    async.waterfall([
      getDojos,
      getCountries,
      getDojoCount
    ], done);

    function getDojos(done) {
      var dojos = [];
      var query = {limit$:'NULL'};
      query.deleted = 0;
      query.verified = 1;
      seneca.make$(ENTITY_NS).list$(query, function(err, response) {
        if(err) return response;
        async.each(response, function(dojo, cb) {
          if(dojo.stage !== 4) {
            dojos.push(dojo);
          }

          cb();
        }, function() {
          done(null, dojos);
        });
      });
    }

    function getCountries(dojos, done) {
      seneca.act({role:'cd-countries', cmd:'countries_continents'}, function(err, response) {
        if(err) return done(err);
        done(null, dojos, response);
      });
    }

    function getDojoCount(dojos, countries, done) {
      var countData = {dojos:{continents:{}}};
      _.each(dojos, function(dojo) {
        if(countries.countries[dojo.alpha2]) {
          var continent = countries.countries[dojo.alpha2].continent;
          if(!countData.dojos.continents[continent]) countData.dojos.continents[continent] = {total:0, countries:{}};
          countData.dojos.continents[continent].total += 1;
          if(!countData.dojos.continents[continent].countries[dojo.alpha2]) countData.dojos.continents[continent].countries[dojo.alpha2] = {total:0};
          countData.dojos.continents[continent].countries[dojo.alpha2].total += 1;
        }
      });
      done(null, countData);
    }
  }

  function cmd_list(args, done) {
    var query = args.query || {};
    query.limit$ = 'NULL';
    query.verified = 1;
    query.deleted = 0;
    seneca.make$(ENTITY_NS).list$(query, function(err, response) {
      if(err) return done(err);

      var dojosByCountry = {};
      response = _.sortBy(response, 'countryName');
      _.each(response, function(dojo) {
        if(dojo.stage !== 4) {
          if(!dojosByCountry[dojo.countryName]) {
            dojosByCountry[dojo.countryName] = {};
            dojosByCountry[dojo.countryName].states = {};
            if(!dojosByCountry[dojo.countryName].states[dojo.admin1Name]) dojosByCountry[dojo.countryName].states[dojo.admin1Name] = [];
            dojosByCountry[dojo.countryName].states[dojo.admin1Name].push(dojo);
          } else {
            if(!dojosByCountry[dojo.countryName].states[dojo.admin1Name]) dojosByCountry[dojo.countryName].states[dojo.admin1Name] = [];
            dojosByCountry[dojo.countryName].states[dojo.admin1Name].push(dojo);
          }
        }
      });

      var countries = Object.keys(dojosByCountry);
      _.each(countries, function(countryName) {
        var states = Object.keys(dojosByCountry[countryName].states);
        _.each(states, function(state) {
          dojosByCountry[countryName].states[state] = _.sortBy(dojosByCountry[countryName].states[state], function(dojo) { return dojo.name; } );
        });
      });

      done(null, dojosByCountry);
    });
  }

  function cmd_load(args, done) {
    seneca.make$(ENTITY_NS).load$(args.id, function(err, response) {
      if(err) return done(err);
      done(null, response);
    });
  }

  function cmd_find(args, done) {
    if(args.query){ args.query.deleted = 0 }
    seneca.make$(ENTITY_NS).load$(args.query, function(err, response) {
      if(err) return done(err);
      done(null, response);
    });
  }

  // user can only create X number of dojos
  function wrapCheckRateLimitCreateDojo(f) {
    return function(args, done) {
      seneca.make$(USER_DOJO_ENTITY_NS).list$({userId: args.user.id}, function(err, data) {
        if (err) return done(err);
        if (data.length >= options.limits.maxUserDojos) {
          return done(null, {ok: false, why: 'Rate limit exceeded, you have already created ' + data.length + ' dojos, the maximum allowed is ' + options.limits.maxUserDojos});
        }

        return f(args, done);
      });
    }
  };

  function cmd_create(args, done){
    var dojo = args.dojo, baseSlug;
    delete dojo.emailSubject;
    var usersDojosEntity = seneca.make$(USER_DOJO_ENTITY_NS);
    var user = args.user;
    var userDojo = {};

    dojo.creator = user.id;
    dojo.creatorEmail = user.email;
    dojo.created = new Date();
    dojo.verified = 0;

    var slugify = function(name) {
      return slug(name);
    };

    if (!dojo.geoPoint && dojo.coordinates) {
      var pair = dojo.coordinates.split(',').map(parseFloat);
      if (pair.length === 2 && _.isFinite(pair[0]) && _.isFinite(pair[1])) {
        dojo.geoPoint = {
          lat: pair[0],
          lon: pair[1]
        }
      }
    }

    baseSlug = _.chain([
      dojo.alpha2, dojo.admin1Name, dojo.placeName, dojo.name
    ]).compact().map(slugify).value().join('/').toLowerCase();

    async.waterfall([
      function (cb){
        if(dojo.geoPoint){
          seneca.act({role: 'cd-countries', cmd: 'reverse_geocode', coords: dojo.geoPoint, fatal:false}, function(err, res){
            if(err){
              console.log(err);
              return cb();
            }
            if(!res){
              console.error('No result when reverse geocoding');
              return cb();
            }

            res = res[0];
            dojo.address1 = (res.streetNumber || '') + ' ' + (res.streetName || '');
            dojo.place = {'name': res.city};
            dojo.place = {'toponymName': res.city};
            dojo.state = {"toponymName": res.administrativeLevels.level1long};
            dojo.country = {"countryName": res.country, "alpha2": res.countryCode};
            dojo.admin1_code = res.administrativeLevels.level1short;
            dojo.admin1_name = res.administrativeLevels.level1long;
            dojo.admin2_code = res.administrativeLevels.level2short;
            dojo.admin2_name = res.administrativeLevels.level2long;
            cb()
          });
        } else {
          cb();
        }
      },
      function (cb){
        var urlSlug = {urlSlug: new RegExp('^' + baseSlug,  'i')};
        seneca.make$(ENTITY_NS).list$(urlSlug,function(err, dojos){
          if(err){
            return cb(err);
          }
          var urlSlugs = {};
          if(_.isEmpty(dojos)){
            return cb(null, baseSlug);
          }

          urlSlugs = _.pluck(dojos, 'urlSlug');
          var urlSlug = baseSlug;
          for (var idx = 1; urlSlugs.indexOf(urlSlug) !=  -1; urlSlug = baseSlug + '-' + idx, idx++);

          cb(null, urlSlug);
        });
      }, function (urlSlug, cb){
        dojo.urlSlug = urlSlug;
        seneca.make$(ENTITY_NS).save$(dojo, cb);
      }, function (dojo, cb){
        userDojo.owner = 1;
        userDojo.userTypes = ['champion'];
        //add user type from users profile.
        seneca.act({role: 'cd-profiles', cmd: 'list_query', query:{userId: user.id}}, function (err, response) {
          if(err) return cb(err);
          var profile = response[0];
          userDojo.userTypes.push(profile.userType);
          userDojo.userTypes = _.uniq(userDojo.userTypes);
          userDojo.userPermissions = [
            {title:'Dojo Admin', name:'dojo-admin'},
            {title:'Ticketing Admin', name:'ticketing-admin'}
          ];
          userDojo.deleted = 0;
          userDojo.userId = user.id;
          userDojo.dojoId = dojo.id;
          usersDojosEntity.save$(userDojo, function (err, userDojo) {
            if(err) return cb(err);
            cb(null, dojo);
          });
        });
      }], done);
  }

  function cmd_update(args, done){
    var dojo = args.dojo;
    var emailSubject = dojo.emailSubject || null;
    delete dojo.emailSubject;

    //load dojo before saving to get it's current state
    var dojoEnt = seneca.make$(ENTITY_NS);
    var dojoLeadsEnt = seneca.make$(DOJO_LEADS_ENTITY_NS);

    async.waterfall([
      function (done) {
        dojoEnt.load$(dojo.id, done);
      },
      /**
       * set 'Verfication' related stuff when verified changed, as follows:
       * - if verified changed to true, set verifiedAt and verifiedBy
       *      * when verified === 1 and if the dojo has no email set, create a new
       *        CD Organization(@coder<EMAIL>) email address for it
       * - if verified changed to false, clear verifiedAt and verifiedBy
      */
      function (currentDojoState, done) {

        if(dojo.coordinates && dojo.coordinates != currentDojoState.coordinates){
          var pair = dojo.coordinates.split(',').map(parseFloat);
          if (pair.length === 2 && _.isFinite(pair[0]) && _.isFinite(pair[1])) {
            dojo.geoPoint = {
              lat: pair[0],
              lon: pair[1]
            }
          }
          seneca.act({role: 'cd-countries', cmd: 'reverse_geocode', coords: dojo.geoPoint}, function(err, res){
            if(err){
              console.error(err);
              return updateLogic();
            }
            if(!res){
              console.error('No result when reverse geocoding');
              return updateLogic();
            }

            res = res[0];
            dojo.address1 = (res.streetNumber || '') + ' ' + (res.streetName || '');
            dojo.place = {'name': res.city};
            dojo.place = {'toponymName': res.city};
            dojo.state = {"toponymName": res.administrativeLevels.level1long};
            dojo.country = {"countryName": res.country, "alpha2": res.countryCode};
            dojo.admin1_code = res.administrativeLevels.level1short;
            dojo.admin1_name = res.administrativeLevels.level1long;
            dojo.admin2_code = res.administrativeLevels.level2short;
            dojo.admin2_name = res.administrativeLevels.level2long;
            updateLogic();
          });
        } else {
          updateLogic();
        }

        function updateLogic(){
          if (dojo.verified && dojo.verified ===1) {
            dojo.verifiedAt = new Date();
            dojo.verifiedBy = args.user.id;
            if(!dojo.dojoLeadId) return done(null, dojo);
            dojoLeadsEnt.load$(dojo.dojoLeadId, function(err, dojoLead) {
              if (err) {
                return done(err)
              }
              dojoLead = dojoLead.data$();
              dojoLead.completed = true;
              dojoLead.currentStep = 5;  // salesforce trigger to set the Dojo Listing Verified...
              //update dojoLead
              seneca.act({role: plugin, cmd: 'save_dojo_lead', dojoLead: dojoLead, dojoAction: "verify"}, function (err, dojoLead) {
{'fraction_non_alphanumeric': 0.14422647409172126, 'fraction_numerical': 0.003871351995235259, 'mean_word_length': 2.527608167553017, 'pattern_counts': {'":': 30, '<': 20, '<?xml version=': 0, '>': 22, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4677570', 'n_tokens_mistral': 17495, 'n_tokens_neox': 17168, 'n_words': 3287}