<commit_before>'use strict';

var remove      = require('es5-ext/array/#/remove')
  , d           = require('d')
  , ee          = require('event-emitter')
  , deferred    = require('deferred')
  , memoize     = require('memoizee')
  , once        = require('timers-ext/once')
  , dbDriver    = require('mano').dbDriver
  , getIndexMap = require('./get-observable-index-map')

  , defineProperty = Object.defineProperty
  , compareStamps = function (a, b) { return a.stamp - b.stamp; };

module.exports = memoize(function (set, sortIndexName) {
	var arr = ee([]), itemsMap = getIndexMap(sortIndexName)
	  , count = 0, isInitialized = false, def = deferred(), setListener, itemsListener;
	arr.emitChange = once(arr.emit.bind(arr, 'change'));
	var add = function (ownerId) {
		return deferred(itemsMap[ownerId] || dbDriver.getComputed(ownerId + '/' + sortIndexName))
			.aside(function (data) {
				if (!set.has(ownerId)) return;
				if (!itemsMap[ownerId]) itemsMap[ownerId] = { id: ownerId, stamp: data.stamp };
				arr.push(itemsMap[ownerId]);
				if (def.resolved) {
					arr.sort(compareStamps);
					arr.emitChange();
				}
			});
	};
	set.on('change', setListener = function (event) {
		if (event.type === 'add') {
			add(event.value).done();
		} else {
			remove.call(arr, itemsMap[event.value]);
			arr.emitChange();
		}
	});
	itemsMap.on('update', itemsListener = function (event) {
		if (!set.has(event.ownerId)) return;
		if (def.resolved) {
			arr.sort(compareStamps);
			arr.emitChange();
		}
	});
	set.forEach(function (ownerId) {
		++count;
		add(ownerId).done(function () {
			if (!--count && isInitialized) def.resolve(arr.sort(compareStamps));
		});
	});
	isInitialized = true;
	if (!count) def.resolve(arr.sort(compareStamps));
	defineProperty(set, '_dispose', d(function () {
		set.off(setListener);
		itemsMap.off(itemsListener);
	}));
	return def.promise;
});
<commit_msg>Fix require path<commit_after>'use strict';

var remove      = require('es5-ext/array/#/remove')
  , d           = require('d')
  , ee          = require('event-emitter')
  , deferred    = require('deferred')
  , memoize     = require('memoizee')
  , once        = require('timers-ext/once')
  , dbDriver    = require('mano').dbDriver
  , getIndexMap = require('./get-observable-sort-index-map')

  , defineProperty = Object.defineProperty
  , compareStamps = function (a, b) { return a.stamp - b.stamp; };

module.exports = memoize(function (set, sortIndexName) {
	var arr = ee([]), itemsMap = getIndexMap(sortIndexName)
	  , count = 0, isInitialized = false, def = deferred(), setListener, itemsListener;
	arr.emitChange = once(arr.emit.bind(arr, 'change'));
	var add = function (ownerId) {
		return deferred(itemsMap[ownerId] || dbDriver.getComputed(ownerId + '/' + sortIndexName))
			.aside(function (data) {
				if (!set.has(ownerId)) return;
				if (!itemsMap[ownerId]) itemsMap[ownerId] = { id: ownerId, stamp: data.stamp };
				arr.push(itemsMap[ownerId]);
				if (def.resolved) {
					arr.sort(compareStamps);
					arr.emitChange();
				}
			});
	};
	set.on('change', setListener = function (event) {
		if (event.type === 'add') {
			add(event.value).done();
		} else {
			remove.call(arr, itemsMap[event.value]);
			arr.emitChange();
		}
	});
	itemsMap.on('update', itemsListener = function (event) {
		if (!set.has(event.ownerId)) return;
		if (def.resolved) {
			arr.sort(compareStamps);
			arr.emitChange();
		}
	});
	set.forEach(function (ownerId) {
		++count;
		add(ownerId).done(function () {
			if (!--count && isInitialized) def.resolve(arr.sort(compareStamps));
		});
	});
	isInitialized = true;
	if (!count) def.resolve(arr.sort(compareStamps));
	defineProperty(set, '_dispose', d(function () {
		set.off(setListener);
		itemsMap.off(itemsListener);
	}));
	return def.promise;
});
<|endoftext|>


<commit_before>/* global process, Promise */

/**
 * Module dependencies
 */

var chalk = require('chalk')
var Table = require('cli-table')

/**
 * Role commands
 */

function registerRole (cli, options, done) {
  var roleCmd = cli.command('role')

  /**
   * API Error Response Handler
   */

  function apiError (err) {
    if (err.statusCode && [401, 403].indexOf(err.statusCode) !== -1) {
      cli.log.error('Please login to the issuer.')
    } else {
      console.log(err)
    }

    process.exit(1)
  }

  /**
   * Display Field
   */

  function displayField (key, value, format) {
    if (typeof value !== 'undefined' && !(Array.isArray(value) && value.length === 0)) {
      // determine the number of tab characters to use
      var tabs = '\t'
      var diff = 24 - key.length
      if (diff > 8) { tabs += '\t' }
      if (diff > 16) { tabs += '\t' }

      // format the value
      if (Array.isArray(value)) {
        value = value.map(function (item) {
          return (format) ? chalk[format](item) : item
        }).join('\n\t\t\t')
      } else {
        value = (format) ? chalk[format](value) : value
      }

      cli.log(key + tabs + value)
    }
  }

  /**
   * Display Role
   */

  function displayRole (data) {
    cli.log()
    displayField('Name', data.name, 'bold')
    displayField('Created', data.created && new Date(data.created))
    displayField('Modified', data.modified && new Date(data.modified))
  }

  roleCmd
    .handler(function (data, flags, done) {
      cli.log('Usage:')
      cli.log(
        '  nvl role:register [<id>] [--issuer | -i <issuer id>] [--name | -n <name>]'
      )

      cli.log('  nvl role:list [--issuer | -i <issuer id>]')

      cli.log('  nvl role:info [<id>] [--issuer | -i <issuer id>]')

      cli.log(
        '  nvl role:update [<id>] [--issuer | -i <issuer id>] [--name | -n <name>]'
      )

      cli.log('  nvl role:delete [<id>] [--issuer | -i <issuer id>]')

      done()
    })

  /**
   * Role registration
   */

  roleCmd
    .task('register')
    .handler(function (data, flags, done) {
      cli.issuers.prompt(flags['issuer'] || flags.i, function (err, issuer) {
        if (err) {
          cli.log.error(err)
          process.exit(1)
        }

        try {
          var anvil = cli.client.create(issuer)
        } catch (e) {
          cli.log.error(e)
          process.exit(1)
        }

        anvil.discover()
          .then(function (configuration) {
            cli.prompt([
              {
                type: 'input',
                name: 'name',
                message: 'Name',
                value: flags['name'] || flags.n,
                trim: true
              }
            ], function (answers) {
              // register the role
              anvil.roles.create(answers, { token: anvil.tokens.access_token })
                .then(function (registration) {
                  displayRole(registration)
                  done()
                })
                // registration error
                .catch(apiError)
            })
          })
          // discover error
          .catch(apiError)
      })
    })

  /**
   * Role list
   */

  roleCmd
    .task('list')
    .handler(function (data, flags, done) {
      cli.issuers.prompt(flags['issuer'] || flags.i, function (err, issuer) {
        if (err) {
          cli.log.error(err)
          process.exit(1)
        }

        try {
          var anvil = cli.client.create(issuer)
        } catch (e) {
          cli.log.error(e)
          process.exit(1)
        }

        anvil.discover()
          .then(function (configuration) {
            return anvil.roles.list({ token: anvil.tokens.access_token })
          })
          .then(function (roles) {
            var table = new Table({
              head: ['Name']
            })

            roles.forEach(function (role) {
              var name = chalk.bold(role.name)
              table.push([name])
            })

            cli.log(table.toString())
            done()
          })
          .catch(apiError)
      })
    })

  /**
   * Role info
   */

  roleCmd
    .task('info')
    .handler(function (data, flags, done) {
      cli.issuers.prompt(flags['issuer'] || flags.i, function (err, issuer) {
        if (err) {
          cli.log.error(err)
          process.exit(1)
        }

        try {
          var anvil = cli.client.create(issuer)
        } catch (e) {
          cli.log.error(e)
          process.exit(1)
        }

        anvil.discover()
          .then(function (configuration) {
            return new Promise(function (resolve, reject) {
              if (data[0]) {
                resolve(data[0])
              } else {
                anvil.roles.list({ token: anvil.tokens.access_token })
                  .then(function (roles) {
                    cli.prompt([
                      {
                        type: 'list',
                        name: 'roleName',
                        message: 'Select a role',
                        choices: roles.map(function (role) {
                          return {
                            name: role.name,
                            value: role.name
                          }
                        })
                      }
                    ], function (answers) {
                      resolve(answers.roleName)
                    })
                  })
                  .catch(reject)
              }
            })
          })
          .then(function (roleName) {
            return anvil.roles.get(roleName, { token: anvil.tokens.access_token })
          })
          .then(function (data) {
            displayRole(data)
            done()
          })
          .catch(apiError)
      })
    })

  /**
   * Role update
   */

  roleCmd
    .task('update')
    .handler(function (data, flags, done) {
      cli.issuers.prompt(flags['issuer'] || flags.i, function (err, issuer) {
        if (err) {
          cli.log.error(err)
          process.exit(1)
        }

        try {
          var anvil = cli.client.create(issuer)
        } catch (e) {
          cli.log.error(e)
          process.exit(1)
        }

        anvil.discover()
          .then(function (configuration) {
            return new Promise(function (resolve, reject) {
              if (data[0]) {
                resolve(data[0])
              } else {
                anvil.roles.list({ token: anvil.tokens.access_token })
                  .then(function (roles) {
                    cli.prompt([
                      {
                        type: 'list',
                        name: 'roleName',
                        message: 'Select a role',
                        choices: roles.map(function (role) {
                          return {
                            name: role.name,
                            value: role.name
                          }
                        })
                      }
                    ], function (answers) {
                      resolve(answers.roleName)
                    })
                  })
                  .catch(reject)
              }
            })
          })
          .then(function (roleName) {
            return anvil.roles.get(roleName, { token: anvil.tokens.access_token })
          })
          .then(function (role) {
            return new Promise(function (resolve, reject) {
              cli.prompt([
                {
                  type: 'input',
                  name: 'name',
                  message: 'Name',
                  value: flags['name'] || flags.n,
                  default: role.name,
                  trim: true
                }
              ], function (update) {
                resolve({
                  role: role,
                  update: update
                })
              })
            })
          })
          .then(function (data) {
            // Don't allow updating of required roles
            if (['authority'].indexOf(data.role.name) !== -1) {
              throw new Error('This role cannot be updated')
            }

            return anvil.roles.update(data.role.name, data.update, { token: anvil.tokens.access_token })
          })
          .then(function (data) {
            displayRole(data)
            done()
          })
          .catch(apiError)
      })
    })

  /**
   * Role delete
   */

  roleCmd
    .task('delete')
    .handler(function (data, flags, done) {
      cli.issuers.prompt(flags['issuer'] || flags.i, function (err, issuer) {
        if (err) {
          cli.log.error(err)
          process.exit(1)
        }

        try {
          var anvil = cli.client.create(issuer)
        } catch (e) {
          cli.log.error(e)
          process.exit(1)
        }

        anvil.discover()
          .then(function (configuration) {
            return new Promise(function (resolve, reject) {
              if (data[0]) {
                resolve(data[0])
              } else {
                anvil.roles.list({ token: anvil.tokens.access_token })
                  .then(function (roles) {
                    cli.prompt([
                      {
                        type: 'list',
                        name: 'roleName',
                        message: 'Select a role',
                        choices: roles.map(function (role) {
                          return {
                            name: role.name,
                            value: role.name
                          }
                        })
                      }
                    ], function (answers) {
                      resolve(answers.roleName)
                    })
                  })
                  .catch(reject)
              }
            })
          })
          .then(function (roleName) {
            // Don't allow deletion of required roles
            if (['authority'].indexOf(roleName) !== -1) {
              throw new Error('This role cannot be deleted')
            }

            return anvil.roles.delete(roleName, { token: anvil.tokens.access_token })
          })
          .then(function () {
            done()
          })
          .catch(apiError)
      })
    })

  done()
}

