<commit_before>'use strict'

const config = require(__dirname + '/../config')
const GitHub = require('./GitHub')
const markdownTable = require('markdown-table')
const md5 = require('md5')
const Mailgun = require('mailgun-js')
const NodeRSA = require('node-rsa')
const Notification = require('./Notification')
const objectPath = require('object-path')
const SiteConfig = require(__dirname + '/../siteConfig')
const slugify = require('slug')
const SubscriptionsManager = require('./SubscriptionsManager')
const uuid = require('node-uuid')
const yaml = require('js-yaml')

const Staticman = function (options, localConfig) {
  this.options = options
  this.localConfig = localConfig

  // Initialise GitHub API
  this.github = new GitHub({
    username: this.options.username,
    repository: this.options.repository,
    branch: this.options.branch
  })

  // Generate unique id
  this.uid = uuid.v1()

  // Initialise RSA
  this.rsa = new NodeRSA()
  this.rsa.importKey(config.get('rsaPrivateKey'))
}

Staticman.prototype._applyInternalFields = function (data) {
  let internalFields = {
    _id: this.uid
  }

  // Inject parent, if present
  if (this.options.parent) {
    internalFields._parent = this.options.parent
  }

  return Object.assign(internalFields, data)
}

Staticman.prototype._applyGeneratedFields = function (data) {
  const generatedFields = this.siteConfig.get('generatedFields')

  if (!generatedFields) return data

  Object.keys(generatedFields).forEach(field => {
    const generatedField = generatedFields[field]

    if ((typeof generatedField === 'object') && (!(generatedField instanceof Array))) {
      const options = generatedField.options || {}

      switch (generatedField.type) {
        case 'date':
          data[field] = this._createDate(options)

          break

        case 'slugify':
          if (typeof options.field === 'string') {
            data[field] = slugify(data[options.field]).toLowerCase()
          }

          break
      }
    } else {
      data[field] = generatedField
    }
  })

  return data
}

Staticman.prototype._applyTransforms = function (fields) {
  // Keep a copy of the original fields for future usage
  this._fieldsOriginal = Object.assign({}, fields)

  const transforms = this.siteConfig.get('transforms')

  if (!transforms) return Promise.resolve(fields)

  // This doesn't serve any purpose for now, but we might want to have
  // asynchronous transforms in the future.
  let queue = []

  Object.keys(transforms).forEach(field => {
    if (!fields[field]) return

    if (transforms[field] === 'md5') {
      fields[field] = md5(fields[field])
    }
  })

  return Promise.all(queue).then((results) => {
    return fields
  })
}

Staticman.prototype._checkForSpam = function (fields) {
  if (!this.siteConfig.get('akismet.enabled')) return Promise.resolve(fields)

  return new Promise((resolve, reject) => {
    const akismet = require('akismet').client({
      blog: config.get('akismet.site'),
      apiKey: config.get('akismet.apiKey')
    })

    akismet.checkSpam({
      user_ip: this.ip,
      user_agent: this.userAgent,
      comment_type: this.siteConfig.get('akismet.type'),
      comment_author: fields[this.siteConfig.get('akismet.author')],
      comment_author_email: fields[this.siteConfig.get('akismet.authorEmail')],
      comment_author_url: fields[this.siteConfig.get('akismet.authorUrl')],
      comment_content: fields[this.siteConfig.get('akismet.content')]
    }, (err, isSpam) => {
      if (err) return reject(err)

      if (isSpam) return reject('IS_SPAM')

      return resolve(fields)
    })
  })
}

Staticman.prototype._createDate = function (options) {
  const date = new Date()

  switch (options.format) {
    case 'timestamp':
      return date.getTime()

    case 'timestamp-seconds':
      return Math.floor(date.getTime() / 1000)

    case 'iso8601':
    default:
      return date.toISOString()
  }
}

Staticman.prototype._createFile = function (fields) {
  return new Promise((resolve, reject) => {
    switch (this.siteConfig.get('format').toLowerCase()) {
      case 'json':
        return resolve(JSON.stringify(fields))

      case 'yaml':
      case 'yml':
        try {
          const output = yaml.safeDump(fields)

          return resolve(output)
        } catch (err) {
          return reject(err)
        }

        break

      case 'frontmatter':
        const transforms = this.siteConfig.get('transforms')

        const contentField = transforms && Object.keys(transforms).find(field => {
          return transforms[field] === 'frontmatterContent'
        })

        if (!contentField) {
          return reject('NO_FRONTMATTER_CONTENT_TRANSFORM')
        }

        const content = fields[contentField]

        delete fields[contentField]

        try {
          const output = `---\n${yaml.safeDump(fields)}---\n${content}\n`

          return resolve(output)
        } catch (err) {
          return reject(err)
        }

        break

      default:
        return reject('INVALID_FORMAT')
    }
  })
}

Staticman.prototype._generatePRBody = function (fields) {
  let table = [
    ['Field', 'Content']
  ]

  Object.keys(fields).forEach(field => {
    table.push([field, fields[field]])
  })

  const message = this.siteConfig.get('pullRequestBody') + markdownTable(table)

  return message
}

Staticman.prototype._getConfig = function (force) {
  if (this.siteConfig && !force) return Promise.resolve(this.siteConfig)

  if (!this.options.config) return Promise.reject('NO_CONFIG_PATH')

  return this.github.readFile(this.options.config.file).then(data => {
    const config = objectPath.get(data, this.options.config.path)
    const validationErrors = this._validateConfig(config)

    if (validationErrors) {
      return Promise.reject(validationErrors)
    }

    if (config.branch !== this.options.branch) {
      return Promise.reject('BRANCH_MISMATCH')
    }
  })
}

Staticman.prototype._getNewFilePath = function (data) {
  const configFilename = this.siteConfig.get('filename')
  const filename = configFilename ? this._resolvePlaceholders(configFilename, {
    fields: data,
    options: this.options
  }) : this.uid

  const path = this._resolvePlaceholders(this.siteConfig.get('path'), {
    fields: data,
    options: this.options
  })

  const extension = this._getExtensionForFormat(this.siteConfig.get('format'))

  return `${path}/${filename}.${extension}`
}

Staticman.prototype._getExtensionForFormat = function (format) {
  switch (format.toLowerCase()) {
    case 'json':
      return 'json'

    case 'yaml':
    case 'yml':
      return 'yml'

    case 'frontmatter':
      return 'md'
  }
}

Staticman.prototype._processSubscriptions = function (fields, options) {
  // If there isn't a parent or if notifications are not enabled, there's
  // nothing to do here
  if (!options.parent || !this.siteConfig.get('notifications.enabled')) {
    return Promise.resolve(true)
  }

  // Initialise Mailgun
  const mailgun = Mailgun({
    apiKey: this.siteConfig.get('notifications.apiKey') || config.get('email.apiKey'),
    domain: this.siteConfig.get('notifications.domain') || config.get('email.domain')
  })

  // Initialise SubscriptionsManager
  const subscriptions = new SubscriptionsManager(this.github, mailgun)

  // Process existing subscriptions
  return subscriptions.get(options.parent).then(list => {
    if (list) {
      const notifications = new Notification(mailgun)

      notifications.send(list, fields, options, this.siteConfig)
    }

    if (options.subscribe && fields[options.subscribe]) {
      return subscriptions.set(options.parent, fields[options.subscribe]);
    }
  }).catch(err => {
    console.log(err.stack || err)
  })
}

Staticman.prototype._resolvePlaceholders = function (subject, baseObject) {
  const matches = subject.match(/{(.*?)}/g)

  if (!matches) return subject

  matches.forEach((match) => {
    const escapedMatch = match.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
    const property = match.slice(1, -1)

    let newText

    switch (property) {
      case '@timestamp':
        newText = new Date().getTime()

        break

      case '@id':
        newText = this.uid

        break

      default:
        newText = objectPath.get(baseObject, property) || ''
    }

    subject = subject.replace(new RegExp(escapedMatch, 'g'), newText)
  })

  return subject
}

Staticman.prototype._validateConfig = function (config) {
  if (!config) {
    return {
      code: 'MISSING_CONFIG_BLOCK'
    }
  }

  const requiredFields = [
    'allowedFields',
    'branch',
    'format',
    'path'
  ]

  let missingFields = []

  // Checking for missing required fields
  requiredFields.forEach((requiredField) => {
    if (objectPath.get(config, requiredField) === undefined) {
      missingFields.push(requiredField)
    }
  })

  if (missingFields.length) {
    return {
      code: 'MISSING_CONFIG_FIELDS',
      data: missingFields
    }
  }

  // Check origin
  if (config.allowedOrigins && config.allowedOrigins.length) {
    if (this.options.origin) {
      const url = require('url').parse(this.options.origin)

      const validOrigin = config.allowedOrigins.some(origin => {
        return origin === url.hostname
      })

      if (!validOrigin) {
        return {
          code: 'INVALID_ORIGIN',
          data: null
        }
      }
    }
  }

  this.siteConfig = SiteConfig(config, this.rsa)

  return null
}

Staticman.prototype._validateFields = function (fields) {
  let errors = []
  let missingRequiredFields = []
  let invalidFields = []

  Object.keys(fields).forEach(field => {
    // Check for any invalid fields
    if ((this.siteConfig.get('allowedFields').indexOf(field) === -1) && (fields[field] !== '')) {
      invalidFields.push(field)
    }

    // Trim fields
    if (typeof fields[field] === 'string') {
      fields[field] = fields[field].trim()
    }
  })

  // Check for missing required fields
  this.siteConfig.get('requiredFields').forEach(field => {
    if ((fields[field] === undefined) || (fields[field] === '')) {
      missingRequiredFields.push(field)
    }
  })

  if (missingRequiredFields.length) {
    errors.push({
      code: 'MISSING_REQUIRED_FIELDS',
      data: missingRequiredFields
    })
  }

  if (invalidFields.length) {
    errors.push({
      code: 'INVALID_FIELDS',
      data: invalidFields
    })
  }

  if (errors.length) return errors

  return null
}

Staticman.prototype.process = function (fields, options) {
  return this._getConfig().then(res => {
    return this._checkForSpam(fields)
  }).then(fields => {
    // Validate fields
    const fieldErrors = this._validateFields(fields)

    if (fieldErrors) return Promise.reject(fieldErrors)

    // Add generated fields
    fields = this._applyGeneratedFields(fields)

    // Apply transforms
    return this._applyTransforms(fields)
  }).then(fields => {
    return this._applyInternalFields(fields)
  }).then(fields => {
    // Create file
    return this._createFile(fields)
  }).then(data => {
    const filePath = this._getNewFilePath(fields)

    if (this.siteConfig.get('moderation')) {
      const newBranch = 'staticman_' + this.uid

      return this.github.writeFileAndSendPR(filePath, data, newBranch, this.siteConfig.get('commitMessage'), this._generatePRBody(fields))
    }

    return this.github.writeFile(filePath, data, branch, this.siteConfig.get('commitMessage'))
  }).then(result => {
    this._processSubscriptions(this._fieldsOriginal, options)

    return {
      fields: fields,
      redirect: options.redirect ? options.redirect : false
    }
  })
}

Staticman.prototype.setConfig = function (config) {
  this.options.config = config
}

Staticman.prototype.setIp = function (ip) {
  this.ip = ip
}

Staticman.prototype.setUserAgent = function (userAgent) {
  this.userAgent = userAgent
}

module.exports = Staticman
<commit_msg>Add error for missing origin<commit_after>'use strict'

const config = require(__dirname + '/../config')
const GitHub = require('./GitHub')
const markdownTable = require('markdown-table')
const md5 = require('md5')
const Mailgun = require('mailgun-js')
const NodeRSA = require('node-rsa')
const Notification = require('./Notification')
const objectPath = require('object-path')
const SiteConfig = require(__dirname + '/../siteConfig')
const slugify = require('slug')
const SubscriptionsManager = require('./SubscriptionsManager')
const uuid = require('node-uuid')
const yaml = require('js-yaml')

const Staticman = function (options, localConfig) {
  this.options = options
  this.localConfig = localConfig

  // Initialise GitHub API
  this.github = new GitHub({
    username: this.options.username,
    repository: this.options.repository,
    branch: this.options.branch
  })

  // Generate unique id
  this.uid = uuid.v1()

  // Initialise RSA
  this.rsa = new NodeRSA()
  this.rsa.importKey(config.get('rsaPrivateKey'))
}

Staticman.prototype._applyInternalFields = function (data) {
  let internalFields = {
    _id: this.uid
  }

  // Inject parent, if present
  if (this.options.parent) {
    internalFields._parent = this.options.parent
  }

  return Object.assign(internalFields, data)
}

Staticman.prototype._applyGeneratedFields = function (data) {
  const generatedFields = this.siteConfig.get('generatedFields')

  if (!generatedFields) return data

  Object.keys(generatedFields).forEach(field => {
    const generatedField = generatedFields[field]

    if ((typeof generatedField === 'object') && (!(generatedField instanceof Array))) {
      const options = generatedField.options || {}

      switch (generatedField.type) {
        case 'date':
          data[field] = this._createDate(options)

          break

        case 'slugify':
          if (typeof options.field === 'string') {
            data[field] = slugify(data[options.field]).toLowerCase()
          }

          break
      }
    } else {
      data[field] = generatedField
    }
  })

  return data
}

Staticman.prototype._applyTransforms = function (fields) {
  // Keep a copy of the original fields for future usage
  this._fieldsOriginal = Object.assign({}, fields)

  const transforms = this.siteConfig.get('transforms')

  if (!transforms) return Promise.resolve(fields)

  // This doesn't serve any purpose for now, but we might want to have
  // asynchronous transforms in the future.
  let queue = []

  Object.keys(transforms).forEach(field => {
    if (!fields[field]) return

    if (transforms[field] === 'md5') {
      fields[field] = md5(fields[field])
    }
  })

  return Promise.all(queue).then((results) => {
    return fields
  })
}

Staticman.prototype._checkForSpam = function (fields) {
  if (!this.siteConfig.get('akismet.enabled')) return Promise.resolve(fields)

  return new Promise((resolve, reject) => {
    const akismet = require('akismet').client({
      blog: config.get('akismet.site'),
      apiKey: config.get('akismet.apiKey')
    })

    akismet.checkSpam({
      user_ip: this.ip,
      user_agent: this.userAgent,
      comment_type: this.siteConfig.get('akismet.type'),
      comment_author: fields[this.siteConfig.get('akismet.author')],
      comment_author_email: fields[this.siteConfig.get('akismet.authorEmail')],
      comment_author_url: fields[this.siteConfig.get('akismet.authorUrl')],
      comment_content: fields[this.siteConfig.get('akismet.content')]
    }, (err, isSpam) => {
      if (err) return reject(err)

      if (isSpam) return reject('IS_SPAM')

      return resolve(fields)
    })
  })
}

Staticman.prototype._createDate = function (options) {
  const date = new Date()

  switch (options.format) {
    case 'timestamp':
      return date.getTime()

    case 'timestamp-seconds':
      return Math.floor(date.getTime() / 1000)

    case 'iso8601':
    default:
      return date.toISOString()
  }
}

Staticman.prototype._createFile = function (fields) {
  return new Promise((resolve, reject) => {
    switch (this.siteConfig.get('format').toLowerCase()) {
      case 'json':
        return resolve(JSON.stringify(fields))

      case 'yaml':
      case 'yml':
        try {
          const output = yaml.safeDump(fields)

          return resolve(output)
        } catch (err) {
          return reject(err)
        }

        break

      case 'frontmatter':
        const transforms = this.siteConfig.get('transforms')

        const contentField = transforms && Object.keys(transforms).find(field => {
          return transforms[field] === 'frontmatterContent'
        })

        if (!contentField) {
          return reject('NO_FRONTMATTER_CONTENT_TRANSFORM')
        }

        const content = fields[contentField]

        delete fields[contentField]

        try {
          const output = `---\n${yaml.safeDump(fields)}---\n${content}\n`

          return resolve(output)
        } catch (err) {
          return reject(err)
        }

        break

      default:
        return reject('INVALID_FORMAT')
    }
  })
}

Staticman.prototype._generatePRBody = function (fields) {
  let table = [
    ['Field', 'Content']
  ]

  Object.keys(fields).forEach(field => {
    table.push([field, fields[field]])
  })

  const message = this.siteConfig.get('pullRequestBody') + markdownTable(table)

  return message
}

Staticman.prototype._getConfig = function (force) {
  if (this.siteConfig && !force) return Promise.resolve(this.siteConfig)

  if (!this.options.config) return Promise.reject('NO_CONFIG_PATH')

  return this.github.readFile(this.options.config.file).then(data => {
    const config = objectPath.get(data, this.options.config.path)
    const validationErrors = this._validateConfig(config)

    if (validationErrors) {
      return Promise.reject(validationErrors)
    }

    if (config.branch !== this.options.branch) {
      return Promise.reject('BRANCH_MISMATCH')
    }
  })
}

Staticman.prototype._getNewFilePath = function (data) {
  const configFilename = this.siteConfig.get('filename')
  const filename = configFilename ? this._resolvePlaceholders(configFilename, {
    fields: data,
    options: this.options
  }) : this.uid

  const path = this._resolvePlaceholders(this.siteConfig.get('path'), {
    fields: data,
    options: this.options
  })

  const extension = this._getExtensionForFormat(this.siteConfig.get('format'))

  return `${path}/${filename}.${extension}`
}

Staticman.prototype._getExtensionForFormat = function (format) {
  switch (format.toLowerCase()) {
    case 'json':
      return 'json'

    case 'yaml':
    case 'yml':
      return 'yml'

    case 'frontmatter':
      return 'md'
  }
}

Staticman.prototype._processSubscriptions = function (fields, options) {
  // If there isn't a parent or if notifications are not enabled, there's
  // nothing to do here
  if (!options.parent || !this.siteConfig.get('notifications.enabled')) {
    return Promise.resolve(true)
  }

  // Initialise Mailgun
  const mailgun = Mailgun({
    apiKey: this.siteConfig.get('notifications.apiKey') || config.get('email.apiKey'),
    domain: this.siteConfig.get('notifications.domain') || config.get('email.domain')
  })

  // Initialise SubscriptionsManager
  const subscriptions = new SubscriptionsManager(this.github, mailgun)

  // Process existing subscriptions
  return subscriptions.get(options.parent).then(list => {
    if (list) {
      const notifications = new Notification(mailgun)

      notifications.send(list, fields, options, this.siteConfig)
    }

    if (options.subscribe && fields[options.subscribe]) {
      return subscriptions.set(options.parent, fields[options.subscribe]);
    }
  }).catch(err => {
    console.log(err.stack || err)
  })
}

Staticman.prototype._resolvePlaceholders = function (subject, baseObject) {
  const matches = subject.match(/{(.*?)}/g)

  if (!matches) return subject

  matches.forEach((match) => {
    const escapedMatch = match.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
    const property = match.slice(1, -1)

    let newText

    switch (property) {
      case '@timestamp':
        newText = new Date().getTime()

        break

      case '@id':
        newText = this.uid

        break

      default:
        newText = objectPath.get(baseObject, property) || ''
    }

    subject = subject.replace(new RegExp(escapedMatch, 'g'), newText)
  })

  return subject
}

Staticman.prototype._validateConfig = function (config) {
  if (!config) {
    return {
      code: 'MISSING_CONFIG_BLOCK'
    }
  }

  const requiredFields = [
    'allowedFields',
    'branch',
    'format',
    'path'
  ]

  let missingFields = []

  // Checking for missing required fields
  requiredFields.forEach((requiredField) => {
    if (objectPath.get(config, requiredField) === undefined) {
      missingFields.push(requiredField)
    }
  })

  if (missingFields.length) {
    return {
      code: 'MISSING_CONFIG_FIELDS',
      data: missingFields
    }
  }

  // Check origin
  if (config.allowedOrigins && config.allowedOrigins.length) {
    if (this.options.origin) {
      const url = require('url').parse(this.options.origin)

      const validOrigin = config.allowedOrigins.some(origin => {
        return origin === url.hostname
      })

      if (!validOrigin) {
        return {
          code: 'INVALID_ORIGIN',
          data: null
        }
      }
    } else {
      return {
        code: 'MISSING_ORIGIN',
        data: null
      }
    }
  }

  this.siteConfig = SiteConfig(config, this.rsa)

  return null
}

Staticman.prototype._validateFields = function (fields) {
  let errors = []
  let missingRequiredFields = []
  let invalidFields = []

  Object.keys(fields).forEach(field => {
    // Check for any invalid fields
    if ((this.siteConfig.get('allowedFields').indexOf(field) === -1) && (fields[field] !== '')) {
      invalidFields.push(field)
    }

    // Trim fields
    if (typeof fields[field] === 'string') {
      fields[field] = fields[field].trim()
    }
  })

  // Check for missing required fields
  this.siteConfig.get('requiredFields').forEach(field => {
    if ((fields[field] === undefined) || (fields[field] === '')) {
      missingRequiredFields.push(field)
    }
  })

  if (missingRequiredFields.length) {
    errors.push({
      code: 'MISSING_REQUIRED_FIELDS',
      data: missingRequiredFields
    })
  }

  if (invalidFields.length) {
    errors.push({
      code: 'INVALID_FIELDS',
      data: invalidFields
    })
  }

  if (errors.length) return errors

  return null
}

Staticman.prototype.process = function (fields, options) {
  return this._getConfig().then(res => {
    return this._checkForSpam(fields)
  }).then(fields => {
    // Validate fields
    const fieldErrors = this._validateFields(fields)

    if (fieldErrors) return Promise.reject(fieldErrors)

    // Add generated fields
    fields = this._applyGeneratedFields(fields)

    // Apply transforms
    return this._applyTransforms(fields)
  }).then(fields => {
    return this._applyInternalFields(fields)
  }).then(fields => {
    // Create file
    return this._createFile(fields)
  }).then(data => {
    const filePath = this._getNewFilePath(fields)

    if (this.siteConfig.get('moderation')) {
      const newBranch = 'staticman_' + this.uid

      return this.github.writeFileAndSendPR(filePath, data, newBranch, this.siteConfig.get('commitMessage'), this._generatePRBody(fields))
    }

    return this.github.writeFile(filePath, data, branch, this.siteConfig.get('commitMessage'))
  }).then(result => {
    this._processSubscriptions(this._fieldsOriginal, options)

    return {
      fields: fields,
      redirect: options.redirect ? options.redirect : false
    }
  })
}

Staticman.prototype.setConfig = function (config) {
  this.options.config = config
}

Staticman.prototype.setIp = function (ip) {
  this.ip = ip
}

Staticman.prototype.setUserAgent = function (userAgent) {
  this.userAgent = userAgent
}

module.exports = Staticman
<|endoftext|>


<commit_before>"use strict";

var async = require("async");
var _ = require("lodash");
var request = require("request");
var utils = require("./util/Utils");
var validator = require("./util/Validator");
var CONST = require("./util/Const");
var log = require("./util/Logger");
var config = require("./util/Config");
var internalTransport;

/**
 * Will issue all calls using async and request
 * @param requests
 * @param done - callback method to call once finish processing all requests
 */