/**
 * Exports
 */

module.exports = registerRole
<commit_msg>feat(roles): list, add, remove role scopes<commit_after>/* global process, Promise */

/**
 * Module dependencies
 */

var chalk = require('chalk')
var Table = require('cli-table')

/**
 * Role commands
 */

function registerRole (cli, options, done) {
  var roleCmd = cli.command('role')

  /**
   * API Error Response Handler
   */

  function apiError (err) {
    if (err.statusCode && [401, 403].indexOf(err.statusCode) !== -1) {
      cli.log.error('Please login to the issuer.')
    } else {
      console.log(err)
    }

    process.exit(1)
  }

  /**
   * Display Field
   */

  function displayField (key, value, format) {
    if (typeof value !== 'undefined' && !(Array.isArray(value) && value.length === 0)) {
      // determine the number of tab characters to use
      var tabs = '\t'
      var diff = 24 - key.length
      if (diff > 8) { tabs += '\t' }
      if (diff > 16) { tabs += '\t' }

      // format the value
      if (Array.isArray(value)) {
        value = value.map(function (item) {
          return (format) ? chalk[format](item) : item
        }).join('\n\t\t\t')
      } else {
        value = (format) ? chalk[format](value) : value
      }

      cli.log(key + tabs + value)
    }
  }

  /**
   * Display Role
   */

  function displayRole (data) {
    cli.log()
    displayField('Name', data.name, 'bold')
    displayField('Created', data.created && new Date(data.created))
    displayField('Modified', data.modified && new Date(data.modified))
  }

  roleCmd
    .handler(function (data, flags, done) {
      cli.log('Usage:')
      cli.log(
        '  nvl role:register [<id>] [--issuer | -i <issuer id>] [--name | -n <name>]'
      )

      cli.log('  nvl role:list [--issuer | -i <issuer id>]')

      cli.log('  nvl role:info [<id>] [--issuer | -i <issuer id>]')

      cli.log(
        '  nvl role:update [<id>] [--issuer | -i <issuer id>] [--name | -n <name>]'
      )

      cli.log('  nvl role:delete [<id>] [--issuer | -i <issuer id>]')

      done()
    })

  /**
   * Role registration
   */

  roleCmd
    .task('register')
    .handler(function (data, flags, done) {
      cli.issuers.prompt(flags['issuer'] || flags.i, function (err, issuer) {
        if (err) {
          cli.log.error(err)
          process.exit(1)
        }

        try {
          var anvil = cli.client.create(issuer)
        } catch (e) {
          cli.log.error(e)
          process.exit(1)
        }

        anvil.discover()
          .then(function (configuration) {
            cli.prompt([
              {
                type: 'input',
                name: 'name',
                message: 'Name',
                value: flags['name'] || flags.n,
                trim: true
              }
            ], function (answers) {
              // register the role
              anvil.roles.create(answers, { token: anvil.tokens.access_token })
                .then(function (registration) {
                  displayRole(registration)
                  done()
                })
                // registration error
                .catch(apiError)
            })
          })
          // discover error
          .catch(apiError)
      })
    })

  /**
   * Role list
   */

  roleCmd
    .task('list')
    .handler(function (data, flags, done) {
      cli.issuers.prompt(flags['issuer'] || flags.i, function (err, issuer) {
        if (err) {
          cli.log.error(err)
          process.exit(1)
        }

        try {
          var anvil = cli.client.create(issuer)
        } catch (e) {
          cli.log.error(e)
          process.exit(1)
        }

        anvil.discover()
          .then(function (configuration) {
            return anvil.roles.list({ token: anvil.tokens.access_token })
          })
          .then(function (roles) {
            var table = new Table({
              head: ['Name']
            })

            roles.forEach(function (role) {
              var name = chalk.bold(role.name)
              table.push([name])
            })

            cli.log(table.toString())
            done()
          })
          .catch(apiError)
      })
    })

  /**
   * Role info
   */

  roleCmd
    .task('info')
    .handler(function (data, flags, done) {
      cli.issuers.prompt(flags['issuer'] || flags.i, function (err, issuer) {
        if (err) {
          cli.log.error(err)
          process.exit(1)
        }

        try {
          var anvil = cli.client.create(issuer)
        } catch (e) {
          cli.log.error(e)
          process.exit(1)
        }

        anvil.discover()
          .then(function (configuration) {
            return new Promise(function (resolve, reject) {
              if (data[0]) {
                resolve(data[0])
              } else {
                anvil.roles.list({ token: anvil.tokens.access_token })
                  .then(function (roles) {
                    cli.prompt([
                      {
                        type: 'list',
                        name: 'roleName',
                        message: 'Select a role',
                        choices: roles.map(function (role) {
                          return {
                            name: role.name,
                            value: role.name
                          }
                        })
                      }
                    ], function (answers) {
                      resolve(answers.roleName)
                    })
                  })
                  .catch(reject)
              }
            })
          })
          .then(function (roleName) {
            return anvil.roles.get(roleName, { token: anvil.tokens.access_token })
          })
          .then(function (data) {
            displayRole(data)
            done()
          })
          .catch(apiError)
      })
    })

  /**
   * Role update
   */

  roleCmd
    .task('update')
    .handler(function (data, flags, done) {
      cli.issuers.prompt(flags['issuer'] || flags.i, function (err, issuer) {
        if (err) {
          cli.log.error(err)
          process.exit(1)
        }

        try {
          var anvil = cli.client.create(issuer)
        } catch (e) {
          cli.log.error(e)
          process.exit(1)
        }

        anvil.discover()
          .then(function (configuration) {
            return new Promise(function (resolve, reject) {
              if (data[0]) {
                resolve(data[0])
              } else {
                anvil.roles.list({ token: anvil.tokens.access_token })
                  .then(function (roles) {
                    cli.prompt([
                      {
                        type: 'list',
                        name: 'roleName',
                        message: 'Select a role',
                        choices: roles.map(function (role) {
                          return {
                            name: role.name,
                            value: role.name
                          }
                        })
                      }
                    ], function (answers) {
                      resolve(answers.roleName)
                    })
                  })
                  .catch(reject)
              }
            })
          })
          .then(function (roleName) {
            return anvil.roles.get(roleName, { token: anvil.tokens.access_token })
          })
          .then(function (role) {
            return new Promise(function (resolve, reject) {
              cli.prompt([
                {
                  type: 'input',
                  name: 'name',
                  message: 'Name',
                  value: flags['name'] || flags.n,
                  default: role.name,
                  trim: true
                }
              ], function (update) {
                resolve({
                  role: role,
                  update: update
                })
              })
            })
          })
          .then(function (data) {
            // Don't allow updating of required roles
            if (['authority'].indexOf(data.role.name) !== -1) {
              throw new Error('This role cannot be updated')
            }

            return anvil.roles.update(data.role.name, data.update, { token: anvil.tokens.access_token })
          })
          .then(function (data) {
            displayRole(data)
            done()
          })
          .catch(apiError)
      })
    })

  /**
   * Role delete
   */

  roleCmd
    .task('delete')
    .handler(function (data, flags, done) {
      cli.issuers.prompt(flags['issuer'] || flags.i, function (err, issuer) {
        if (err) {
          cli.log.error(err)
          process.exit(1)
        }

        try {
          var anvil = cli.client.create(issuer)
        } catch (e) {
          cli.log.error(e)
          process.exit(1)
        }

        anvil.discover()
          .then(function (configuration) {
            return new Promise(function (resolve, reject) {
              if (data[0]) {
                resolve(data[0])
              } else {
                anvil.roles.list({ token: anvil.tokens.access_token })
                  .then(function (roles) {
                    cli.prompt([
                      {
                        type: 'list',
                        name: 'roleName',
                        message: 'Select a role',
                        choices: roles.map(function (role) {
                          return {
                            name: role.name,
                            value: role.name
                          }
                        })
                      }
                    ], function (answers) {
                      resolve(answers.roleName)
                    })
                  })
                  .catch(reject)
              }
            })
          })
          .then(function (roleName) {
            // Don't allow deletion of required roles
            if (['authority'].indexOf(roleName) !== -1) {
              throw new Error('This role cannot be deleted')
            }

            return anvil.roles.delete(roleName, { token: anvil.tokens.access_token })
          })
          .then(function () {
            done()
          })
          .catch(apiError)
      })
    })

  /**
   * Scope list
   */

  roleCmd
    .task('scopes')
    .handler(function (data, flags, done) {
      cli.issuers.prompt(flags['issuer'] || flags.i, function (err, issuer) {
        if (err) {
          cli.log.error(err)
          process.exit(1)
        }

        try {
          var anvil = cli.client.create(issuer)
          var token = anvil.tokens.access_token
        } catch (e) {
          cli.log.error(e)
          process.exit(1)
        }

        anvil.discover()

          // prompt for role
          .then(function (configuration) {
            return new Promise(function (resolve, reject) {
              if (data[0]) {
                resolve(data[0])
              } else {
                anvil.roles.list({ token: token })
                  .then(function (roles) {
                    cli.prompt([
                      {
                        type: 'list',
                        name: 'roleName',
                        message: 'Select a role',
                        choices: roles.map(function (role) {
                          return {
                            name: role.name,
                            value: role.name
                          }
                        })
                      }
                    ], function (answers) {
                      resolve(answers.roleName)
                    })
                  })
                  .catch(reject)
              }
            })
          })

          // get the scopes
          .then(function (role) {
            return anvil.roles.scopes.list(role, { token: token })
          })

          // display the scopes
          .then(function (scopes) {
            var table = new Table({
              head: ['Name', 'Description', 'Restricted']
            })

            scopes.forEach(function (scope) {
              var name = chalk.bold(scope.name)
              var description = scope.description
              var restricted = scope.restricted
              table.push([name, description, restricted])
            })

            cli.log(table.toString())
            done()
          })
          .catch(apiError)
      })
    })

  /**
   * Permit
   */

  roleCmd
    .task('permit')
    .handler(function (data, flags, done) {
      cli.issuers.prompt(flags['issuer'] || flags.i, function (err, issuer) {
        if (err) {
          cli.log.error(err)
          process.exit(1)
        }

        try {
          var anvil = cli.client.create(issuer)
          var token = anvil.tokens.access_token
        } catch (e) {
          cli.log.error(e)
          process.exit(1)
        }

        anvil.discover()

          // get role and user choices
          .then(function (configuration) {
            return new Promise(function (resolve, reject) {
              // command arguments for user and role
              if (data[0] && data[1]) {
                resolve({
                  roleName: data[0],
                  scopeName: data[1]
                })

              // prompt for user and role
              } else {
                Promise.all([
                  anvil.roles.list({ token: token }),
                  anvil.scopes.list({ token: token })
                ])
                .then(function (data) {
                  var roles = data[0]
                  var scopes = data[1]

                  cli.prompt([
                    {
                      type: 'list',
                      name: 'roleName',
                      message: 'Select a role',
                      choices: roles.map(function (role) {
                        return {
                          name: role.name,
                          value: role.name
                        }
                      })
                    },
                    {
                      type: 'list',
                      name: 'scopeName',
                      message: 'Select a scope to permit',
                      choices: scopes.map(function (scope) {
                        return {
                          name: scope.name,
                          value: scope.name
                        }
                      })
                    }
                  ], function (answers) {
                    resolve(answers)
                  })
                })
                .catch(reject)
              }
            })
          })

          // try to add the role
          .then(function (data) {
            var role = data.roleName
            var scope = data.scopeName

            return anvil.roles.scopes.add(role, scope, { token: token })
          })

          // display the result
          .then(function (response) {
            if (response.added) {
              console.log('The scope is now permitted')
            }
            done()
          })

          // handle error
          .catch(apiError)
      })
    })

  /**
   * Permit
   */

  roleCmd
    .task('forbid')
    .handler(function (data, flags, done) {
      cli.issuers.prompt(flags['issuer'] || flags.i, function (err, issuer) {
        if (err) {
          cli.log.error(err)
          process.exit(1)
        }

        try {
          var anvil = cli.client.create(issuer)
          var token = anvil.tokens.access_token
        } catch (e) {
          cli.log.error(e)
          process.exit(1)
        }

        anvil.discover()

          // get role and user choices
          .then(function (configuration) {
            return new Promise(function (resolve, reject) {
              // command arguments for user and role
              if (data[0] && data[1]) {
                resolve({
                  roleName: data[0],
                  scopeName: data[1]
                })

              // prompt for user and role
              } else {
                Promise.all([
                  anvil.roles.list({ token: token }),
                  anvil.scopes.list({ token: token })
                ])
                .then(function (data) {
                  var roles = data[0]
                  var scopes = data[1]

                  cli.prompt([
                    {
                      type: 'list',
                      name: 'roleName',
                      message: 'Select a role',
                      choices: roles.map(function (role) {
                        return {
                          name: role.name,
                          value: role.name
                        }
                      })
                    },
                    {
                      type: 'list',
                      name: 'scopeName',
                      message: 'Select a scope to forbid',
                      choices: scopes.map(function (scope) {
                        return {
                          name: scope.name,
                          value: scope.name
                        }
                      })
                    }
                  ], function (answers) {
                    resolve(answers)
                  })
                })
                .catch(reject)
              }
            })
          })

          // try to add the role
          .then(function (data) {
            var role = data.roleName
            var scope = data.scopeName

            return anvil.roles.scopes.delete(role, scope, { token: token })
          })

          // display the result
          .then(function (response) {
            console.log('The scope is no longer permitted')
            done()
          })

          // handle error
          .catch(apiError)
      })
    })

  done()
}

/**
 * Exports
 */

module.exports = registerRole
<|endoftext|>


<commit_before>/*Copyright (c) 2013-2016, Rob Schmuecker
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this
  list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice,
  this list of conditions and the following disclaimer in the documentation
  and/or other materials provided with the distribution.

* The name Rob Schmuecker may not be used to endorse or promote products
  derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.*/

function draw(release) {
    release = release || 0;
    var url = '/Json/NewSpecies/data?release='+ release +';format=d3'
    // Get JSON data
    treeJSON = d3.json(url, function(error, treeData) {
        document.getElementById('msg').innerHTML = '';
        if (!!!release) {
          document.getElementById('footer-msg').innerHTML = 'All species in e!';
        }
        else {
          if (!treeData.total) {
            document.getElementById('msg').innerHTML = 'No new species in release ' + release + ' or data not available';
            return;
          }
          document.getElementById('rel').innerHTML = release;
        }

        // Calculate total nodes, max label length
        var totalNodes = 0;
        var maxLabelLength = 0;
        // variables for drag/drop
        var selectedNode = null;
        var draggingNode = null;
        // panning variables
        var panSpeed = 200;
        var panBoundary = 20; // Within 20px from edges will pan when dragging.
        // Misc. variables
        var i = 0;
        var duration = 750;
        var root;

        // size of the diagram
        var viewerWidth = $(document).width();
        var viewerHeight = $(document).height();

        var tree = d3.layout.tree()
            .size([viewerHeight, viewerWidth]);

        // define a d3 diagonal projection for use by the node paths later on.
        var diagonal = d3.svg.diagonal()
            .projection(function(d) {
                return [d.y, d.x];
            });

        // A recursive helper function for performing some setup by walking through all nodes

        function visit(parent, visitFn, childrenFn) {
            if (!parent) return;

            visitFn(parent);

            var children = childrenFn(parent);
            if (children) {
                var count = children.length;
                for (var i = 0; i < count; i++) {
                    visit(children[i], visitFn, childrenFn);
                }
            }
        }

        // Call visit function to establish maxLabelLength
        visit(treeData, function(d) {
            totalNodes++;
            maxLabelLength = Math.max(d.name.length, maxLabelLength);

        }, function(d) {
            return d.children && d.children.length > 0 ? d.children : null;
        });

        // sort the tree according to the node names

        function sortTree() {
            tree.sort(function(a, b) {
                return b.name.toLowerCase() < a.name.toLowerCase() ? 1 : -1;
            });
        }
        // Sort the tree initially incase the JSON isn't in a sorted order.
        sortTree();

        // TODO: Pan function, can be better implemented.

        function pan(domNode, direction) {
            var speed = panSpeed;
            if (panTimer) {
                clearTimeout(panTimer);
                translateCoords = d3.transform(svgGroup.attr("transform"));
                if (direction == 'left' || direction == 'right') {
                    translateX = direction == 'left' ? translateCoords.translate[0] + speed : translateCoords.translate[0] - speed;
                    translateY = translateCoords.translate[1];
                } else if (direction == 'up' || direction == 'down') {
                    translateX = translateCoords.translate[0];
                    translateY = direction == 'up' ? translateCoords.translate[1] + speed : translateCoords.translate[1] - speed;
                }
                scaleX = translateCoords.scale[0];
                scaleY = translateCoords.scale[1];
                scale = zoomListener.scale();
                svgGroup.transition().attr("transform", "translate(" + translateX + "," + translateY + ")scale(" + scale + ")");
                d3.select(domNode).select('g.node').attr("transform", "translate(" + translateX + "," + translateY + ")");
                zoomListener.scale(zoomListener.scale());
                zoomListener.translate([translateX, translateY]);
                panTimer = setTimeout(function() {
                    pan(domNode, speed, direction);
                }, 50);
            }
        }

        // Define the zoom function for the zoomable tree

        function zoom() {
            svgGroup.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
        }

        // define the zoomListener which calls the zoom function on the "zoom" event constrained within the scaleExtents
        var zoomListener = d3.behavior.zoom().scaleExtent([0.1, 3]).on("zoom", zoom);

        // define the baseSvg, attaching a class for styling and the zoomListener
        var baseSvg = d3.select("#tree-container").append("svg")
            .attr("width", viewerWidth)
            .attr("height", viewerHeight)
            .attr("class", "overlay")
            .call(zoomListener);

        // Helper functions for collapsing and expanding nodes.

        function collapse(d) {
            if (d.children) {
                d._children = d.children;
                d._children.forEach(collapse);
                d.children = null;
            }
        }

        function expand(d) {
            if (d._children) {
                d.children = d._children;
                d.children.forEach(expand);
                d._children = null;
            }
        }

        // Function to center node when clicked/dropped so node doesn't get lost when collapsing/moving with large amount of children.

        function centerNode(source, fl) {
            scale = zoomListener.scale();
            x = -source.y0;
            y = -source.x0;
            vw = !fl ? viewerWidth : 180;
            x = x * scale + vw / 2;
            y = y * scale + viewerHeight / 2;
            d3.select('g').transition()
                .duration(duration)
                .attr("transform", "translate(" + x + "," + y + ")scale(" + scale + ")");
            zoomListener.scale(scale);
            zoomListener.translate([x, y]);
        }

        function displaySpeciesInfo(source) {
          console.log(source);
          if(!source._children && !source.children) {
            document.getElementById('sci').innerHTML = '(' + source.name + ')';
            document.getElementById('common').innerHTML = '' + source.display_name + '';
            document.getElementById('img').src = '/i/species/' + source.name + '.png';
          }
          else {
            document.getElementById('sci').innerHTML = source.name;
            document.getElementById('common').innerHTML = '';
            document.getElementById('img').src = '/i/e-bang.gif';
          }
        }

        // Toggle children function

        function toggleChildren(d) {
            if (d.children) {
                d._children = d.children;
                d.children = null;
            } else if (d._children) {
                d.children = d._children;
                d._children = null;
            }
            return d;
        }

        // Toggle children on click.

        function click(d) {
            if (d3.event.defaultPrevented) return; // click suppressed
            d = toggleChildren(d);
            update(d);
            centerNode(d);
            displaySpeciesInfo(d);
        }

        function update(source) {
            // Compute the new height, function counts total children of root node and sets tree height accordingly.
            // This prevents the layout looking squashed when new nodes are made visible or looking sparse when nodes are removed
            // This makes the layout more consistent.
            var levelWidth = [1];
            var childCount = function(level, n) {

                if (n.children && n.children.length > 0) {
                    if (levelWidth.length <= level + 1) levelWidth.push(0);

                    levelWidth[level + 1] += n.children.length;
                    n.children.forEach(function(d) {
                        childCount(level + 1, d);
                    });
                }
            };
            childCount(0, root);
            root.total = root.total || 1;
            var newHeight = d3.max(levelWidth) * (root.total > 50 ? 25 : (viewerHeight - 150)/(root.total)); // 25 pixels per line
            tree = tree.size([newHeight, viewerWidth]);

            // Compute the new tree layout.
            var nodes = tree.nodes(root).reverse(),
                links = tree.links(nodes);

            // Set widths between levels based on maxLabelLength.
            nodes.forEach(function(d) {
                d.y = (d.depth * (maxLabelLength * 10)); //maxLabelLength * 10px
                // alternatively to keep a fixed scale one can set a fixed depth per level
                // Normalize for fixed-depth by commenting out below line
                // d.y = (d.depth * 500); //500px per level.
            });

            // Update the nodes…
            node = svgGroup.selectAll("g.node")
                .data(nodes, function(d) {
                    return d.id || (d.id = ++i);
                });

            // Enter any new nodes at the parent's previous position.
            var nodeEnter = node.enter().append("g")
                .attr("class", "node")
                .attr("transform", function(d) {
                    return "translate(" + source.y0 + "," + source.x0 + ")";
                })
                .on('click', click);

            nodeEnter.append("circle")
                .attr('class', 'nodeCircle')
                .attr("r", 0)
                .style("fill", function(d) {
                    return d._children ? "lightsteelblue" : "#fff";
                });

            nodeEnter.append("text")
                .attr("x", function(d) {
                    return d.children || d._children ? -10 : 10;
                })
                .attr("dy", ".35em")
                .attr('class', 'nodeText')
                .attr("text-anchor", function(d) {
                    return d.children || d._children ? "end" : "start";
                })
                .text(function(d) {
                    return d.name;
                })
                .style("fill-opacity", 0);

            nodeEnter.append("image")
                .attr("xlink:href", function(d) { if (!d.children) return '/i/species/' + d.name + '.png' })
                .attr("x", "-6px")
                .attr("y", "-6px")
                .attr("width", "12px")
                .attr("height", "12px");

            // phantom node to give us mouseover in a radius around it
            nodeEnter.append("circle")
                .attr('class', 'ghostCircle')
                .attr("r", 30)
                .attr("opacity", 0.2) // change this to zero to hide the target area
            .style("fill", "red")
                .attr('pointer-events', 'mouseover')
                .on("mouseover", function(node) {
                    overCircle(node);
                })
                .on("mouseout", function(node) {
                    outCircle(node);
                });

            // Update the text to reflect whether node has children or not.
            node.select('text')
                .attr("x", function(d) {
                    return d.children || d._children ? -10 : 10;
                })
                .attr("text-anchor", function(d) {
                    return d.children || d._children ? "end" : "start";
                })
                .text(function(d) {
                    return d.name;
                });

            // Change the circle fill depending on whether it has children and is collapsed
            node.select("circle.nodeCircle")
                .attr("r", 4.5)
                .style("fill", function(d) {
                    return d._children ? "lightsteelblue" : "#fff";
                });

            // Transition nodes to their new position.
            var nodeUpdate = node.transition()
                .duration(duration)
                .attr("transform", function(d) {
                    return "translate(" + d.y + "," + d.x + ")";
                });

            // Fade the text in
            nodeUpdate.select("text")
                .style("fill-opacity", 1);

            // Transition exiting nodes to the parent's new position.
            var nodeExit = node.exit().transition()
                .duration(duration)
                .attr("transform", function(d) {
                    return "translate(" + source.y + "," + source.x + ")";
                })
                .remove();

            nodeExit.select("circle")
                .attr("r", 0);

            nodeExit.select("text")
                .style("fill-opacity", 0);

            // Update the links…
            var link = svgGroup.selectAll("path.link")
                .data(links, function(d) {
                    return d.target.id;
                });

            // Enter any new links at the parent's previous position.
            link.enter().insert("path", "g")
                .attr("class", "link")
                .attr("d", function(d) {
                    var o = {
                        x: source.x0,
                        y: source.y0
                    };
                    return diagonal({
                        source: o,
                        target: o
                    });
                });

            // Transition links to their new position.
            link.transition()
                .duration(duration)
                .attr("d", diagonal);

            // Transition exiting nodes to the parent's new position.
            link.exit().transition()
                .duration(duration)
                .attr("d", function(d) {
                    var o = {
                        x: source.x,
                        y: source.y
                    };
                    return diagonal({
                        source: o,
                        target: o
                    });
                })
                .remove();

            // Stash the old positions for transition.
            nodes.forEach(function(d) {
                d.x0 = d.x;
                d.y0 = d.y;
            });
        }

        // Append a group which holds all nodes and which the zoom Listener can act upon.
        var svgGroup = baseSvg.append("g");

        // Define the root
        root = treeData;
        root.x0 = viewerHeight / 2;
        root.y0 = 0;

        // Layout the tree initially and center on the root node.
        update(root);
        centerNode(root, 1);
    });
}<commit_msg>Merging postreleasefix/99 to master<commit_after>/*Copyright (c) 2013-2016, Rob Schmuecker
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this
  list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice,
  this list of conditions and the following disclaimer in the documentation
  and/or other materials provided with the distribution.

* The name Rob Schmuecker may not be used to endorse or promote products
  derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.*/