function issueCalls(requests, done) {
    if (!internalTransport) {
        log.debug("[batchelor] Transport running issueCalls requests=" + JSON.stringify(requests));
        var requestsMap = {};
        // creates the collection of 'requests' (holds a collection of getReq methods)
        var allRequests = _validateRequests(requests);
        allRequests.valid.forEach(function (req) {
            requestsMap[req.name] = _getAsyncRequestWrapper(req);
        });

        async.parallelLimit(requestsMap, config.getConf().maxConcurrentBatches, function (err, results) {
            if (err) {
                log.error("[batchelor] Transport Error in running requests, err: " + err);
                done(err);
            }
            else {
                log.debug("[batchelor] Transport finished running requests, results:" + JSON.stringify(results));
                _.assign(results, allRequests.invalid);
                done(null, results);
            }
        });
    }
    else {
        internalTransport.issueCalls(requests, done);
    }
}

/**
 * Allows overriding transport layer or clearing it (by providing void 0)
 * @param transport
 */
function setTransport(transport) {
    if (!transport || (transport && typeof transport.issueCalls === "function")) {
        internalTransport = transport;
    }
}

/**
 * method building the request for the running process
 * @param requests
 * @returns {{valid: Array, invalid: {}}}
 * @private
 */
function _validateRequests(requests) {
    log.debug("[batchelor] Transport _validateRequests");
    requests = utils.padArray(requests);
    var requestNames = {};
    var batchRequests = {
        valid: [],
        invalid: {}
    };
    requests.forEach(function (req) {
        var clonedRequest = _.clone(config.getConf().request, true); //Deep clone
        _.merge(clonedRequest, req);

        if (validator.isValidRequest(clonedRequest)) {
            if (!requestNames[clonedRequest.name]) {
                if (clonedRequest.body && typeof clonedRequest === "object") {
                    clonedRequest.json = true;
                }
                batchRequests.valid.push(validator.cleanRequest(clonedRequest));
                requestNames[clonedRequest.name] = true;
            }
            else {
                batchRequests.invalid[clonedRequest.name] = CONST.RESPONSE_TYPE.DUPLICATE_NAMES;
            }
        }
        else {
            batchRequests.invalid[clonedRequest.name || "missingName"] = CONST.RESPONSE_TYPE.INVALID_TASK;
        }
    });

    return batchRequests;
}

/**
 * method to be called in parallel from async.parallelLimit
 * @param req - request to threat
 * @returns {Function}
 * @private
 */
function _getAsyncRequestWrapper(req) {
    return function (done) {
        req.body = req.body || req.data;
        try {
            req.headers.HTTP_X_FORWARDED_FOR = req.ip;

            req.timeout = parseInt(req.timeout, 10) || req.timeout;
            log.debug("[batchelor] Transport Requesting URL: " + req.url + ", headers: " + JSON.stringify(req.headers) + ", method: " + req.method + ", body(when POST method): " + req.body + ", timeout: " + req.timeout);

            // request to the API host for every job
            request(req, function (error, response, body) {
                var result = _processResponse(error, response, body, req);
                done(null, result);
            });
        }
        catch (ex) {
            log.error("[batchelor] Transport exception parsing the body or in request, ex: " + ex.message);
            done(ex);
        }
    };
}

/**
 * callback method to be called once request return from call
 * @param originalRequest
 * @param error
 * @param response
 * @param body
 * @private
 */
function _processResponse(error, response, body, originalRequest) {
    log.debug("[batchelor] Transport _processResponse");
    var result = {};
    var rawHeaders;

    if (error || !response) {
        log.debug("[batchelor] Transport _processResponse no response or error with originalRequest.url: " + originalRequest.url + ", IP: " + originalRequest.ip + (error ? " Error Code: " + error.code : ""));
        result = (error && error.code) ? (CONST.RESPONSE_TYPE[error.code] || CONST.RESPONSE_TYPE.ERROR_API_URL) : CONST.RESPONSE_TYPE.ERROR_API_URL;
    }
    else {
        try {
            log.debug("[batchelor] Transport _processResponse body: " + body + " originalRequest.url: " + originalRequest.url);
            if (typeof  body === "string") {
                result.body = JSON.parse(body);
            }
            else {
                result.body = body;
            }
        }
        catch (err) {
            result.body = body;//Might be string and not JSON
            log.debug(CONST.BODY_RESPONSE.NO_JSON_OBJECT + ", IP: " + originalRequest.ip + " body: " + body + "originalRequest.url: " + originalRequest.url);
        }

        // set the headers/status in the result object
        result.headers = response.headers;
        result.statusCode = result.statusCode || response.statusCode;
        result.HTTPStatus = result.HTTPStatus || response.statusCode;

        // if the flag originalHeader is true, add originalHeader to the response
        if (config.getConf().originalHeader || originalRequest.caseSensitiveHeaders) {

            // call to the method in case one of the options is true
            rawHeaders = _getRawOriginalHeaders(response.rawHeaders);

            if (config.getConf().originalHeader) {
                result.originalHeader = rawHeaders.originalHeadersStr;
            }

            result.headers = (originalRequest.caseSensitiveHeaders) ? rawHeaders.originalHeadersObj : result.headers;
        }
    }

    return result;
}

/**
 * The raw headers as they were received are pass in the headers parameter, which is an array of [key, value, key2, value2, ...]
 * @param rawHeaders: array
 * @private
 */
function _getRawOriginalHeaders(rawHeaders) {
    rawHeaders = rawHeaders || [];
    var rawHeadersStr = "";
    var rawHeadersObj = {};
    for (var i = 0, j = 1; i < rawHeaders.length; i += 2, j += 2) {
        rawHeadersStr += rawHeaders[i] + ": " + rawHeaders[j];
        rawHeadersObj[rawHeaders[i]] = rawHeaders[j];
        // for the last headers don't add '\n'
        rawHeadersStr += (i < rawHeaders.length - 2) ? "\n" : "";
    }
    return {
        originalHeadersStr: rawHeadersStr,
        originalHeadersObj: rawHeadersObj
    };
}

module.exports = {
    issueCalls: issueCalls,
    setTransport: setTransport
};
<commit_msg>fixed request object building<commit_after>"use strict";

var async = require("async");
var _ = require("lodash");
var request = require("request");
var utils = require("./util/Utils");
var validator = require("./util/Validator");
var CONST = require("./util/Const");
var log = require("./util/Logger");
var config = require("./util/Config");
var internalTransport;

/**
 * Will issue all calls using async and request
 * @param requests
 * @param done - callback method to call once finish processing all requests
 */
function issueCalls(requests, done) {
    if (!internalTransport) {
        log.debug("[batchelor] Transport running issueCalls requests=" + JSON.stringify(requests));
        var requestsMap = {};
        // creates the collection of 'requests' (holds a collection of getReq methods)
        var allRequests = _validateRequests(requests);
        allRequests.valid.forEach(function (req) {
            requestsMap[req.name] = _getAsyncRequestWrapper(req);
        });

        async.parallelLimit(requestsMap, config.getConf().maxConcurrentBatches, function (err, results) {
            if (err) {
                log.error("[batchelor] Transport Error in running requests, err: " + err);
                done(err);
            }
            else {
                log.debug("[batchelor] Transport finished running requests, results:" + JSON.stringify(results));
                _.assign(results, allRequests.invalid);
                done(null, results);
            }
        });
    }
    else {
        internalTransport.issueCalls(requests, done);
    }
}

/**
 * Allows overriding transport layer or clearing it (by providing void 0)
 * @param transport
 */
function setTransport(transport) {
    if (!transport || (transport && typeof transport.issueCalls === "function")) {
        internalTransport = transport;
    }
}

/**
 * method building the request for the running process
 * @param requests
 * @returns {{valid: Array, invalid: {}}}
 * @private
 */
function _validateRequests(requests) {
    log.debug("[batchelor] Transport _validateRequests");
    requests = utils.padArray(requests);
    var requestNames = {};
    var batchRequests = {
        valid: [],
        invalid: {}
    };
    requests.forEach(function (req) {
        var clonedRequest = _.clone(config.getConf().request, true); //Deep clone
        _.merge(clonedRequest, req);

        if (validator.isValidRequest(clonedRequest)) {
            if (!requestNames[clonedRequest.name]) {
                if (clonedRequest.body && typeof clonedRequest === "object") {
                    clonedRequest.json = true;
                }
                batchRequests.valid.push(validator.cleanRequest(clonedRequest));
                requestNames[clonedRequest.name] = true;
            }
            else {
                batchRequests.invalid[clonedRequest.name] = CONST.RESPONSE_TYPE.DUPLICATE_NAMES;
            }
        }
        else {
            batchRequests.invalid[clonedRequest.name || "missingName"] = CONST.RESPONSE_TYPE.INVALID_TASK;
        }
    });

    return batchRequests;
}

/**
 * method to be called in parallel from async.parallelLimit
 * @param req - request to threat
 * @returns {Function}
 * @private
 */
function _getAsyncRequestWrapper(req) {
    return function (done) {
        req.body = req.body || req.data;
        delete req.data;
        try {
            req.headers.HTTP_X_FORWARDED_FOR = req.ip;

            req.timeout = parseInt(req.timeout, 10) || req.timeout;
            log.debug("[batchelor] Transport Requesting URL: " + req.url + ", headers: " + JSON.stringify(req.headers) + ", method: " + req.method + ", body(when POST method): " + req.body + ", timeout: " + req.timeout);

            // request to the API host for every job
            request(req, function (error, response, body) {
                var result = _processResponse(error, response, body, req);
                done(null, result);
            });
        }
        catch (ex) {
            log.error("[batchelor] Transport exception parsing the body or in request, ex: " + ex.message);
            done(ex);
        }
    };
}

/**
 * callback method to be called once request return from call
 * @param originalRequest
 * @param error
 * @param response
 * @param body
 * @private
 */
function _processResponse(error, response, body, originalRequest) {
    log.debug("[batchelor] Transport _processResponse");
    var result = {};
    var rawHeaders;

    if (error || !response) {
        log.debug("[batchelor] Transport _processResponse no response or error with originalRequest.url: " + originalRequest.url + ", IP: " + originalRequest.ip + (error ? " Error Code: " + error.code : ""));
        result = (error && error.code) ? (CONST.RESPONSE_TYPE[error.code] || CONST.RESPONSE_TYPE.ERROR_API_URL) : CONST.RESPONSE_TYPE.ERROR_API_URL;
    }
    else {
        try {
            log.debug("[batchelor] Transport _processResponse body: " + body + " originalRequest.url: " + originalRequest.url);
            if (typeof  body === "string") {
                result.body = JSON.parse(body);
            }
            else {
                result.body = body;
            }
        }
        catch (err) {
            result.body = body;//Might be string and not JSON
            log.debug(CONST.BODY_RESPONSE.NO_JSON_OBJECT + ", IP: " + originalRequest.ip + " body: " + body + "originalRequest.url: " + originalRequest.url);
        }

        // set the headers/status in the result object
        result.headers = response.headers;
        result.statusCode = result.statusCode || response.statusCode;
        result.HTTPStatus = result.HTTPStatus || response.statusCode;

        // if the flag originalHeader is true, add originalHeader to the response
        if (config.getConf().originalHeader || originalRequest.caseSensitiveHeaders) {

            // call to the method in case one of the options is true
            rawHeaders = _getRawOriginalHeaders(response.rawHeaders);

            if (config.getConf().originalHeader) {
                result.originalHeader = rawHeaders.originalHeadersStr;
            }

            result.headers = (originalRequest.caseSensitiveHeaders) ? rawHeaders.originalHeadersObj : result.headers;
        }
    }

    return result;
}

/**
 * The raw headers as they were received are pass in the headers parameter, which is an array of [key, value, key2, value2, ...]
 * @param rawHeaders: array
 * @private
 */
function _getRawOriginalHeaders(rawHeaders) {
    rawHeaders = rawHeaders || [];
    var rawHeadersStr = "";
    var rawHeadersObj = {};
    for (var i = 0, j = 1; i < rawHeaders.length; i += 2, j += 2) {
        rawHeadersStr += rawHeaders[i] + ": " + rawHeaders[j];
        rawHeadersObj[rawHeaders[i]] = rawHeaders[j];
        // for the last headers don't add '\n'
        rawHeadersStr += (i < rawHeaders.length - 2) ? "\n" : "";
    }
    return {
        originalHeadersStr: rawHeadersStr,
        originalHeadersObj: rawHeadersObj
    };
}