function draw(release) {
    release = release || 0;
    var url = '/Json/NewSpecies/data?release='+ release +';format=d3'
    // Get JSON data
    treeJSON = d3.json(url, function(error, treeData) {
        document.getElementById('msg').innerHTML = '';
        if (!!!release) {
          document.getElementById('footer-msg').innerHTML = 'All species in e!';
        }
        else {
          if (!treeData.total) {
            document.getElementById('msg').innerHTML = 'No new species in release ' + release + ' or data not available';
            return;
          }
          document.getElementById('rel').innerHTML = release;
        }

        // Calculate total nodes, max label length
        var totalNodes = 0;
        var maxLabelLength = 0;
        // variables for drag/drop
        var selectedNode = null;
        var draggingNode = null;
        // panning variables
        var panSpeed = 200;
        var panBoundary = 20; // Within 20px from edges will pan when dragging.
        // Misc. variables
        var i = 0;
        var duration = 750;
        var root;

        // size of the diagram
        var viewerWidth = $(document).width();
        var viewerHeight = $(document).height();

        var tree = d3.layout.tree()
            .size([viewerHeight, viewerWidth]);

        // define a d3 diagonal projection for use by the node paths later on.
        var diagonal = d3.svg.diagonal()
            .projection(function(d) {
                return [d.y, d.x];
            });

        // A recursive helper function for performing some setup by walking through all nodes

        function visit(parent, visitFn, childrenFn) {
            if (!parent) return;

            visitFn(parent);

            var children = childrenFn(parent);
            if (children) {
                var count = children.length;
                for (var i = 0; i < count; i++) {
                    visit(children[i], visitFn, childrenFn);
                }
            }
        }

        // Call visit function to establish maxLabelLength
        visit(treeData, function(d) {
            totalNodes++;
            maxLabelLength = Math.max(d.name.length, maxLabelLength);

        }, function(d) {
            return d.children && d.children.length > 0 ? d.children : null;
        });

        // sort the tree according to the node names

        function sortTree() {
            tree.sort(function(a, b) {
                return b.name.toLowerCase() < a.name.toLowerCase() ? 1 : -1;
            });
        }
        // Sort the tree initially incase the JSON isn't in a sorted order.
        sortTree();

        // TODO: Pan function, can be better implemented.

        function pan(domNode, direction) {
            var speed = panSpeed;
            if (panTimer) {
                clearTimeout(panTimer);
                translateCoords = d3.transform(svgGroup.attr("transform"));
                if (direction == 'left' || direction == 'right') {
                    translateX = direction == 'left' ? translateCoords.translate[0] + speed : translateCoords.translate[0] - speed;
                    translateY = translateCoords.translate[1];
                } else if (direction == 'up' || direction == 'down') {
                    translateX = translateCoords.translate[0];
                    translateY = direction == 'up' ? translateCoords.translate[1] + speed : translateCoords.translate[1] - speed;
                }
                scaleX = translateCoords.scale[0];
                scaleY = translateCoords.scale[1];
                scale = zoomListener.scale();
                svgGroup.transition().attr("transform", "translate(" + translateX + "," + translateY + ")scale(" + scale + ")");
                d3.select(domNode).select('g.node').attr("transform", "translate(" + translateX + "," + translateY + ")");
                zoomListener.scale(zoomListener.scale());
                zoomListener.translate([translateX, translateY]);
                panTimer = setTimeout(function() {
                    pan(domNode, speed, direction);
                }, 50);
            }
        }

        // Define the zoom function for the zoomable tree

        function zoom() {
            svgGroup.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
        }

        // define the zoomListener which calls the zoom function on the "zoom" event constrained within the scaleExtents
        var zoomListener = d3.behavior.zoom().scaleExtent([0.1, 3]).on("zoom", zoom);

        // define the baseSvg, attaching a class for styling and the zoomListener
        var baseSvg = d3.select("#tree-container").append("svg")
            .attr("width", viewerWidth)
            .attr("height", viewerHeight)
            .attr("class", "overlay")
            .call(zoomListener);

        // Helper functions for collapsing and expanding nodes.

        function collapse(d) {
            if (d.children) {
                d._children = d.children;
                d._children.forEach(collapse);
                d.children = null;
            }
        }

        function expand(d) {
            if (d._children) {
                d.children = d._children;
                d.children.forEach(expand);
                d._children = null;
            }
        }

        // Function to center node when clicked/dropped so node doesn't get lost when collapsing/moving with large amount of children.

        function centerNode(source, fl) {
            scale = zoomListener.scale();
            x = -source.y0;
            y = -source.x0;
            vw = !fl ? viewerWidth : 180;
            x = x * scale + vw / 2;
            y = y * scale + viewerHeight / 2;
            d3.select('g').transition()
                .duration(duration)
                .attr("transform", "translate(" + x + "," + y + ")scale(" + scale + ")");
            zoomListener.scale(scale);
            zoomListener.translate([x, y]);
        }

        function displaySpeciesInfo(source) {
          console.log(source);
          if(!source._children && !source.children) {
            document.getElementById('sci').innerHTML = '(' + source.name + ')';
            document.getElementById('common').innerHTML = '' + source.display_name + '';
            document.getElementById('img').src = '/i/species/' + source.name + '.png';
          }
          else {
            document.getElementById('sci').innerHTML = source.name;
            document.getElementById('common').innerHTML = '';
            document.getElementById('img').src = '/i/e-bang.gif';
          }
        }

        // Toggle children function

        function toggleChildren(d) {
            if (d.children) {
                d._children = d.children;
                d.children = null;
            } else if (d._children) {
                d.children = d._children;
                d._children = null;
            }
            return d;
        }

        // Toggle children on click.

        function click(d) {
            if (d3.event.defaultPrevented) return; // click suppressed
            d = toggleChildren(d);
            update(d);
            // centerNode(d);
            displaySpeciesInfo(d);
        }

        function update(source) {
            // Compute the new height, function counts total children of root node and sets tree height accordingly.
            // This prevents the layout looking squashed when new nodes are made visible or looking sparse when nodes are removed
            // This makes the layout more consistent.
            var levelWidth = [1];
            var childCount = function(level, n) {

                if (n.children && n.children.length > 0) {
                    if (levelWidth.length <= level + 1) levelWidth.push(0);

                    levelWidth[level + 1] += n.children.length;
                    n.children.forEach(function(d) {
                        childCount(level + 1, d);
                    });
                }
            };
            childCount(0, root);
            root.total = root.total || 1;
            var newHeight = d3.max(levelWidth) * (root.total > 50 ? 25 : (viewerHeight - 150)/(root.total)); // 25 pixels per line
            tree = tree.size([newHeight, viewerWidth]);

            // Compute the new tree layout.
            var nodes = tree.nodes(root).reverse(),
                links = tree.links(nodes);

            // Set widths between levels based on maxLabelLength.
            nodes.forEach(function(d) {
                d.y = (d.depth * (maxLabelLength * 10)); //maxLabelLength * 10px
                // alternatively to keep a fixed scale one can set a fixed depth per level
                // Normalize for fixed-depth by commenting out below line
                // d.y = (d.depth * 500); //500px per level.
            });

            // Update the nodes…
            node = svgGroup.selectAll("g.node")
                .data(nodes, function(d) {
                    return d.id || (d.id = ++i);
                });

            // Enter any new nodes at the parent's previous position.
            var nodeEnter = node.enter().append("g")
                .attr("class", "node")
                .attr("transform", function(d) {
                    return "translate(" + source.y0 + "," + source.x0 + ")";
                })
                .on('click', click);

            nodeEnter.append("circle")
                .attr('class', 'nodeCircle')
                .attr("r", 0)
                .style("fill", function(d) {
                    return d._children ? "lightsteelblue" : "#fff";
                });

            nodeEnter.append("text")
                .attr("x", function(d) {
                    return d.children || d._children ? -10 : 10;
                })
                .attr("dy", ".35em")
                .attr('class', 'nodeText')
                .attr("text-anchor", function(d) {
                    return d.children || d._children ? "end" : "start";
                })
                .text(function(d) {
                    return d.name;
                })
                .style("fill-opacity", 0);

            nodeEnter.append("image")
                .attr("xlink:href", function(d) { if (!d.children) return '/i/species/' + d.name + '.png' })
                .attr("x", "-6px")
                .attr("y", "-6px")
                .attr("width", "12px")
                .attr("height", "12px");

            // phantom node to give us mouseover in a radius around it
            nodeEnter.append("circle")
                .attr('class', 'ghostCircle')
                .attr("r", 30)
                .attr("opacity", 0.2) // change this to zero to hide the target area
            .style("fill", "red")
                .attr('pointer-events', 'mouseover')
                .on("mouseover", function(node) {
                    overCircle(node);
                })
                .on("mouseout", function(node) {
                    outCircle(node);
                });

            // Update the text to reflect whether node has children or not.
            node.select('text')
                .attr("x", function(d) {
                    return d.children || d._children ? -10 : 10;
                })
                .attr("text-anchor", function(d) {
                    return d.children || d._children ? "end" : "start";
                })
                .text(function(d) {
                    return d.name;
                });

            // Change the circle fill depending on whether it has children and is collapsed
            node.select("circle.nodeCircle")
                .attr("r", 4.5)
                .style("fill", function(d) {
                    return d._children ? "lightsteelblue" : "#fff";
                });

            // Transition nodes to their new position.
            var nodeUpdate = node.transition()
                .duration(duration)
                .attr("transform", function(d) {
                    return "translate(" + d.y + "," + d.x + ")";
                });

            // Fade the text in
            nodeUpdate.select("text")
                .style("fill-opacity", 1);

            // Transition exiting nodes to the parent's new position.
            var nodeExit = node.exit().transition()
                .duration(duration)
                .attr("transform", function(d) {
                    return "translate(" + source.y + "," + source.x + ")";
                })
                .remove();

            nodeExit.select("circle")
                .attr("r", 0);

            nodeExit.select("text")
                .style("fill-opacity", 0);

            // Update the links…
            var link = svgGroup.selectAll("path.link")
                .data(links, function(d) {
                    return d.target.id;
                });

            // Enter any new links at the parent's previous position.
            link.enter().insert("path", "g")
                .attr("class", "link")
                .attr("d", function(d) {
                    var o = {
                        x: source.x0,
                        y: source.y0
                    };
                    return diagonal({
                        source: o,
                        target: o
                    });
                });

            // Transition links to their new position.
            link.transition()
                .duration(duration)
                .attr("d", diagonal);

            // Transition exiting nodes to the parent's new position.
            link.exit().transition()
                .duration(duration)
                .attr("d", function(d) {
                    var o = {
                        x: source.x,
                        y: source.y
                    };
                    return diagonal({
                        source: o,
                        target: o
                    });
                })
                .remove();

            // Stash the old positions for transition.
            nodes.forEach(function(d) {
                d.x0 = d.x;
                d.y0 = d.y;
            });
        }

        // Append a group which holds all nodes and which the zoom Listener can act upon.
        var svgGroup = baseSvg.append("g");

        // Define the root
        root = treeData;
        root.x0 = viewerHeight / 2;
        root.y0 = 0;

        // Layout the tree initially and center on the root node.
        update(root);
        centerNode(root, 1);
    });
}<|endoftext|>


<commit_before>'use strict'

const multiaddr = require('multiaddr')
const defaultsDeep = require('lodash.defaultsdeep')
const repoUtils = require('./utils/repo/nodejs')
const defaults = require('lodash.defaults')
const waterfall = require('async/waterfall')
const debug = require('debug')
const EventEmitter = require('events')

const log = debug('ipfsd-ctl:in-proc')

let IPFS = null

/**
 * ipfsd for a js-ipfs instance (aka in-process IPFS node)
 */
class Node extends EventEmitter {
  /**
   * Create a new node.
   *
   * @param {Object} [opts]
   * @param {Object} [opts.env={}] - Additional environment settings, passed to executing shell.
   * @returns {Node}
   */
  constructor (opts) {
    super()
    this.opts = opts || {}

    IPFS = this.opts.exec

    this.opts.args = this.opts.args || []
    this.path = this.opts.repoPath || repoUtils.createTempRepoPath()
    this.disposable = this.opts.disposable
    this.clean = true
    this._apiAddr = null
    this._gatewayAddr = null
    this._started = false
    this.api = null
    this.initialized = false
    this.bits = this.opts.initOptions ? this.opts.initOptions.bits : null

    this.opts.EXPERIMENTAL = defaultsDeep({}, opts.EXPERIMENTAL, {
      pubsub: false,
      sharding: false,
      relay: {
        enabled: false,
        hop: {
          enabled: false
        }
      },
      mfs: false
    })

    this.opts.args.forEach((arg) => {
      if (arg === '--enable-pubsub-experiment') {
        this.opts.EXPERIMENTAL.pubsub = true
      } else if (arg === '--enable-sharding-experiment') {
        this.opts.EXPERIMENTAL.sharding = true
      } else if (arg.startsWith('--pass')) {
        this.opts.pass = arg.split(' ').slice(1).join(' ')
      } else {
        throw new Error(`Unknown argument ${arg}`)
      }
    })

    this.exec = new IPFS({
      repo: this.path,
      init: false,
      start: false,
      pass: this.opts.pass,
      EXPERIMENTAL: this.opts.EXPERIMENTAL,
      libp2p: this.opts.libp2p,
      config: this.opts.config
    })

    // TODO: should this be wrapped in a process.nextTick(), for context:
    // https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/#why-use-process-nexttick
    this.exec.once('error', err => this.emit('error', err))
    this.exec.once('ready', () => this.emit('ready'))
  }