module.exports = {
    issueCalls: issueCalls,
    setTransport: setTransport
};
<|endoftext|>


<commit_before>// bounce tests

exports.register = function () {
    var plugin = this;
    plugin.register_hook('mail',      'refresh_config');
    plugin.register_hook('mail',      'reject_all');
    plugin.register_hook('data',      'single_recipient');
    plugin.register_hook('data',      'bad_rcpt');
    plugin.register_hook('data_post', 'empty_return_path');
};

exports.refresh_config = function (next, connection) {
    var plugin = this;

    var check_defaults = {
        reject_all        : false,
        single_recipient  : true,
        empty_return_path : false,
        bad_rcpt          : true,
    };
    var reject_defaults = {
        single_recipient : true,
        empty_return_path: false,
    };

    var bools = [];
    for (var cd in check_defaults) {
        bools.push('checks.' + (check_defaults[cd] ? '+' : '-') + cd);
    }
    for (var rd in reject_defaults) {
        bools.push('reject.' + (reject_defaults[rd] ? '+' : '-') + rd);
    }

    plugin.cfg = plugin.config.get('bounce.ini', { booleans: bools });

    // Legacy config handling
    if (plugin.cfg.main.reject_invalid) {
        connection.logerror(plugin, "bounce.ini is out of date, please update!");
        plugin.cfg.checks.single_recipient=true;
        plugin.cfg.reject.single_recipient=true;
    }

    if (plugin.cfg.main.reject_all) {
        connection.logerror(plugin, "bounce.ini is out of date, please update!");
        plugin.cfg.checks.reject_all=true;
    }

    plugin.cfg.invalid_addrs = plugin.config.get('bounce_bad_rcpt', 'list');
    return next();
};

exports.reject_all = function (next, connection, params) {
    var plugin = this;
    if (!plugin.cfg.checks.reject_all) return next();
    var mail_from = params[0];

    if (!plugin.has_null_sender(connection, mail_from)) {
        return next(); // bounce messages are from null senders
    }

    connection.results.add(plugin, {fail: 'bounces_accepted', emit: 1 });
    return next(DENY, "No bounces accepted here");
};

exports.single_recipient = function(next, connection) {
    var plugin = this;
    if (!plugin.cfg.checks.single_recipient) return next();
    if (!plugin.has_null_sender(connection)) return next();

    // Valid bounces have a single recipient
    if (connection.transaction.rcpt_to.length === 1) {
        connection.results.add(plugin, {pass: 'single_recipient', emit: true });
        return next();
    }

    connection.loginfo(plugin, "bounce with too many recipients to: " +
        connection.transaction.rcpt_to.join(','));

    connection.results.add(plugin, {fail: 'single_recipient', emit: true });

    if (!plugin.cfg.reject.single_recipient) return next();

    return next(DENY, "this bounce message does not have 1 recipient");
};

exports.empty_return_path = function(next, connection) {
    var plugin = this;
    if (!plugin.cfg.checks.empty_return_path) return next();
    if (!plugin.has_null_sender(connection)) return next();

    // Bounce messages generally do not have a Return-Path set. This checks
    // for that. But whether it should is worth questioning...

    // On Jan 20, 2014, Matt Simerson examined the most recent 50,000 mail
    // connections for the presence of Return-Path in bounce messages. I
    // found 14 hits, 12 of which were from Google, in response to
    // undeliverable DMARC reports (IE, automated messages that Google
    // shouldn't have replied to). Another appears to be a valid bounce from
    // a poorly configured mailer, and the 14th was a confirmed spam kill.
    // Unless new data demonstrate otherwise, this should remain disabled.

    // Return-Path, aka Reverse-PATH, Envelope FROM, RFC5321.MailFrom
    // validate that the Return-Path header is empty, RFC 3834

    var rp = connection.transaction.header.get('Return-Path');
    if (!rp) {
        connection.results.add(plugin, {pass: 'empty_return_path' });
        return next();
    }

    if (rp === '<>') {
        connection.results.add(plugin, {pass: 'empty_return_path' });
        return next();
    }

    connection.results.add(plugin, {fail: 'empty_return_path', emit: 1 });
    return next(DENY, "bounce with non-empty Return-Path (RFC 3834)");
};

exports.bad_rcpt = function (next, connection) {
    var plugin = this;
    if (!plugin.cfg.checks.bad_rcpt) return next();
    if (!plugin.has_null_sender(connection)) return next();
    if (!plugin.cfg.invalid_addrs) return next();

    for (var i=0; i < connection.transaction.rcpt_to.length; i++) {
        var rcpt = connection.transaction.rcpt_to[i].address();
        if (plugin.cfg.invalid_addrs.indexOf(rcpt) === -1) continue;
        connection.results.add(plugin, {fail: 'bad_rcpt', emit: 1 });
        return next(DENY, "That recipient does not accept bounces");
    }

    connection.results.add(plugin, {pass: 'bad_rcpt'});
    return next();
};

exports.has_null_sender = function (connection, mail_from) {
    var plugin = this;
    if (!mail_from) mail_from = connection.transaction.mail_from;

    // bounces have a null sender.
    // null sender could also be tested with mail_from.user
    // Why would isNull() exist if it wasn't the right way to test this?

    if (mail_from.isNull()) {
        connection.results.add(plugin, {isa: 'yes'});
        return true;
    }

    connection.results.add(plugin, {isa: 'no'});
    return false;
};
<commit_msg>bounce: s/emit: 1/emit: true/ (3x)<commit_after>// bounce tests

exports.register = function () {
    var plugin = this;
    plugin.register_hook('mail',      'refresh_config');
    plugin.register_hook('mail',      'reject_all');
    plugin.register_hook('data',      'single_recipient');
    plugin.register_hook('data',      'bad_rcpt');
    plugin.register_hook('data_post', 'empty_return_path');
};

exports.refresh_config = function (next, connection) {
    var plugin = this;

    var check_defaults = {
        reject_all        : false,
        single_recipient  : true,
        empty_return_path : false,
        bad_rcpt          : true,
    };
    var reject_defaults = {
        single_recipient : true,
        empty_return_path: false,
    };

    var bools = [];
    for (var cd in check_defaults) {
        bools.push('checks.' + (check_defaults[cd] ? '+' : '-') + cd);
    }
    for (var rd in reject_defaults) {
        bools.push('reject.' + (reject_defaults[rd] ? '+' : '-') + rd);
    }

    plugin.cfg = plugin.config.get('bounce.ini', { booleans: bools });

    // Legacy config handling
    if (plugin.cfg.main.reject_invalid) {
        connection.logerror(plugin, "bounce.ini is out of date, please update!");
        plugin.cfg.checks.single_recipient=true;
        plugin.cfg.reject.single_recipient=true;
    }

    if (plugin.cfg.main.reject_all) {
        connection.logerror(plugin, "bounce.ini is out of date, please update!");
        plugin.cfg.checks.reject_all=true;
    }

    plugin.cfg.invalid_addrs = plugin.config.get('bounce_bad_rcpt', 'list');
    return next();
};

exports.reject_all = function (next, connection, params) {
    var plugin = this;
    if (!plugin.cfg.checks.reject_all) return next();
    var mail_from = params[0];

    if (!plugin.has_null_sender(connection, mail_from)) {
        return next(); // bounce messages are from null senders
    }

    connection.results.add(plugin, {fail: 'bounces_accepted', emit: true });
    return next(DENY, "No bounces accepted here");
};

exports.single_recipient = function(next, connection) {
    var plugin = this;
    if (!plugin.cfg.checks.single_recipient) return next();
    if (!plugin.has_null_sender(connection)) return next();

    // Valid bounces have a single recipient
    if (connection.transaction.rcpt_to.length === 1) {
        connection.results.add(plugin, {pass: 'single_recipient', emit: true });
        return next();
    }

    connection.loginfo(plugin, "bounce with too many recipients to: " +
        connection.transaction.rcpt_to.join(','));

    connection.results.add(plugin, {fail: 'single_recipient', emit: true });

    if (!plugin.cfg.reject.single_recipient) return next();

    return next(DENY, "this bounce message does not have 1 recipient");
};

exports.empty_return_path = function(next, connection) {
    var plugin = this;
    if (!plugin.cfg.checks.empty_return_path) return next();
    if (!plugin.has_null_sender(connection)) return next();

    // Bounce messages generally do not have a Return-Path set. This checks
    // for that. But whether it should is worth questioning...

    // On Jan 20, 2014, Matt Simerson examined the most recent 50,000 mail
    // connections for the presence of Return-Path in bounce messages. I
    // found 14 hits, 12 of which were from Google, in response to
    // undeliverable DMARC reports (IE, automated messages that Google
    // shouldn't have replied to). Another appears to be a valid bounce from
    // a poorly configured mailer, and the 14th was a confirmed spam kill.
    // Unless new data demonstrate otherwise, this should remain disabled.

    // Return-Path, aka Reverse-PATH, Envelope FROM, RFC5321.MailFrom
    // validate that the Return-Path header is empty, RFC 3834

    var rp = connection.transaction.header.get('Return-Path');
    if (!rp) {
        connection.results.add(plugin, {pass: 'empty_return_path' });
        return next();
    }

    if (rp === '<>') {
        connection.results.add(plugin, {pass: 'empty_return_path' });
        return next();
    }

    connection.results.add(plugin, {fail: 'empty_return_path', emit: true });
    return next(DENY, "bounce with non-empty Return-Path (RFC 3834)");
};

exports.bad_rcpt = function (next, connection) {
    var plugin = this;
    if (!plugin.cfg.checks.bad_rcpt) return next();
    if (!plugin.has_null_sender(connection)) return next();
    if (!plugin.cfg.invalid_addrs) return next();

    for (var i=0; i < connection.transaction.rcpt_to.length; i++) {
        var rcpt = connection.transaction.rcpt_to[i].address();
        if (plugin.cfg.invalid_addrs.indexOf(rcpt) === -1) continue;
        connection.results.add(plugin, {fail: 'bad_rcpt', emit: true });
        return next(DENY, "That recipient does not accept bounces");
    }

    connection.results.add(plugin, {pass: 'bad_rcpt'});
    return next();
};

exports.has_null_sender = function (connection, mail_from) {
    var plugin = this;
    if (!mail_from) mail_from = connection.transaction.mail_from;

    // bounces have a null sender.
    // null sender could also be tested with mail_from.user
    // Why would isNull() exist if it wasn't the right way to test this?

    if (mail_from.isNull()) {
        connection.results.add(plugin, {isa: 'yes'});
        return true;
    }

    connection.results.add(plugin, {isa: 'no'});
    return false;
};
<|endoftext|>