  /**
   * Get the address of connected IPFS API.
   *
   * @returns {Multiaddr}
   */
  get apiAddr () {
    return this._apiAddr
  }

  /**
   * Get the address of connected IPFS HTTP Gateway.
   *
   * @returns {Multiaddr}
   */
  get gatewayAddr () {
    return this._gatewayAddr
  }

  /**
   * Get the current repo path
   *
   * @return {string}
   */
  get repoPath () {
    return this.path
  }

  /**
   * Is the node started
   *
   * @return {boolean}
   */
  get started () {
    return this._started
  }

  /**
   * Is the environment
   *
   * @return {object}
   */
  get env () {
    throw new Error('Not implemented!')
  }

  /**
   * Initialize a repo.
   *
   * @param {Object} [initOptions={}]
   * @param {number} [initOptions.bits=2048] - The bit size of the identiy key.
   * @param {string} [initOptions.directory=IPFS_PATH] - The location of the repo.
   * @param {string} [initOptions.pass] - The passphrase of the keychain.
   * @param {function (Error, Node)} callback
   * @returns {undefined}
   */
  init (initOptions, callback) {
    if (typeof initOptions === 'function') {
      callback = initOptions
      initOptions = {}
    }

    const bits = initOptions.keysize ? initOptions.bits : this.bits
    // do not just set a default keysize,
    // in case we decide to change it at
    // the daemon level in the future
    if (bits) {
      initOptions.bits = bits
      log(`initializing with keysize: ${bits}`)
    }
    this.exec.init(initOptions, (err) => {
      if (err) {
        return callback(err)
      }

      const self = this
      waterfall([
        (cb) => this.getConfig(cb),
        (conf, cb) => this.replaceConfig(defaults({}, this.opts.config, conf), cb)
      ], (err) => {
        if (err) { return callback(err) }
        self.clean = false
        self.initialized = true
        return callback(null, this)
      })
    })
  }

  /**
   * Delete the repo that was being used.
   * If the node was marked as `disposable` this will be called
   * automatically when the process is exited.
   *
   * @param {function(Error)} callback
   * @returns {undefined}
   */
  cleanup (callback) {
    if (this.clean) {
      return callback()
    }

    repoUtils.removeRepo(this.path, callback)
  }

  /**
   * Start the daemon.
   *
   * @param {Array<string>} [flags=[]] - Flags to be passed to the `ipfs daemon` command.
   * @param {function(Error, IpfsApi)} callback
   * @returns {undefined}
   */
  start (flags, callback) {
    if (typeof flags === 'function') {
      callback = flags
      flags = undefined // not used
    }

    this.exec.start((err) => {
      if (err) {
        return callback(err)
      }

      this._started = true
      this.api = this.exec
      this.exec.config.get((err, conf) => {
        if (err) {
          return callback(err)
        }

        this._apiAddr = conf.Addresses.API
        this._gatewayAddr = conf.Addresses.Gateway

        this.api.apiHost = multiaddr(conf.Addresses.API).nodeAddress().host
        this.api.apiPort = multiaddr(conf.Addresses.API).nodeAddress().port

        callback(null, this.api)
      })
    })
  }

  /**
   * Stop the daemon.
   *
   * @param {function(Error)} [callback]
   * @returns {undefined}
   */
  stop (callback) {
    callback = callback || function noop () {}

    if (!this.exec) {
      return callback()
    }

    this.exec.stop((err) => {
      if (err) {
        return callback(err)
      }

      this._started = false
      if (this.disposable) {
        return this.cleanup(callback)
      }

      return callback()
    })
  }

  /**
   * Kill the `ipfs daemon` process.
   *
   * First `SIGTERM` is sent, after 10.5 seconds `SIGKILL` is sent
   * if the process hasn't exited yet.
   *
   * @param {function()} callback - Called when the process was killed.
   * @returns {undefined}
   */
  killProcess (callback) {
    this.stop(callback)
  }

  /**
   * Get the pid of the `ipfs daemon` process.
   *
   * @returns {number}
   */
  pid () {
    throw new Error('not implemented')
  }

  /**
   * Call `ipfs config`
   *
   * If no `key` is passed, the whole config is returned as an object.
   *
   * @param {string} [key] - A specific config to retrieve.
   * @param {function(Error, (Object|string))} callback
   * @returns {undefined}
   */
  getConfig (key, callback) {
    if (typeof key === 'function') {
      callback = key
      key = undefined
    }

    this.exec.config.get(key, callback)
  }

  /**
   * Set a config value.
   *
   * @param {string} key
   * @param {string} value
   * @param {function(Error)} callback
   * @returns {undefined}
   */
  setConfig (key, value, callback) {
    this.exec.config.set(key, value, callback)
  }

  /**
   * Replace the current config with the provided one
   *
   * @param {object} config
   * @param {function(Error)} callback
   * @return {undefined}
   */
  replaceConfig (config, callback) {
    this.exec.config.replace(config, callback)
  }

  /**
   * Get the version of ipfs
   *
   * @param {function(Error, string)} callback
   * @returns {undefined}
   */
  version (callback) {
    this.exec.version(callback)
  }
}

module.exports = Node
<commit_msg>fix: add namesys pubsub option (#293)<commit_after>'use strict'

const multiaddr = require('multiaddr')
const defaultsDeep = require('lodash.defaultsdeep')
const repoUtils = require('./utils/repo/nodejs')
const defaults = require('lodash.defaults')
const waterfall = require('async/waterfall')
const debug = require('debug')
const EventEmitter = require('events')

const log = debug('ipfsd-ctl:in-proc')

let IPFS = null

/**
 * ipfsd for a js-ipfs instance (aka in-process IPFS node)
 */
class Node extends EventEmitter {
  /**
   * Create a new node.
   *
   * @param {Object} [opts]
   * @param {Object} [opts.env={}] - Additional environment settings, passed to executing shell.
   * @returns {Node}
   */
  constructor (opts) {
    super()
    this.opts = opts || {}

    IPFS = this.opts.exec

    this.opts.args = this.opts.args || []
    this.path = this.opts.repoPath || repoUtils.createTempRepoPath()
    this.disposable = this.opts.disposable
    this.clean = true
    this._apiAddr = null
    this._gatewayAddr = null
    this._started = false
    this.api = null
    this.initialized = false
    this.bits = this.opts.initOptions ? this.opts.initOptions.bits : null

    this.opts.EXPERIMENTAL = defaultsDeep({}, opts.EXPERIMENTAL, {
      pubsub: false,
      sharding: false,
      relay: {
        enabled: false,
        hop: {
          enabled: false
        }
      },
      mfs: false
    })

    this.opts.args.forEach((arg) => {
      if (arg === '--enable-pubsub-experiment') {
        this.opts.EXPERIMENTAL.pubsub = true
      } else if (arg === '--enable-sharding-experiment') {
        this.opts.EXPERIMENTAL.sharding = true
      } else if (arg === '--enable-namesys-pubsub') {
        this.opts.EXPERIMENTAL.ipnsPubsub = true
      } else if (arg.startsWith('--pass')) {
        this.opts.pass = arg.split(' ').slice(1).join(' ')
      } else {
        throw new Error(`Unknown argument ${arg}`)
      }
    })

    this.exec = new IPFS({
      repo: this.path,
      init: false,
      start: false,
      pass: this.opts.pass,
      EXPERIMENTAL: this.opts.EXPERIMENTAL,
      libp2p: this.opts.libp2p,
      config: this.opts.config
    })

    // TODO: should this be wrapped in a process.nextTick(), for context:
    // https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/#why-use-process-nexttick
    this.exec.once('error', err => this.emit('error', err))
    this.exec.once('ready', () => this.emit('ready'))
  }

  /**
   * Get the address of connected IPFS API.
   *
   * @returns {Multiaddr}
   */
  get apiAddr () {
    return this._apiAddr
  }

  /**
   * Get the address of connected IPFS HTTP Gateway.
   *
   * @returns {Multiaddr}
   */
  get gatewayAddr () {
    return this._gatewayAddr
  }

  /**
   * Get the current repo path
   *
   * @return {string}
   */
  get repoPath () {
    return this.path
  }

  /**
   * Is the node started
   *
   * @return {boolean}
   */
  get started () {
    return this._started
  }

  /**
   * Is the environment
   *
   * @return {object}
   */
  get env () {
    throw new Error('Not implemented!')
  }

  /**
   * Initialize a repo.
   *
   * @param {Object} [initOptions={}]
   * @param {number} [initOptions.bits=2048] - The bit size of the identiy key.
   * @param {string} [initOptions.directory=IPFS_PATH] - The location of the repo.
   * @param {string} [initOptions.pass] - The passphrase of the keychain.
   * @param {function (Error, Node)} callback
   * @returns {undefined}
   */
  init (initOptions, callback) {
    if (typeof initOptions === 'function') {
      callback = initOptions
      initOptions = {}
    }

    const bits = initOptions.keysize ? initOptions.bits : this.bits
    // do not just set a default keysize,
    // in case we decide to change it at
    // the daemon level in the future
    if (bits) {
      initOptions.bits = bits
      log(`initializing with keysize: ${bits}`)
    }
    this.exec.init(initOptions, (err) => {
      if (err) {
        return callback(err)
      }

      const self = this
      waterfall([
        (cb) => this.getConfig(cb),
        (conf, cb) => this.replaceConfig(defaults({}, this.opts.config, conf), cb)
      ], (err) => {
        if (err) { return callback(err) }
        self.clean = false
        self.initialized = true
        return callback(null, this)
      })
    })
  }

  /**
   * Delete the repo that was being used.
   * If the node was marked as `disposable` this will be called
   * automatically when the process is exited.
   *
   * @param {function(Error)} callback
   * @returns {undefined}
   */
  cleanup (callback) {
    if (this.clean) {
      return callback()
    }

    repoUtils.removeRepo(this.path, callback)
  }

  /**
   * Start the daemon.
   *
   * @param {Array<string>} [flags=[]] - Flags to be passed to the `ipfs daemon` command.
   * @param {function(Error, IpfsApi)} callback
   * @returns {undefined}
   */
  start (flags, callback) {
    if (typeof flags === 'function') {
      callback = flags
      flags = undefined // not used
    }

    this.exec.start((err) => {
      if (err) {
        return callback(err)
      }

      this._started = true
      this.api = this.exec
      this.exec.config.get((err, conf) => {
        if (err) {
          return callback(err)
        }

        this._apiAddr = conf.Addresses.API
        this._gatewayAddr = conf.Addresses.Gateway

        this.api.apiHost = multiaddr(conf.Addresses.API).nodeAddress().host
        this.api.apiPort = multiaddr(conf.Addresses.API).nodeAddress().port

        callback(null, this.api)
      })
    })
  }

  /**
   * Stop the daemon.
   *
   * @param {function(Error)} [callback]
   * @returns {undefined}
   */
  stop (callback) {
    callback = callback || function noop () {}

    if (!this.exec) {
      return callback()
    }

    this.exec.stop((err) => {
      if (err) {
        return callback(err)
      }

      this._started = false
      if (this.disposable) {
        return this.cleanup(callback)
      }

      return callback()
    })
  }

  /**
   * Kill the `ipfs daemon` process.
   *
   * First `SIGTERM` is sent, after 10.5 seconds `SIGKILL` is sent
   * if the process hasn't exited yet.
   *
   * @param {function()} callback - Called when the process was killed.
   * @returns {undefined}
   */
  killProcess (callback) {
    this.stop(callback)
  }

  /**
   * Get the pid of the `ipfs daemon` process.
   *
   * @returns {number}
   */
  pid () {
    throw new Error('not implemented')
  }

  /**
   * Call `ipfs config`
   *
   * If no `key` is passed, the whole config is returned as an object.
   *
   * @param {string} [key] - A specific config to retrieve.
   * @param {function(Error, (Object|string))} callback
   * @returns {undefined}
   */
  getConfig (key, callback) {
    if (typeof key === 'function') {
      callback = key
      key = undefined
    }

    this.exec.config.get(key, callback)
  }

  /**
   * Set a config value.
   *
   * @param {string} key
   * @param {string} value
   * @param {function(Error)} callback
   * @returns {undefined}
   */
  setConfig (key, value, callback) {
    this.exec.config.set(key, value, callback)
  }

  /**
   * Replace the current config with the provided one
   *
   * @param {object} config
   * @param {function(Error)} callback
   * @return {undefined}
   */
  replaceConfig (config, callback) {
    this.exec.config.replace(config, callback)
  }

  /**
   * Get the version of ipfs
   *
   * @param {function(Error, string)} callback
   * @returns {undefined}
   */
  version (callback) {
    this.exec.version(callback)
  }
}

module.exports = Node
<|endoftext|>


<commit_before>/**
 * make sure overrides get's called first!
 * - keeping the overrides import here works for installing Ghost as npm!
 *
 * the call order is the following:
 * - root index requires core module
 * - core index requires server
 * - overrides is the first package to load
 */
require('./overrides');

const debug = require('ghost-ignition').debug('boot:init');
const path = require('path');
const Promise = require('bluebird');
const config = require('../shared/config');
const {events, i18n} = require('./lib/common');
const logging = require('../shared/logging');
const migrator = require('./data/db/migrator');
const urlUtils = require('./../shared/url-utils');
let parentApp;

// Frontend Components
const themeService = require('../frontend/services/themes');
const appService = require('../frontend/services/apps');
const frontendSettings = require('../frontend/services/settings');

function initialiseServices() {
    // CASE: When Ghost is ready with bootstrapping (db migrations etc.), we can trigger the router creation.
    //       Reason is that the routers access the routes.yaml, which shouldn't and doesn't have to be validated to
    //       start Ghost in maintenance mode.
    // Routing is a bridge between the frontend and API
    const routing = require('../frontend/services/routing');
    // We pass the themeService API version here, so that the frontend services are less tightly-coupled
    routing.bootstrap.start(themeService.getApiVersion());

    const settings = require('./services/settings');
    const permissions = require('./services/permissions');
    const xmlrpc = require('./services/xmlrpc');
    const slack = require('./services/slack');
    const {mega} = require('./services/mega');
    const webhooks = require('./services/webhooks');
    const scheduling = require('./adapters/scheduling');

    debug('`initialiseServices` Start...');
    const getRoutesHash = () => frontendSettings.getCurrentHash('routes');

    return Promise.join(
        // Initialize the permissions actions and objects
        permissions.init(),
        xmlrpc.listen(),
        slack.listen(),
        mega.listen(),
        webhooks.listen(),
        settings.syncRoutesHash(getRoutesHash),
        appService.init(),
        scheduling.init({
            // NOTE: When changing API version need to consider how to migrate custom scheduling adapters
            //       that rely on URL to lookup persisted scheduled records (jobs, etc.). Ref: https://github.com/TryGhost/Ghost/pull/10726#issuecomment-489557162
            apiUrl: urlUtils.urlFor('api', {version: 'v3', versionType: 'admin'}, true)
        })
    ).then(function () {
        debug('XMLRPC, Slack, MEGA, Webhooks, Scheduling, Permissions done');

        // Initialise analytics events
        if (config.get('segment:key')) {
            require('./analytics-events').init();
        }
    }).then(function () {
        debug('...`initialiseServices` End');
    });
}

function initializeRecurringJobs() {
    // we don't want to kick off scheduled/recurring jobs that will interfere with tests
    if (process.env.NODE_ENV.match(/^testing/)) {
        return;
    }

    const jobsService = require('./services/jobs');

    if (config.get('backgroundJobs:emailAnalytics')) {
        jobsService.scheduleJob(
            'every 1 minute',
            path.resolve(__dirname, 'services', 'email-analytics', 'jobs', 'fetch-latest.js'),
            undefined,
            'email-analytics-fetch-latest'
        );
    }
}

/**
 * - initialise models
 * - initialise i18n
 * - load all settings into settings cache (almost every component makes use of this cache)
 * - load active theme
 * - create our express apps (site, admin, api)
 * - start the ghost server
 * - enable maintenance mode if migrations are missing
 */
const minimalRequiredSetupToStartGhost = (dbState) => {
    const settings = require('./services/settings');
    const jobService = require('./services/jobs');
    const models = require('./models');
    const GhostServer = require('./ghost-server');

    let ghostServer;

    // Initialize Ghost core internationalization
    i18n.init();
    debug('Default i18n done for core');

    models.init();
    debug('Models done');

    return settings.init()
        .then(() => {
            debug('Settings done');

            return frontendSettings.init();
        })
        .then(() => {
            debug('Frontend settings done');
            return themeService.init();
        })
        .then(() => {
            debug('Themes done');

            parentApp = require('./web/parent/app')();
            debug('Express Apps done');

            return new GhostServer(parentApp);
        })
        .then((_ghostServer) => {
            ghostServer = _ghostServer;

            ghostServer.registerCleanupTask(async () => {
                await jobService.shutdown();
            });

            // CASE: all good or db was just initialised
            if (dbState === 1 || dbState === 2) {
                events.emit('db.ready');

                return initialiseServices()
                    .then(() => {
                        initializeRecurringJobs();
                    })
                    .then(() => {
                        return ghostServer;
                    });
            }

            // CASE: migrations required, put blog into maintenance mode
            if (dbState === 4) {
                logging.info('Blog is in maintenance mode.');

                config.set('maintenance:enabled', true);

                migrator.migrate()
                    .then(() => {
                        return settings.reinit().then(() => {
                            events.emit('db.ready');
                            return initialiseServices();
                        });
                    })
                    .then(() => {
                        config.set('maintenance:enabled', false);
                        logging.info('Blog is out of maintenance mode.');
                        return GhostServer.announceServerReadiness();
                    })
                    .then(() => {
                        initializeRecurringJobs();
                    })
                    .catch((err) => {
                        return GhostServer.announceServerReadiness(err)
                            .finally(() => {
                                logging.error(err);
                                setTimeout(() => {
                                    process.exit(-1);
                                }, 100);
                            });
                    });

                return ghostServer;
            }
        });
};

/**
 * Connect to database.
 * Check db state.
 */
const isDatabaseInitialisationRequired = () => {
    const db = require('./data/db/connection');
    let dbState;

    return migrator.getState()
        .then((state) => {
            dbState = state;

            // CASE: db initialisation required, wait till finished
            if (dbState === 2) {
                return migrator.dbInit();
            }

            // CASE: is db incompatible? e.g. you can't connect a 0.11 database with Ghost 1.0 or 2.0
            if (dbState === 3) {
                return migrator.isDbCompatible(db)
                    .then(() => {
                        dbState = 2;
                        return migrator.dbInit();
                    });
            }
        })
        .then(() => {
            return minimalRequiredSetupToStartGhost(dbState);
        });
};

module.exports = isDatabaseInitialisationRequired;
<commit_msg>Adjusted email analytics job scheduling to avoid API spikes<commit_after>/**
 * make sure overrides get's called first!
 * - keeping the overrides import here works for installing Ghost as npm!
 *
 * the call order is the following:
 * - root index requires core module
 * - core index requires server
 * - overrides is the first package to load
 */
require('./overrides');

const debug = require('ghost-ignition').debug('boot:init');
const path = require('path');
const Promise = require('bluebird');
const config = require('../shared/config');
const {events, i18n} = require('./lib/common');
const logging = require('../shared/logging');
const migrator = require('./data/db/migrator');
const urlUtils = require('./../shared/url-utils');
let parentApp;

// Frontend Components
const themeService = require('../frontend/services/themes');
const appService = require('../frontend/services/apps');
const frontendSettings = require('../frontend/services/settings');

function initialiseServices() {
    // CASE: When Ghost is ready with bootstrapping (db migrations etc.), we can trigger the router creation.
    //       Reason is that the routers access the routes.yaml, which shouldn't and doesn't have to be validated to
    //       start Ghost in maintenance mode.
    // Routing is a bridge between the frontend and API
    const routing = require('../frontend/services/routing');
    // We pass the themeService API version here, so that the frontend services are less tightly-coupled
    routing.bootstrap.start(themeService.getApiVersion());

    const settings = require('./services/settings');
    const permissions = require('./services/permissions');
    const xmlrpc = require('./services/xmlrpc');
    const slack = require('./services/slack');
    const {mega} = require('./services/mega');
    const webhooks = require('./services/webhooks');
    const scheduling = require('./adapters/scheduling');

    debug('`initialiseServices` Start...');
    const getRoutesHash = () => frontendSettings.getCurrentHash('routes');

    return Promise.join(
        // Initialize the permissions actions and objects
        permissions.init(),
        xmlrpc.listen(),
        slack.listen(),
        mega.listen(),
        webhooks.listen(),
        settings.syncRoutesHash(getRoutesHash),
        appService.init(),
        scheduling.init({
            // NOTE: When changing API version need to consider how to migrate custom scheduling adapters
            //       that rely on URL to lookup persisted scheduled records (jobs, etc.). Ref: https://github.com/TryGhost/Ghost/pull/10726#issuecomment-489557162
            apiUrl: urlUtils.urlFor('api', {version: 'v3', versionType: 'admin'}, true)
        })
    ).then(function () {
        debug('XMLRPC, Slack, MEGA, Webhooks, Scheduling, Permissions done');

        // Initialise analytics events
        if (config.get('segment:key')) {
            require('./analytics-events').init();
        }
    }).then(function () {
        debug('...`initialiseServices` End');
    });
}

function initializeRecurringJobs() {
    // we don't want to kick off scheduled/recurring jobs that will interfere with tests
    if (process.env.NODE_ENV.match(/^testing/)) {
        return;
    }

    const jobsService = require('./services/jobs');

    if (config.get('backgroundJobs:emailAnalytics')) {
        // use a random seconds value to avoid spikes to external APIs on the minute
        const s = Math.floor(Math.random() * 60); // 0-59
        // run every 2 minutes, either on 1,3,5... or 2,4,6...
        const m = Math.floor(Math.random() * 2) + 1; // 1-2

        jobsService.scheduleJob(
            `${s} ${m}/2 * * * *`,
            path.resolve(__dirname, 'services', 'email-analytics', 'jobs', 'fetch-latest.js'),
            undefined,
            'email-analytics-fetch-latest'
        );
    }
}

/**
 * - initialise models
 * - initialise i18n
 * - load all settings into settings cache (almost every component makes use of this cache)
 * - load active theme
 * - create our express apps (site, admin, api)
 * - start the ghost server
 * - enable maintenance mode if migrations are missing
 */
const minimalRequiredSetupToStartGhost = (dbState) => {
    const settings = require('./services/settings');
    const jobService = require('./services/jobs');
    const models = require('./models');
    const GhostServer = require('./ghost-server');

    let ghostServer;

    // Initialize Ghost core internationalization
    i18n.init();
    debug('Default i18n done for core');

    models.init();
    debug('Models done');

    return settings.init()
        .then(() => {
            debug('Settings done');

            return frontendSettings.init();
        })
        .then(() => {
            debug('Frontend settings done');
            return themeService.init();
        })
        .then(() => {
            debug('Themes done');

            parentApp = require('./web/parent/app')();
            debug('Express Apps done');

            return new GhostServer(parentApp);
        })
        .then((_ghostServer) => {
            ghostServer = _ghostServer;

            ghostServer.registerCleanupTask(async () => {
                await jobService.shutdown();
            });

            // CASE: all good or db was just initialised
            if (dbState === 1 || dbState === 2) {
                events.emit('db.ready');

                return initialiseServices()
                    .then(() => {
                        initializeRecurringJobs();
                    })
                    .then(() => {
                        return ghostServer;
                    });
            }

            // CASE: migrations required, put blog into maintenance mode
            if (dbState === 4) {
                logging.info('Blog is in maintenance mode.');

                config.set('maintenance:enabled', true);

                migrator.migrate()
                    .then(() => {
                        return settings.reinit().then(() => {
                            events.emit('db.ready');
                            return initialiseServices();
                        });
                    })
                    .then(() => {
                        config.set('maintenance:enabled', false);
                        logging.info('Blog is out of maintenance mode.');
                        return GhostServer.announceServerReadiness();
                    })
                    .then(() => {
                        initializeRecurringJobs();
                    })
                    .catch((err) => {
                        return GhostServer.announceServerReadiness(err)
                            .finally(() => {
                                logging.error(err);
                                setTimeout(() => {
                                    process.exit(-1);
                                }, 100);
                            });
                    });

                return ghostServer;
            }
        });
};

/**
 * Connect to database.
 * Check db state.
 */
const isDatabaseInitialisationRequired = () => {
    const db = require('./data/db/connection');
    let dbState;

    return migrator.getState()
        .then((state) => {
            dbState = state;

            // CASE: db initialisation required, wait till finished
            if (dbState === 2) {
                return migrator.dbInit();
            }

            // CASE: is db incompatible? e.g. you can't connect a 0.11 database with Ghost 1.0 or 2.0
            if (dbState === 3) {
                return migrator.isDbCompatible(db)
                    .then(() => {
                        dbState = 2;
                        return migrator.dbInit();
                    });
            }
        })
        .then(() => {
            return minimalRequiredSetupToStartGhost(dbState);
        });
};

module.exports = isDatabaseInitialisationRequired;
<|endoftext|>


<commit_before>/**
 * CSS build pipeline for production mode
 *
 * @package: Everledger JS Toolchain
 * @author:  pospi <sam@everledger.io>
 * @since:   2016-10-06
 * @flow
 */

const { compose } = require('ramda');
const webpack = require('webpack');
const loader = require('webpack-partial/loader').default;
const plugin = require('webpack-partial/plugin').default;
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const postCSSModuleComponents = require('postcss-modules-component-plugin');

const { makePostCSSOptions } = require('../postcss-config');

const matchRegex = /\.(css|scss)$/;

module.exports = compose(
  loader({
    test: matchRegex,
    exclude: [/\/node_modules\//],
    loaders: [
      'style-loader',
      postCSSModuleComponents.loader(),  // use commonjs output mode for PostCSS modules instead of CSS module format
      ExtractTextPlugin.extract({
        // :IMPORTANT: do *not* use fallbackLoader, place your loaders prior to ExtractTextPlugin's
        fallbackLoader: [],
        // :IMPORTANT: you must specify loaders as strings here, as ExtractTextPlugin will break with objects
        loader: [
          'css-loader?sourceMap=true&importLoaders=1',
          'postcss-loader',
        ],
      }),
    ],
  }),
  plugin(new webpack.LoaderOptionsPlugin({
    test: matchRegex,
    options: { postcss: makePostCSSOptions('[emoji][hash:base64:5]') },
  }))
);
<commit_msg>make production CSS smaller, emoji and hash names provide the same entropy<commit_after>/**
 * CSS build pipeline for production mode
 *
 * @package: Everledger JS Toolchain
 * @author:  pospi <sam@everledger.io>
 * @since:   2016-10-06
 * @flow
 */

const { compose } = require('ramda');
const webpack = require('webpack');
const loader = require('webpack-partial/loader').default;
const plugin = require('webpack-partial/plugin').default;
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const postCSSModuleComponents = require('postcss-modules-component-plugin');

const { makePostCSSOptions } = require('../postcss-config');

const matchRegex = /\.(css|scss)$/;

module.exports = compose(
  loader({
    test: matchRegex,
    exclude: [/\/node_modules\//],
    loaders: [
      'style-loader',
      postCSSModuleComponents.loader(),  // use commonjs output mode for PostCSS modules instead of CSS module format
      ExtractTextPlugin.extract({
        // :IMPORTANT: do *not* use fallbackLoader, place your loaders prior to ExtractTextPlugin's
        fallbackLoader: [],
        // :IMPORTANT: you must specify loaders as strings here, as ExtractTextPlugin will break with objects
        loader: [
          'css-loader?sourceMap=true&importLoaders=1',
          'postcss-loader',
        ],
      }),
    ],
  }),
  plugin(new webpack.LoaderOptionsPlugin({
    test: matchRegex,
    options: { postcss: makePostCSSOptions('[emoji]') },
  }))
);
<|endoftext|>


<commit_before>const { ActionTransport } = require('@microfleet/core');
const fsort = require('redis-filtered-sort');

// helpers
const { processResult, mapResult } = require('../../list-utils');
const { SALES_ID_INDEX, SALES_DATA_PREFIX } = require('../../constants');
const key = require('../../redis-key');

function saleList({ params: opts }) {
  const { redis } = this;
  const { filter, criteria } = opts;
  const strFilter = typeof filter === 'string' ? filter : fsort.filter(filter || {});
  const order = opts.order || 'ASC';
  const offset = opts.offset || 0;
  const limit = opts.limit || 10;

  return redis
    .fsort(SALES_ID_INDEX, key(SALES_DATA_PREFIX, '*'), criteria, order, strFilter, Date.now(), offset, limit)
    .then(processResult(SALES_DATA_PREFIX, redis))
    .spread(mapResult(offset, limit));
}

saleList.transports = [ActionTransport.amqp];

module.exports = saleList;
<commit_msg>fix: access to sale.list via internal dispatch<commit_after>const { ActionTransport } = require('@microfleet/core');
const fsort = require('redis-filtered-sort');

// helpers
const { processResult, mapResult } = require('../../list-utils');
const { SALES_ID_INDEX, SALES_DATA_PREFIX } = require('../../constants');
const key = require('../../redis-key');

function saleList({ params: opts }) {
  const { redis } = this;
  const { filter, criteria } = opts;
  const strFilter = typeof filter === 'string' ? filter : fsort.filter(filter || {});
  const order = opts.order || 'ASC';
  const offset = opts.offset || 0;
  const limit = opts.limit || 10;

  return redis
    .fsort(SALES_ID_INDEX, key(SALES_DATA_PREFIX, '*'), criteria, order, strFilter, Date.now(), offset, limit)
    .then(processResult(SALES_DATA_PREFIX, redis))
    .spread(mapResult(offset, limit));
}

saleList.transports = [ActionTransport.amqp, ActionTransport.internal];

module.exports = saleList;
<|endoftext|>