<commit_before>module.exports = function(grasshopper){
    "use strict";

    var node = {},
        async = require('async'),
        _ = require('underscore'),
        base = require('./api-base')(grasshopper);

    function userCanForNode(user, parentNodeId, minRole, callback){
        var isAllowed = _.find(user.permissions, function(permission){
            var userPrivLevel = base.AVAILABLE_PRIVILEGES[permission.role.toUpperCase()];
            return (permission.nodeid == parentNodeId &&  (userPrivLevel <= parseInt(minRole, 10)));
        });

        callback((!isAllowed) ? new Error("User does not have enough privileges.") : null);
    }

    function userPreventedForNode(user, parentNodeId, minRole, callback){
        var ruleIsSet = false,
            isAllowed = _.find(user.permissions, function(permission){
                var userPrivLevel = base.AVAILABLE_PRIVILEGES[permission.role.toUpperCase()];
                ruleIsSet = true;

                return (permission.nodeid == parentNodeId &&  (userPrivLevel <= parseInt(minRole, 10)));
            });

        callback((ruleIsSet && !isAllowed) ? new Error("User does not have enough privileges.") : null);
    }

    function resolveRequestParams(req){
        var input = req.params.id + (req.params[0] || '');

        if(input.indexOf('/') > -1){
            req.params.type = 'slug';
            req.params.id = '/' + input;
        }
        else if(!req.params.id.match(/^[0-9a-fA-F]{24}$/)){
            req.params.type = 'slug';
        }

        return req;
    }

    function userCan(token, minRole, parentNodeId, callback){

        async.waterfall([
            function(cb){
                grasshopper.users.getById(token.profile._id, function(err, obj){
                    var permissions = {
                        userPermission: false,
                        nodePermission: false,
                        nodeRestriction: true
                    };

                   cb(null, obj, permissions);
                });
            },
            function(user, permissions, cb){
                userCanForNode(user, parentNodeId, minRole, function(err){
                   permissions.nodePermission = (err == null);
                   cb(null, user, permissions);
                });
            },
            function(user, permissions, cb){
                userPreventedForNode(user, parentNodeId, minRole, function(err){
                    permissions.nodeRestriction = (err != null);
                    cb(null, user, permissions);
                });
            },
            function(user, permissions, cb){
                base.userCan(token, minRole, function(err){
                    permissions.userPermission = (err == null);
                    cb(null, user, permissions);
                });
            }
        ], function(err, user, permissions){
            var error = ((permissions.userPermission && !permissions.nodeRestriction) || permissions.nodePermission) ? null : new Error("User does not have  rights to perform this action.");
            callback(error);
        });
    }

    node.getBySlug = function(req, res){
        grasshopper.nodes.getBySlug(req.params.id, function(err, obj){
            if(err){
                if(err.message.indexOf('[404]') > -1){
                    base.write(base.STATUS_CODES.NOT_FOUND, JSON.stringify(err), res);
                }
                else {
                    base.write(base.STATUS_CODES.SERVER_ERROR, JSON.stringify(err), res);
                }
                return;
            }

            base.authenticateRequest(req, res, function(token){
                userCan(token, base.AVAILABLE_PRIVILEGES.READER, obj._id, function(err){
                    if(err){
                        base.write(base.STATUS_CODES.FORBIDDEN, base.getStatusMessage(base.STATUS_CODES.FORBIDDEN), res);
                    }
                    else {
                        base.write(base.STATUS_CODES.SUCCESS, JSON.stringify(obj), res);
                    }
                });
            });
        });
    };

     node.getById = function (req, res){
        req = resolveRequestParams(req);

        if(req.params.type && req.params.type == 'slug'){
            node.getBySlug(req, res);
            return;
        }

        base.authenticateRequest(req, res, function(token){
            userCan(token, base.AVAILABLE_PRIVILEGES.READER, req.params.node, function(err){

                if(err){
                    base.write(base.STATUS_CODES.FORBIDDEN, base.getStatusMessage(base.STATUS_CODES.FORBIDDEN), res);
                }
                else {
                    grasshopper.nodes.getById(req.params.id, function(err, obj){
                        if(err){
                            if(err.message.indexOf('[404]') > -1){
                                base.write(base.STATUS_CODES.NOT_FOUND, JSON.stringify(err), res);
                            }
                            else {
                                base.write(base.STATUS_CODES.SERVER_ERROR, JSON.stringify(err), res);
                            }
                        }
                        else {
                            base.write(base.STATUS_CODES.SUCCESS, JSON.stringify(obj), res);
                        }
                    });
                }
            });
        });
    };

     node.deleteById = function(req, res){
        base.authenticateRequest(req, res, function(token){

            base.userCan(token, base.AVAILABLE_PRIVILEGES.EDITOR, function(err){
                if(err){
                    base.write(base.STATUS_CODES.FORBIDDEN, base.getStatusMessage(base.STATUS_CODES.FORBIDDEN), res);
                }
                else {
                    grasshopper.nodes.deleteById(req.params.id, function(err){
                        if(err){
                            base.write(base.STATUS_CODES.SERVER_ERROR, JSON.stringify(err), res);
                        }
                        else {
                            base.write(base.STATUS_CODES.SUCCESS, JSON.stringify({}), res);
                        }
                    });
                }
            });
        });
    };

    node.getChildNodes = function(req, res){
        //[TODO]
    };

    node.getFiles = function(req, res){
        //[TODO]
    };

    node.getFilesDeepLoad = function(req, res){
        //[TODO]
    };

    node.getChildNodesDeepLoad = function(req, res){
        //[TODO]
    };

     node.describe = function (req, res){
        base.authenticateRequest(req, res, function(token){

            base.userCan(token, base.AVAILABLE_PRIVILEGES.READER, function(err){

                if(err){
                    base.write(base.STATUS_CODES.FORBIDDEN, base.getStatusMessage(base.STATUS_CODES.FORBIDDEN), res);
                }
                else {
                    grasshopper.nodes.describe(function(err, obj){
                        if(err){
                            base.write(base.STATUS_CODES.SERVER_ERROR, JSON.stringify(err), res);
                        }
                        else {
                            base.write(base.STATUS_CODES.SUCCESS, JSON.stringify(obj), res);
                        }
                    });
                }
            });
        });
    };

     node.update = function(req, res){
        base.authenticateRequest(req, res, function(token){

            userCan(token, base.AVAILABLE_PRIVILEGES.EDITOR, req.param.body._id, function(err) {

                if(err){
                    base.write(base.STATUS_CODES.FORBIDDEN, base.getStatusMessage(base.STATUS_CODES.FORBIDDEN), res);
                }
                else {

                    grasshopper.nodes.update(req.body, function(err, obj){
                        if(err){
                            base.write(base.STATUS_CODES.SERVER_ERROR, JSON.stringify({message: err.message}), res);
                        }
                        else {
                            base.write(base.STATUS_CODES.SUCCESS, JSON.stringify(obj), res);
                        }
                    });
                }
            });
        });
    };

    node.create = function(req, res){
        base.authenticateRequest(req, res, function(token){
            userCan(token, base.AVAILABLE_PRIVILEGES.EDITOR, req.body.parent, function(err) {
                if(err){
                    base.write(base.STATUS_CODES.FORBIDDEN, base.getStatusMessage(base.STATUS_CODES.FORBIDDEN), res);
                }
                else {
                    grasshopper.nodes.create(req.body, function(err, obj){
                        if(err){
                            base.write(base.STATUS_CODES.SERVER_ERROR, JSON.stringify({message: err.message}), res);
                        }
                        else {
                            base.write(base.STATUS_CODES.SUCCESS, JSON.stringify(obj), res);
                        }
                    });
                }
            });
        });
    };

    node.addContentTypes = function(req, res){
        base.authenticateRequest(req, res, function(token){
            userCan(token, base.AVAILABLE_PRIVILEGES.EDITOR, req.params.id, function(err) {
                if(err){
                    base.write(base.STATUS_CODES.FORBIDDEN, base.getStatusMessage(base.STATUS_CODES.FORBIDDEN), res);
                }
                else {
                    grasshopper.nodes.addContentTypes(req.params.id, req.body, function(err){
                        if(err){
                            base.write(base.STATUS_CODES.SERVER_ERROR, JSON.stringify({message: err.message}), res);
                        }
                        else {
                            base.write(base.STATUS_CODES.SUCCESS, "Success", res);
                        }
                    });
                }
            });
        });
    };

    node.deleteContentType = function(req, res){
        base.authenticateRequest(req, res, function(token){
            userCan(token, base.AVAILABLE_PRIVILEGES.EDITOR, req.params.id, function(err) {
                if(err){
                    base.write(base.STATUS_CODES.FORBIDDEN, base.getStatusMessage(base.STATUS_CODES.FORBIDDEN), res);
                }
                else {
                    grasshopper.nodes.deleteContentTypes(req.params.cid, function(err, obj){
                        if(err){
                            base.write(base.STATUS_CODES.SERVER_ERROR, JSON.stringify({message: err.message}), res);
                        }
                        else {
                            base.write(base.STATUS_CODES.SUCCESS, "Success", res);
                        }
                    });
                }
            });
        });
    };

    return node;
};<commit_msg>Removed console.log<commit_after>module.exports = function(grasshopper){
    "use strict";

    var node = {},
        async = require('async'),
        _ = require('underscore'),
        base = require('./api-base')(grasshopper);

    function userCanForNode(user, parentNodeId, minRole, callback){
        var isAllowed = _.find(user.permissions, function(permission){
            var userPrivLevel = base.AVAILABLE_PRIVILEGES[permission.role.toUpperCase()];
            return (permission.nodeid == parentNodeId &&  (userPrivLevel <= parseInt(minRole, 10)));
        });

        callback((!isAllowed) ? new Error("User does not have enough privileges.") : null);
    }

    function userPreventedForNode(user, parentNodeId, minRole, callback){
        var ruleIsSet = false,
            isAllowed = _.find(user.permissions, function(permission){
                var userPrivLevel = base.AVAILABLE_PRIVILEGES[permission.role.toUpperCase()];
                ruleIsSet = true;

                return (permission.nodeid == parentNodeId &&  (userPrivLevel <= parseInt(minRole, 10)));
            });

        callback((ruleIsSet && !isAllowed) ? new Error("User does not have enough privileges.") : null);
    }

    function resolveRequestParams(req){
        var input = req.params.id + (req.params[0] || '');

        if(input.indexOf('/') > -1){
            req.params.type = 'slug';
            req.params.id = '/' + input;
        }
        else if(!req.params.id.match(/^[0-9a-fA-F]{24}$/)){
            req.params.type = 'slug';
        }

        return req;
    }

    function userCan(token, minRole, parentNodeId, callback){

        async.waterfall([
            function(cb){
                grasshopper.users.getById(token.profile._id, function(err, obj){
                    var permissions = {
                        userPermission: false,
                        nodePermission: false,
                        nodeRestriction: true
                    };

                   cb(null, obj, permissions);
                });
            },
            function(user, permissions, cb){
                userCanForNode(user, parentNodeId, minRole, function(err){
                   permissions.nodePermission = (err == null);
                   cb(null, user, permissions);
                });
            },
            function(user, permissions, cb){
                userPreventedForNode(user, parentNodeId, minRole, function(err){
                    permissions.nodeRestriction = (err != null);
                    cb(null, user, permissions);
                });
            },
            function(user, permissions, cb){
                base.userCan(token, minRole, function(err){
                    permissions.userPermission = (err == null);
                    cb(null, user, permissions);
                });
            }
        ], function(err, user, permissions){
            var error = ((permissions.userPermission && !permissions.nodeRestriction) || permissions.nodePermission) ? null : new Error("User does not have  rights to perform this action.");
            callback(error);
        });
    }

    node.getBySlug = function(req, res){
        grasshopper.nodes.getBySlug(req.params.id, function(err, obj){
            if(err){
                if(err.message.indexOf('[404]') > -1){
                    base.write(base.STATUS_CODES.NOT_FOUND, JSON.stringify(err), res);
                }
                else {
                    base.write(base.STATUS_CODES.SERVER_ERROR, JSON.stringify(err), res);
                }
                return;
            }

            base.authenticateRequest(req, res, function(token){
                userCan(token, base.AVAILABLE_PRIVILEGES.READER, obj._id, function(err){
                    if(err){
                        base.write(base.STATUS_CODES.FORBIDDEN, base.getStatusMessage(base.STATUS_CODES.FORBIDDEN), res);
                    }
                    else {
                        base.write(base.STATUS_CODES.SUCCESS, JSON.stringify(obj), res);
                    }
                });
            });
        });
    };

     node.getById = function (req, res){
        req = resolveRequestParams(req);

        if(req.params.type && req.params.type == 'slug'){
            node.getBySlug(req, res);
            return;
        }

        base.authenticateRequest(req, res, function(token){
            userCan(token, base.AVAILABLE_PRIVILEGES.READER, req.params.node, function(err){

                if(err){
                    base.write(base.STATUS_CODES.FORBIDDEN, base.getStatusMessage(base.STATUS_CODES.FORBIDDEN), res);
                }
                else {
                    grasshopper.nodes.getById(req.params.id, function(err, obj){
                        if(err){
                            if(err.message.indexOf('[404]') > -1){
                                base.write(base.STATUS_CODES.NOT_FOUND, JSON.stringify(err), res);
                            }
                            else {
                                base.write(base.STATUS_CODES.SERVER_ERROR, JSON.stringify(err), res);
                            }
                        }
                        else {
                            base.write(base.STATUS_CODES.SUCCESS, JSON.stringify(obj), res);
                        }
                    });
                }
            });
        });
    };

     node.deleteById = function(req, res){
        base.authenticateRequest(req, res, function(token){

            base.userCan(token, base.AVAILABLE_PRIVILEGES.EDITOR, function(err){
                if(err){
                    base.write(base.STATUS_CODES.FORBIDDEN, base.getStatusMessage(base.STATUS_CODES.FORBIDDEN), res);
                }
                else {
                    grasshopper.nodes.deleteById(req.params.id, function(err){
                        if(err){
                            base.write(base.STATUS_CODES.SERVER_ERROR, JSON.stringify(err), res);
                        }
                        else {
                            base.write(base.STATUS_CODES.SUCCESS, JSON.stringify({}), res);
                        }
                    });
                }
            });
        });
    };

    node.getChildNodes = function(req, res){
        //[TODO]
    };

    node.getFiles = function(req, res){
        base.authenticateRequest(req, res, function(token){
            userCan(token, base.AVAILABLE_PRIVILEGES.READER, req.params.id, function(err){

                if(err){
                    base.write(base.STATUS_CODES.FORBIDDEN, base.getStatusMessage(base.STATUS_CODES.FORBIDDEN), res);
                }
                else {
                    grasshopper.nodes.getFiles(req.params.id, function(err, obj){
                        if(err){
                            if(err.message.indexOf('[404]') > -1){
                                base.write(base.STATUS_CODES.NOT_FOUND, JSON.stringify(err), res);
                            }
                            else {
                                base.write(base.STATUS_CODES.SERVER_ERROR, JSON.stringify(err), res);
                            }
                        }
                        else {
                            base.write(base.STATUS_CODES.SUCCESS, JSON.stringify(obj), res);
                        }
                    });
                }
            });
        });
    };

    node.getFilesDeepLoad = function(req, res){
        //[TODO]
    };

    node.getChildNodesDeepLoad = function(req, res){
        //[TODO]
    };

     node.describe = function (req, res){
        base.authenticateRequest(req, res, function(token){

            base.userCan(token, base.AVAILABLE_PRIVILEGES.READER, function(err){

                if(err){
                    base.write(base.STATUS_CODES.FORBIDDEN, base.getStatusMessage(base.STATUS_CODES.FORBIDDEN), res);
                }
                else {
                    grasshopper.nodes.describe(function(err, obj){
                        if(err){
                            base.write(base.STATUS_CODES.SERVER_ERROR, JSON.stringify(err), res);
                        }
                        else {
                            base.write(base.STATUS_CODES.SUCCESS, JSON.stringify(obj), res);
                        }
                    });
                }
            });
        });
    };

     node.update = function(req, res){
        base.authenticateRequest(req, res, function(token){

            userCan(token, base.AVAILABLE_PRIVILEGES.EDITOR, req.param.body._id, function(err) {

                if(err){
                    base.write(base.STATUS_CODES.FORBIDDEN, base.getStatusMessage(base.STATUS_CODES.FORBIDDEN), res);
                }
                else {

                    grasshopper.nodes.update(req.body, function(err, obj){
                        if(err){
                            base.write(base.STATUS_CODES.SERVER_ERROR, JSON.stringify({message: err.message}), res);
                        }
                        else {
                            base.write(base.STATUS_CODES.SUCCESS, JSON.stringify(obj), res);
                        }
                    });
                }
            });
        });
    };

    node.create = function(req, res){
        base.authenticateRequest(req, res, function(token){
            userCan(token, base.AVAILABLE_PRIVILEGES.EDITOR, req.body.parent, function(err) {
                if(err){
                    base.write(base.STATUS_CODES.FORBIDDEN, base.getStatusMessage(base.STATUS_CODES.FORBIDDEN), res);
                }
                else {
                    grasshopper.nodes.create(req.body, function(err, obj){
                        if(err){
                            base.write(base.STATUS_CODES.SERVER_ERROR, JSON.stringify({message: err.message}), res);
                        }
                        else {
                            base.write(base.STATUS_CODES.SUCCESS, JSON.stringify(obj), res);
                        }
                    });
                }
            });
        });
    };

    node.addContentTypes = function(req, res){
        base.authenticateRequest(req, res, function(token){
            userCan(token, base.AVAILABLE_PRIVILEGES.EDITOR, req.params.id, function(err) {
                if(err){
                    base.write(base.STATUS_CODES.FORBIDDEN, base.getStatusMessage(base.STATUS_CODES.FORBIDDEN), res);
                }
                else {
                    grasshopper.nodes.addContentTypes(req.params.id, req.body, function(err){
                        if(err){
                            base.write(base.STATUS_CODES.SERVER_ERROR, JSON.stringify({message: err.message}), res);
                        }
                        else {
                            base.write(base.STATUS_CODES.SUCCESS, "Success", res);
                        }
                    });
                }
            });
        });
    };

    node.deleteContentType = function(req, res){
        base.authenticateRequest(req, res, function(token){
            userCan(token, base.AVAILABLE_PRIVILEGES.EDITOR, req.params.id, function(err) {
                if(err){
                    base.write(base.STATUS_CODES.FORBIDDEN, base.getStatusMessage(base.STATUS_CODES.FORBIDDEN), res);
                }
                else {
                    grasshopper.nodes.deleteContentTypes(req.params.cid, function(err, obj){
                        if(err){
                            base.write(base.STATUS_CODES.SERVER_ERROR, JSON.stringify({message: err.message}), res);
                        }
                        else {
                            base.write(base.STATUS_CODES.SUCCESS, "Success", res);
                        }
                    });
                }
            });
        });
    };

    return node;
};<|endoftext|>


<commit_before>const c = require("irc-colors");
const R = require("ramda");
const {stripIndent} = require("common-tags");
const S3FileStateStore = require("./common/s3store");

const sanitize = (s) => c.stripColorsAndStyle(s).trim();
const normalize = (s) => sanitize(s).toLowerCase();
const quotesEqual = R.curry((q1, q2) => normalize(q1) == normalize(q2));
const quoteFuzzyMatch = R.curry((query, quote) => normalize(quote).includes(normalize(query)));

const findQuote = (quotes, query) => {
  if (query) quotes = R.filter(quoteFuzzyMatch(query), quotes);
  if (quotes.length == 0) return null;
  return quotes[Math.floor(Math.random() * quotes.length)];
};

const getQuotes = (state) =>
  R.pathOr([], ["quotes"], state);

const setQuotes = (quotes, state) =>
  R.assocPath(["quotes"], quotes, state);

const updateQuotes = (datastore, upd, cb) => {
  datastore.getState((err, state) => {
    if (err) return cb(err);
    const newQuotes = upd(getQuotes(state));
    datastore.saveState(setQuotes(newQuotes, state), (err) => {
      cb(err, err ? null : newQuotes);
    });
  });
};

const retrieveQuotes = (datastore, cb) => {
  datastore.getState((err, state) => {
    if (err) return cb(err);
    cb(null, getQuotes(state));
  });
};

module.exports = {
  name: "quotes",
  help: (config) => stripIndent`
    Save your shitposts for future generations.
    ${c.red(`${config.global.prefix}q [query]`)}: Posts a random saved quote, optionally within search results
    ${c.red(`${config.global.prefix}aq <text>`)}: Adds the given quote
    ${c.red(`${config.global.prefix}dq <text>`)}: Deletes the given quote
  `,

  init: (bot, config) => {
    const datastore = new S3FileStateStore(config.global.sharedS3Bucket, "quotes.json", config.global.aws);

    bot.msg(new RegExp(`^${config.global.prefix}q(?:\\s+(.+))?$`, "i"), (nick, channel, match) => {
      retrieveQuotes(datastore, (err, quotes) => {
        if (err) return console.error(err);
        const quote = findQuote(quotes, match[1]);
        bot.reply(nick, channel, quote ? c.teal(quote) : "No results");
      });
    });

    bot.msg(new RegExp(`^${config.global.prefix}aq\\s+(.+)$`, "i"), (nick, channel, match) => {
      updateQuotes(datastore, R.append(sanitize(match[1])), (err, newQuotes) => {
        bot.reply(nick, channel, err ? "(╯°□°)╯︵ ┻━┻" : `Quote added (${newQuotes.length} total)`);
      });
    });

    bot.msg(new RegExp(`^${config.global.prefix}dq\\s+(.+)$`, "i"), (nick, channel, match) => {
      updateQuotes(datastore, R.reject(quotesEqual(match[1])), (err, newQuotes) => {
        bot.reply(nick, channel, err ? "(╯°□°)╯︵ ┻━┻" : `Quote deleted (${newQuotes.length} total)`);
      });
    });
  }
};<commit_msg>Don't repost quotes<commit_after>const c = require("irc-colors");
const R = require("ramda");
const {stripIndent} = require("common-tags");
const S3FileStateStore = require("./common/s3store");

const sanitize = (s) => c.stripColorsAndStyle(s).trim();
const normalize = (s) => sanitize(s).toLowerCase();
const quotesEqual = R.curry((q1, q2) => normalize(q1) == normalize(q2));
const quoteFuzzyMatch = R.curry((query, quote) => normalize(quote).includes(normalize(query)));

const findQuote = (quotes, query, lastQuote) => {
  if (query) quotes = R.filter(quoteFuzzyMatch(query), quotes);
  if (!query && lastQuote) quotes = R.reject(quotesEqual(lastQuote), quotes);
  if (quotes.length == 0) return null;
  return quotes[Math.floor(Math.random() * quotes.length)];
};

const getQuotes = (state) =>
  R.pathOr([], ["quotes"], state);

const setQuotes = (quotes, state) =>
  R.assocPath(["quotes"], quotes, state);

const updateQuotes = (datastore, upd, cb) => {
  datastore.getState((err, state) => {
    if (err) return cb(err);
    const newQuotes = upd(getQuotes(state));
    datastore.saveState(setQuotes(newQuotes, state), (err) => {
      cb(err, err ? null : newQuotes);
    });
  });
};

const retrieveQuotes = (datastore, cb) => {
  datastore.getState((err, state) => {
    if (err) return cb(err);
    cb(null, getQuotes(state));
  });
};

module.exports = {
  name: "quotes",
  help: (config) => stripIndent`
    Save your shitposts for future generations.
    ${c.red(`${config.global.prefix}q [query]`)}: Posts a random saved quote, optionally within search results
    ${c.red(`${config.global.prefix}aq <text>`)}: Adds the given quote
    ${c.red(`${config.global.prefix}dq <text>`)}: Deletes the given quote
  `,

  init: (bot, config) => {
    const datastore = new S3FileStateStore(config.global.sharedS3Bucket, "quotes.json", config.global.aws);
    let lastQuote = null;

    bot.msg(new RegExp(`^${config.global.prefix}q(?:\\s+(.+))?$`, "i"), (nick, channel, match) => {
      retrieveQuotes(datastore, (err, quotes) => {
        if (err) return console.error(err);
        const quote = findQuote(quotes, match[1], lastQuote);
        bot.reply(nick, channel, quote ? c.teal(quote) : "No results");
        lastQuote = quote;
      });
    });

    bot.msg(new RegExp(`^${config.global.prefix}aq\\s+(.+)$`, "i"), (nick, channel, match) => {
      updateQuotes(datastore, R.append(sanitize(match[1])), (err, newQuotes) => {
        bot.reply(nick, channel, err ? "(╯°□°)╯︵ ┻━┻" : `Quote added (${newQuotes.length} total)`);
      });
    });

    bot.msg(new RegExp(`^${config.global.prefix}dq\\s+(.+)$`, "i"), (nick, channel, match) => {
      updateQuotes(datastore, R.reject(quotesEqual(match[1])), (err, newQuotes) => {
        bot.reply(nick, channel, err ? "(╯°□°)╯︵ ┻━┻" : `Quote deleted (${newQuotes.length} total)`);
      });
    });
  }
};<|endoftext|>


<commit_before>{ /* global rilti location */
  const { directive, emitter, run, isRenderable, isFunc } = rilti

  const route = emitter((route, view) => {
    if (view == null) return
    if (route[0] !== '#') route = '#' + route

    if (view.tagName === 'TEMPLATE') {
      view.remove()
      view = [...view.content.childNodes]
    }

    if (isRenderable(view)) {
      views[route] = isFunc(view) ? view() : view
    }
  })
  const views = route.views = Object.create(null)
  route.hash = () => location.hash.substr(1)

  directive('route-link', {
    init (el, val) {
      el.routeLink = route.on.change(() => {
        el.class['active-route'] = location.hash === (el.href = '#' + el.attr['route-link'])
      })
      el.class['active-route'] = location.hash === (el.href = '#' + val)
    },
    remove: (el, val) => el.routeLink.off()
  })

  directive('route', {
    init (el, val) {
      if (el.tagName === 'TEMPLATE') {
        route(val, el)
      } else {
        el.routeHandler = route.on.change((view, hash) => {
          if (hash === el.attr.route) el.html = view
          else el.textContent = ''
        })
      }
    },
    remove (el, val) {
      if (el.routeHandler) {
        el.routeHandler.off()
        el.textContent = ''
      }
    }
  })

  directive('route-active', {
    init (el, val) {
      el.routeHandler = route.on.change((view, hash) => {
        el.attr['route-active'] = hash
        el.html = view
      })
    },
    remove (el, val) {
      el.routeHandler.off()
      el.textContent = ''
    }
  })

  rilti.route = route
  route.handle = () => {
    const view = route.views[location.hash]
    if (view == null) return
    route.active = view
    const hash = route.hash()
    route.emit.change(view, hash, route)
    route.emit[hash](view, route)
  }

  window.onhashchange = route.handle
  run(() => route.handle())
}
<commit_msg> router improvements<commit_after>{ /* global rilti location */
  const { directive, emitter, run, isRenderable, isFunc } = rilti

  const route = emitter((route, view) => {
    if (view == null) return
    if (route[0] !== '#') route = '#' + route

    if (view.tagName === 'TEMPLATE') {
      view.remove()
      view = [...view.content.childNodes]
    }

    if (isRenderable(view)) {
      views[route] = isFunc(view) ? view() : view
    }
  })
  const views = route.views = Object.create(null)
  route.hash = (hash = location.hash) => hash.replace('#', '')

  directive('route-link', {
    init (el, val) {
      el.routeLink = route.on.change(() => {
        el.class['active-route'] = location.hash === (el.href = '#' + el.attr['route-link'])
      })
      el.class['active-route'] = location.hash === (el.href = '#' + val)
    },
    remove: (el, val) => el.routeLink.off()
  })

  directive('route', {
    init (el, val) {
      if (el.tagName === 'TEMPLATE') {
        route(val, el)
      } else {
        el.routeHandler = route.on.change((view, hash) => {
          if (hash === el.attr.route) el.html = view
          else el.textContent = ''
        })
      }
    },
    remove (el, val) {
      if (el.routeHandler) {
        el.routeHandler.off()
        el.textContent = ''
      }
    }
  })

  directive('route-active', {
    init (el, val) {
      el.routeHandler = route.on.change((view, hash) => {
        el.attr['route-active'] = hash
        el.html = view
      })
    },
    remove (el, val) {
      el.routeHandler.off()
      el.textContent = ''
    }
  })

  rilti.route = route
  route.handle = () => {
    const view = route.views[location.hash]
    if (view == null) return
    route.active = view
    const hash = route.hash()
    route.emit.change(view, hash, route)
    route.emit[hash](view, route)
  }

  route.whenActive = (hash, fn, once) => rilti.run(() => {
    if (hash[0] !== '#') hash = '#' + hash
    let view = route.views[hash]
    if (location.hash === hash && view != null) {
      fn(view, route, hash)
      if (once) return
    }
    hash = route.hash(hash)
    route[once ? 'once' : 'on'][hash](view => {
      fn(view, route, hash)
    })
  })

  window.onhashchange = route.handle
  run(() => {
    route.handle()
    window.dispatchEvent(new window.CustomEvent('routerReady'))
  })
}
<|endoftext|>


<commit_before>var hoverZoomPlugins = hoverZoomPlugins || [];
hoverZoomPlugins.push({
    name:'Weasyl',
    prepareImgLinks:function (callback) {
        var res = [],
            imgs = qsa('img[src*=".thumb."]');
        for (var i=0; i<imgs.length; i++) {
            var link = $(imgs[i].parentNode),
                url = imgs[i].src.replace(/\.thumb\..*/, '.cover.');
            link.data().hoverZoomSrc = [url + 'jpg', url + 'png'];
            link.data().hoverZoomCaption = link.find('.ol').text();
            res.push(link);
        }
        callback($(res));
    }
});
<commit_msg>Fix for plug-in: Weasyl<commit_after>var hoverZoomPlugins = hoverZoomPlugins || [];
hoverZoomPlugins.push({
    name:'Weasyl',
    version: '0.2',
    prepareImgLinks:function (callback) {
        var res = [],
            imgs = qsa('img[src*=".thumb."]');
        for (var i=0; i<imgs.length; i++) {
            var link = $(imgs[i].parentNode),
                url = imgs[i].src.replace(/\.thumb\..*/, '.cover.');
            link.data().hoverZoomSrc = [url + 'jpg', url + 'png'];
            link.data().hoverZoomCaption = link.find('.ol').text();
            res.push(link);
        }

        $('img[src]').each(function() {
            let link = $(this).parents('[href]').first();
            if (link[0]) {
                let fullsizeUrl = link[0].href + '.png';
                if (link.data().hoverZoomSrc == undefined) { link.data().hoverZoomSrc = [] }
                if (link.data().hoverZoomSrc.indexOf(fullsizeUrl) == -1) {
                    link.data().hoverZoomSrc.unshift(fullsizeUrl);
                    link.data().hoverZoomCaption = link.find('.ol').text();
                    res.push(link);
                }
            }
        });

        callback($(res));
    }
});
<|endoftext|>


<commit_before>
"use strict";

var APP = APP || {};

APP.setup = function(args) {
	if (args) {
		MEDIA.width = args.w;
		MEDIA.height = args.h;
	}
	MEDIA.Camera.init();

	APP.video = MEDIA.Camera.video;
	APP.canvas = new MEDIA.Canvas();
	APP.gui = new dat.GUI();

};

//loads an image through a cors proxy and sets crossOrigin = true
//otherwise canvas is tainted, see: https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image
APP.loadImage = function(canvas) {
	var url = window.prompt('Enter image url');
	var img = new Image();
	img.crossOrigin = true;
	img.onload = function() {
		canvas.context.drawImage(img, 0, 0, MEDIA.width, MEDIA.height);
	};
	img.src = 'http://www.corsproxy.com/' + url.substr(url.indexOf('//') + 2);
};

APP.guiToFront = function() {
	var gui = document.querySelector('.dg');
	document.body.removeChild(gui);
	document.body.appendChild(gui);
};

APP.setupControls = function() {
	var main = {
		name:'Main',
		controls:{
			ToggleVideo:function() {
				if (!APP.video.style.display || APP.video.style.display === 'none') APP.video.style.display = 'block';
				else APP.video.style.display = 'none';
			}
		}
	};
	APP.addControls(main, true, true);
	for (var i = 0; i < APP.effects.length; i++)
		APP.addControls(APP.effects[i]);

	APP.guiToFront();
};

APP.addControls = function(effect, open, skipBase) {
	var f = APP.gui.addFolder(effect.name);
	if (!skipBase) {
		var base = {
			Toggle:APP.Effect.toggle.bind(effect),
			Freeze:APP.Effect.toggleLoop,
			Save:APP.Effect.save
		};
		for (var key in base)
			f.add(base, key);
	}
	if (!effect.controls) return;
	for (var key in effect.controls) {
		var control = effect.controls[key];
		switch (typeof control) {
			case 'function':
			f.add(effect.controls, key);
			break;
			case 'object':
			var c = f.add(control, 'value', control.min, control.max).step(control.step);
			c.name(key);
			break;
		}
	}
	if (open) f.open();
};

APP.drawImage = function(canvas) {
	canvas.context.drawImage(MEDIA.Camera.video, 0, 0, MEDIA.width, MEDIA.height);
};

APP.Effect = {
	current:null,
	looping:false,
	loop:function() {
		if (!APP.Effect.looping || !APP.Effect.current) return;
		requestAnimationFrame(APP.Effect.loop);
		APP.Effect.current.draw();
	},
	stop:function() {
		APP.Effect.looping = false;
	},
	start:function() {
		APP.Effect.looping = true;
		APP.Effect.loop();
	},
	save:function() {
		var a = document.createElement('a');
		a.onclick = function(e) {
			a.href = APP.Effect.current.canvas.toDataURL();
			a.download = 'image-effect-sample.png';
		};
		a.click();
	},
	toggleLoop:function() {
		if (APP.Effect.looping) APP.Effect.stop();
		else APP.Effect.start();
	},
	toggle:function() {
		var e = APP.Effect;
		if (e.current === this) {
			e.current.canvas.el.style.display = 'none';
			e.current = null;
		} else {
			e.looping = true;
			e.current = this;
			e.current.canvas.el.style.display = 'block';
			e.loop();
		}
	}
};

APP.Utils = {
	clamp:function(value, min, max) {
		return (value < min) ? min : (value > max ? max : value);
	}
}<commit_msg>added lecture-workshop-1<commit_after>
"use strict";

var APP = APP || {};

APP.setup = function(args) {
	if (args) {
		MEDIA.width = args.w;
		MEDIA.height = args.h;
	}
	MEDIA.Camera.init();

	APP.video = MEDIA.Camera.video;
	APP.canvas = new MEDIA.Canvas();
	APP.gui = new dat.GUI();

};

//loads an image through a cors proxy and sets crossOrigin = true
//otherwise canvas is tainted, see: https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image
APP.loadImage = function(canvas) {
	var url = window.prompt('Enter image url');
	var img = new Image();
	img.crossOrigin = true;
	img.onload = function() {
		canvas.context.drawImage(img, 0, 0, MEDIA.width, MEDIA.height);
	};
	img.src = 'http://www.corsproxy.com/' + url.substr(url.indexOf('//') + 2);
};

APP.guiToFront = function() {
	var gui = document.querySelector('.dg');
	document.body.removeChild(gui);
	document.body.appendChild(gui);
};

APP.setupControls = function() {
	var main = {
		name:'Main',
		controls:{
			ToggleVideo:function() {
				if (!APP.video.style.display || APP.video.style.display === 'none') APP.video.style.display = 'block';
				else APP.video.style.display = 'none';
			}
		}
	};
	APP.addControls(main, true, true);
	if (APP.effects) {
		for (var i = 0; i < APP.effects.length; i++)
			APP.addControls(APP.effects[i]);
	}
	APP.guiToFront();
};

APP.addControls = function(effect, open, skipBase) {
	var f = APP.gui.addFolder(effect.name);
	if (!skipBase) {
		var base = {
			Toggle:APP.Effect.toggle.bind(effect),
			Freeze:APP.Effect.toggleLoop,
			Save:APP.Effect.save
		};
		for (var key in base)
			f.add(base, key);
	}
	if (!effect.controls) return;
	for (var key in effect.controls) {
		var control = effect.controls[key];
		switch (typeof control) {
			case 'function':
			f.add(effect.controls, key);
			break;
			case 'object':
			var c = f.add(control, 'value', control.min, control.max).step(control.step);
			c.name(key);
			break;
		}
	}
	if (open) f.open();
};

APP.drawImage = function(canvas) {
	canvas.context.drawImage(MEDIA.Camera.video, 0, 0, MEDIA.width, MEDIA.height);
};

APP.Effect = {
	current:null,
	looping:false,
	loop:function() {
		if (!APP.Effect.looping || !APP.Effect.current) return;
		requestAnimationFrame(APP.Effect.loop);
		APP.Effect.current.draw();
	},
	stop:function() {
		APP.Effect.looping = false;
	},
	start:function() {
		APP.Effect.looping = true;
		APP.Effect.loop();
	},
	save:function() {
		var a = document.createElement('a');
		a.onclick = function(e) {
			a.href = APP.Effect.current.canvas.toDataURL();
			a.download = 'image-effect-sample.png';
		};
		a.click();
	},
	toggleLoop:function() {
		if (APP.Effect.looping) APP.Effect.stop();
		else APP.Effect.start();
	},
	toggle:function() {
		var e = APP.Effect;
		if (e.current === this) {
			e.current.canvas.el.style.display = 'none';
			e.current = null;
		} else {
			e.looping = true;
			e.current = this;
			e.current.canvas.el.style.display = 'block';
			e.loop();
		}
	}
};

APP.Utils = {
	clamp:function(value, min, max) {
		return (value < min) ? min : (value > max ? max : value);
	}
}<|endoftext|>


<commit_before>var app = require('derby')
  .createApp(module)
  .use(require('../../ui'))
  .use(require('derby-ui-github-buttons'));

require('derby-lang').app(app);
require('derby-validate')(app);
require('derby-validate-user').app(app);
require('./controllerFns')(app);
require('./events')(app);
require('./user')(app);
require('./validation');
require('./viewFns')(app);
require('./routes')(app);<commit_msg>fix validation commit<commit_after>var app = require('derby')
  .createApp(module)
  .use(require('../../ui'))
  .use(require('derby-ui-github-buttons'));

require('derby-lang').app(app);
require('derby-validate')(app);
require('derby-validate-user').app(app);
require('./controllerFns')(app);
require('./events')(app);
require('./user')(app);
require('./validation')(app);
require('./viewFns')(app);
require('./routes')(app);<|endoftext|>


<commit_before>var async            = require("async");
var DomainRepository = require("./domain_repository");
var CommandBus       = require("./command_bus");
var Configuration    = require("./configuration");
var Entity           = require("./entity");
var Service          = require("./service");
var logger           = require("./logger");

function Assembler(App, configurationPath) {
  if (!App)
    throw new Error("Missing app constructor");
  if (!configurationPath)
    throw new Error("Missing configuration file path");

  this.App       = App;
  this.configurationPath = configurationPath;
  this.logger    = logger;
  this.reporters = [];
}

// Assemble an app, load the reporters.
Assembler.prototype.loadCompleteApp = function () {
  this.assembleApp();
  this.loadReporters();
};

// Assemble an app only, no reporters
Assembler.prototype.assembleApp = function () {
  if (this.app)
    throw new Error("An app was assembled already.");

  this._configure();

  var appOptions = {
    logger: this.logger,
    assembler: this
  };

  if (this.configuration.app) {
    for (var key in this.configuration.app) {
      appOptions[key] = this.configuration.app[key];
    }
  }

  if (this.configuration.domain) {
    this.assembleDomainInfrastructure();
    appOptions.domainRepository = this.domainRepository;
    appOptions.commandBus       = this.commandBus;
  }

  this.app = new this.App(appOptions);
};

Assembler.prototype.assembleDomainInfrastructure = function () {
  var EventStore       = this.configuration.domain.eventStore.ctor;
  var eventStore       = new EventStore({
    uri: this.configuration.domain.eventStore.uri,
    logger: this.logger
  });
  var EventBusEmitter  = this.configuration.domain.eventBusEmitter.ctor;
  var eventBusEmitter  = new EventBusEmitter(); // todo: pass config
  var domainRepository = new DomainRepository({
    store: eventStore,
    emitter: eventBusEmitter,
    logger: this.logger
  });
  var commandBus = new CommandBus({
    domainRepository: domainRepository,
    logger: this.logger
  });

  // These initializations take place on the constructor, not instances.
  // This is potentially buggy as a second in-process assembling could lead to
  // overwriting the previous command bus and domain repository instances.
  // TODO: Fix that.
  Entity.initialize({
    commandBus:       commandBus,
    domainRepository: domainRepository,
    logger:           this.logger
  });

  Service.initialize({
    commandBus:       commandBus,
    domainRepository: domainRepository,
    logger:           this.logger
  });

  this.eventBusEmitter  = eventBusEmitter;
  this.domainRepository = domainRepository;
  this.commandBus       = commandBus;
};

// Load the reporters
Assembler.prototype.loadReporters = function () {
  var self = this;

  if (!self.app)
    throw new Error("An app must be assembled first.");

  var reportersSettings = self.configuration.reporters;
  var logger            = self.logger;
  var reporters         = [];

  reportersSettings.forEach(function (reporterSettings) {
    var EventBusReceiver  = reporterSettings.eventBusReceiver.ctor;
    var eventBusReceiver = new EventBusReceiver({
      queueName: reporterSettings.eventBusReceiver.queue,
      logger: self.logger,
      configuration: reporterSettings.configuration
    });
    var reporterOptions = {
      eventBusReceiver: eventBusReceiver,
      logger: logger,
      configuration: reporterSettings.configuration
    };
    var reporter = new reporterSettings.ctor(reporterOptions);
    reporters.push(reporter);
  });
  this.reporters = reporters;
};

// Setup the domain event store (be careful, you'll lose all domain events!)
Assembler.prototype.setupEventStore = function (callback) {
  if (!this.app)
    throw new Error("An app must be assembled first.");

  this.app.domainRepository.store.setup(callback);
};

// Setup the reporters (will destroy all reports)
Assembler.prototype.setupReporters = function (callback) {
  if (!this.reporters)
    throw new Error("Reporters must be loaded first.");
  if (this.reporters.length == 0)
    callback();

  var queue = async.queue(function (reporter, callback) {
    reporter.setup(callback);
  }, Infinity);

  queue.drain = callback;

  this.reporters.forEach(function (reporter) {
    queue.push(reporter);
  });
};

Assembler.prototype._configure = function (defaultEnv) {
  this.configuration = new Configuration(this.configurationPath, process.env.NODE_ENV || defaultEnv || "development");
};

module.exports = Assembler;
<commit_msg>Separate reporters from reports in assembler<commit_after>var async            = require("async");
var DomainRepository = require("./domain_repository");
var CommandBus       = require("./command_bus");
var Configuration    = require("./configuration");
var Entity           = require("./entity");
var Service          = require("./service");
var logger           = require("./logger");

function Assembler(App, configurationPath) {
  if (!App)
    throw new Error("Missing app constructor");
  if (!configurationPath)
    throw new Error("Missing configuration file path");

  this.App               = App;
  this.configurationPath = configurationPath;
  this.logger            = logger;
  this.reporters         = [];
  this.reports           = [];
  this.reportStores      = [];
}

// Assemble an app, load the reporters.
Assembler.prototype.loadCompleteApp = function () {
  this.assembleApp();
  this.initializeReports();
  this.loadReporters();
};

// Assemble an app only, no reporters
Assembler.prototype.assembleApp = function () {
  if (this.app)
    throw new Error("An app was assembled already.");

  this._configure();

  var appOptions = {
    logger: this.logger,
    assembler: this
  };

  if (this.configuration.app) {
    for (var key in this.configuration.app) {
      appOptions[key] = this.configuration.app[key];
    }
  }

  if (this.configuration.domain) {
    this.assembleDomainInfrastructure();
    appOptions.domainRepository = this.domainRepository;
    appOptions.commandBus       = this.commandBus;
  }

  this.app = new this.App(appOptions);
};

Assembler.prototype.assembleDomainInfrastructure = function () {
  var EventStore       = this.configuration.domain.eventStore.ctor;
  var eventStore       = new EventStore({
    uri: this.configuration.domain.eventStore.uri,
    logger: this.logger
  });
  var EventBusEmitter  = this.configuration.domain.eventBusEmitter.ctor;
  var eventBusEmitter  = new EventBusEmitter(); // todo: pass config
  var domainRepository = new DomainRepository({
    store: eventStore,
    emitter: eventBusEmitter,
    logger: this.logger
  });
  var commandBus = new CommandBus({
    domainRepository: domainRepository,
    logger: this.logger
  });

  // These initializations take place on the constructor, not instances.
  // This is potentially buggy as a second in-process assembling could lead to
  // overwriting the previous command bus and domain repository instances.
  // TODO: Fix that.
  Entity.initialize({
    commandBus:       commandBus,
    domainRepository: domainRepository,
    logger:           this.logger
  });

  Service.initialize({
    commandBus:       commandBus,
    domainRepository: domainRepository,
    logger:           this.logger
  });

  this.eventBusEmitter  = eventBusEmitter;
  this.domainRepository = domainRepository;
  this.commandBus       = commandBus;
};

// Load the reporters
Assembler.prototype.loadReporters = function () {
  var self = this;

  if (!self.app)
    throw new Error("An app must be assembled first.");

  var reportersSettings = self.configuration.reporters;
  var logger            = self.logger;

  reportersSettings.forEach(function (reporterSettings) {
    var EventBusReceiver  = reporterSettings.eventBusReceiver.ctor;
    var eventBusReceiver = new EventBusReceiver({
      queueName: reporterSettings.eventBusReceiver.queue,
      logger: self.logger
    });
    var reporterOptions = {
      eventBusReceiver: eventBusReceiver,
      logger: logger
    };
    var reporter = new reporterSettings.ctor(reporterOptions);
    self.reporters.push(reporter);
  });
};

// Initialize the reports
Assembler.prototype.initializeReports = function () {
  var self = this;

  if (!self.app)
    throw new Error("An app must be assembled first.");

  var reportsSettings = self.configuration.reports;

  reportsSettings.forEach(function (reportSettings) {
    var Report      = reportSettings.ctor;
    var ReportStore = reportSettings.store.ctor;
    var reportStore = new ReportStore({
      uri: reportSettings.store.uri,
      logger: self.logger
    });
    Report.initialize({store: reportStore, logger: self.logger});
    self.reports.push(Report);
    self.reportStores.push(reportStore);
  });
};

Assembler.prototype.setupEventStore = function (callback) {
  if (!this.app)
    throw new Error("An app must be assembled first.");

  this.app.domainRepository.store.setup(callback);
};

// Setup the reports (will destroy all reports)
Assembler.prototype.setupReportStores = function (callback) {
  if (!this.reportStores)
    throw new Error("Reports must be loaded first.");
  if (this.reportStores.length == 0)
    callback();

  var queue = async.queue(function (reportStore, callback) {
    reportStore.setup(callback);
  }, Infinity);

  queue.drain = callback;

  this.reportStores.forEach(function (reportStore) {
    queue.push(reportStore);
  });
};

Assembler.prototype._configure = function (defaultEnv) {
  this.configuration = new Configuration(this.configurationPath, process.env.NODE_ENV || defaultEnv || "development");
};

module.exports = Assembler;
<|endoftext|>