<commit_before>'use strict'

const morgan = require('morgan')
const helmet = require('helmet')
const bodyParser = require('body-parser')
const _ = require('lodash/fp')
const express = require('express')
const nmd = require('nano-markdown')

const dbErrorCodes = require('./db-error-codes')
const options = require('./options')
const logger = require('./logger')
const configManager = require('./config-manager')
const pairing = require('./pairing')
const settingsLoader = require('./settings-loader')
const plugins = require('./plugins')
const helpers = require('./route-helpers')
const poller = require('./poller')
const Tx = require('./tx')
const E = require('./error')
const customers = require('./customers')
const logs = require('./logs')
const compliance = require('./compliance')

const argv = require('minimist')(process.argv.slice(2))

const CLOCK_SKEW = 60 * 1000
const REQUEST_TTL = 3 * 60 * 1000
const THROTTLE_LOGS_QUERY = 30 * 1000
const THROTTLE_CLOCK_SKEW = 60 * 1000

const pids = {}
const reboots = {}
const restartServicesMap = {}
const canGetLastSeenMap = {}
const canLogClockSkewMap = {}

const devMode = argv.dev || options.http

function checkHasLightning (settings) {
  return configManager.cryptoScoped('BTC', settings.config).layer2 !== 'no-layer2'
}

function poll (req, res, next) {
  const deviceId = req.deviceId
  const deviceTime = req.deviceTime
  const serialNumber = req.query.sn
  const pid = req.query.pid
  const settings = req.settings
  const config = configManager.machineScoped(deviceId, settings.config)
  const pi = plugins(settings, deviceId)
  const hasLightning = checkHasLightning(settings)

  pids[deviceId] = { pid, ts: Date.now() }

  return pi.pollQueries(serialNumber, deviceTime, req.query)
    .then(results => {
      const cassettes = results.cassettes

      const reboot = pid && reboots[deviceId] && reboots[deviceId] === pid
      const restartServices = pid && restartServicesMap[deviceId] && restartServicesMap[deviceId] === pid
      const langs = config.machineLanguages

      const locale = {
        fiatCode: config.fiatCurrency,
        localeInfo: {
          primaryLocale: langs[0],
          primaryLocales: langs,
          country: config.country
        }
      }

      const terms = config.termsScreenActive && config.termsScreenText ? createTerms(config) : null

      const response = {
        error: null,
        locale,
        txLimit: config.cashInTransactionLimit,
        idVerificationEnabled: config.idVerificationEnabled,
        smsVerificationActive: config.smsVerificationActive,
        smsVerificationThreshold: config.smsVerificationThreshold,
        hardLimitVerificationActive: config.hardLimitVerificationActive,
        hardLimitVerificationThreshold: config.hardLimitVerificationThreshold,
        idCardDataVerificationActive: config.idCardDataVerificationActive,
        idCardDataVerificationThreshold: config.idCardDataVerificationThreshold,
        idCardPhotoVerificationActive: config.idCardPhotoVerificationActive,
        idCardPhotoVerificationThreshold: config.idCardPhotoVerificationThreshold,
        sanctionsVerificationActive: config.sanctionsVerificationActive,
        sanctionsVerificationThreshold: config.sanctionsVerificationThreshold,
        crossRefVerificationActive: config.crossRefVerificationActive,
        crossRefVerificationThreshold: config.crossRefVerificationThreshold,
        frontCameraVerificationActive: config.frontCameraVerificationActive,
        frontCameraVerificationThreshold: config.frontCameraVerificationThreshold,
        receiptPrintingActive: config.receiptPrintingActive,
        terms,
        cassettes,
        twoWayMode: config.cashOutEnabled,
        zeroConfLimit: config.zeroConfLimit,
        reboot,
        restartServices,
        hasLightning,
        operatorInfo: {
          active: config.operatorInfoActive,
          name: config.operatorInfoName,
          phone: config.operatorInfoPhone,
          email: config.operatorInfoEmail,
          website: config.operatorInfoWebsite,
          companyNumber: config.operatorInfoCompanyNumber
        }
      }

      if (response.idVerificationEnabled) {
        response.idVerificationLimit = config.idVerificationLimit
      }

      return res.json(_.assign(response, results))
    })
    .catch(next)
}

function getTx (req, res, next) {
  if (req.query.status) {
    return helpers.fetchStatusTx(req.params.id, req.query.status)
      .then(r => res.json(r))
      .catch(next)
  }

  return next(httpError('Not Found', 404))
}

function getPhoneTx (req, res, next) {
  if (req.query.phone) {
    return helpers.fetchPhoneTx(req.query.phone)
      .then(r => res.json(r))
      .catch(next)
  }

  return next(httpError('Not Found', 404))
}

function postTx (req, res, next) {
  const pi = plugins(req.settings, req.deviceId)

  return Tx.post(_.set('deviceId', req.deviceId, req.body), pi)
    .then(tx => {
      if (tx.errorCode) {
        logger.error(tx.error)
        throw httpError(tx.error, 500)
      }

      return res.json(tx)
    })
    .catch(err => {
      // 204 so that l-m can ignore the error
      // this is fine because the request is polled and will be retried if needed.
      if (err.code === dbErrorCodes.SERIALIZATION_FAILURE) {
        logger.warn('Harmless DB conflict, the query will be retried.')
        return res.status(204).json({})
      }
      if (err instanceof E.StaleTxError) return res.status(409).json({ errorType: 'stale' })
      if (err instanceof E.RatchetError) return res.status(409).json({ errorType: 'ratchet' })

      throw err
    })
    .catch(next)
}

function stateChange (req, res, next) {
  helpers.stateChange(req.deviceId, req.deviceTime, req.body)
    .then(() => respond(req, res))
    .catch(next)
}

function verifyUser (req, res, next) {
  const pi = plugins(req.settings, req.deviceId)
  pi.verifyUser(req.body)
    .then(idResult => respond(req, res, idResult))
    .catch(next)
}

function verifyTx (req, res, next) {
  const pi = plugins(req.settings, req.deviceId)
  pi.verifyTransaction(req.body)
    .then(idResult => respond(req, res, idResult))
    .catch(next)
}

function addOrUpdateCustomer (req) {
  const customerData = req.body
  const config = configManager.unscoped(req.settings.config)

  return customers.get(customerData.phone)
    .then(customer => {
      if (customer) return customer

      return customers.add(req.body)
    })
    .then(customer => {
      return compliance.validationPatch(req.deviceId, config, customer)
        .then(patch => {
          if (_.isEmpty(patch)) return customer
          return customers.update(customer.id, patch)
        })
    })
}

function getCustomerWithPhoneCode (req, res, next) {
  const pi = plugins(req.settings, req.deviceId)
  const phone = req.body.phone

  return pi.getPhoneCode(phone)
    .then(code => {
      return addOrUpdateCustomer(req)
        .then(customer => respond(req, res, { code, customer }))
    })
    .catch(err => {
      if (err.name === 'BadNumberError') throw httpError('Bad number', 401)
      throw err
    })
    .catch(next)
}

function updateCustomer (req, res, next) {
  const id = req.params.id
  const txId = req.query.txId
  const patch = req.body
  const config = configManager.unscoped(req.settings.config)

  customers.getById(id)
    .then(customer => {
      if (!customer) { throw httpError('Not Found', 404) }

      const mergedCustomer = _.merge(customer, patch)
      return compliance.validationPatch(req.deviceId, config, mergedCustomer)
        .then(_.merge(patch))
        .then(newPatch => customers.updatePhotoCard(id, newPatch))
        .then(newPatch => customers.updateFrontCamera(id, newPatch))
        .then(newPatch => customers.update(id, newPatch, null, txId))
    })
    .then(customer => respond(req, res, { customer }))
    .catch(next)
}

function getLastSeen (req, res, next) {
  const deviceId = req.deviceId
  const timestamp = Date.now()
  const shouldTrigger = !canGetLastSeenMap[deviceId] ||
    timestamp - canGetLastSeenMap[deviceId] >= THROTTLE_LOGS_QUERY

  if (shouldTrigger) {
    canGetLastSeenMap[deviceId] = timestamp
    return logs.getLastSeen(deviceId)
      .then(r => res.json(r))
      .catch(next)
  }

  return res.status(408).json({})
}

function updateLogs (req, res, next) {
  return logs.update(req.deviceId, req.body.logs)
    .then(status => res.json({ success: status }))
    .catch(next)
}

function ca (req, res) {
  const token = req.query.token

  return pairing.authorizeCaDownload(token)
    .then(ca => res.json({ ca }))
    .catch(() => res.status(403).json({ error: 'forbidden' }))
}

function pair (req, res, next) {
  const token = req.query.token
  const deviceId = req.deviceId
  const model = req.query.model

  return pairing.pair(token, deviceId, model)
    .then(valid => {
      if (valid) {
        return helpers.updateMachineDefaults(deviceId)
          .then(() => res.json({ status: 'paired' }))
      }

      throw httpError('Pairing failed')
    })
    .catch(next)
}

function errorHandler (err, req, res, next) {
  const statusCode = err.name === 'HTTPError'
    ? err.code || 500
    : 500

  const json = { error: err.message }

  if (statusCode >= 400) logger.error(err)

  return res.status(statusCode).json(json)
}

function respond (req, res, _body, _status) {
  const status = _status || 200
  const body = _body || {}

  return res.status(status).json(body)
}

function httpError (msg, code) {
  const err = new Error(msg)
  err.name = 'HTTPError'
  err.code = code || 500

  return err
}

function filterOldRequests (req, res, next) {
  const deviceTime = req.deviceTime
  const deviceId = req.deviceId
  const timestamp = Date.now()
  const delta = timestamp - Date.parse(deviceTime)

  const shouldTrigger = !canLogClockSkewMap[deviceId] ||
    timestamp - canLogClockSkewMap[deviceId] >= THROTTLE_CLOCK_SKEW

  if (delta > CLOCK_SKEW && shouldTrigger) {
    canLogClockSkewMap[deviceId] = timestamp
    logger.error('Clock skew with lamassu-machine[%s] too high [%ss], adjust lamassu-machine clock',
      req.deviceName, (delta / 1000).toFixed(2))
  }

  if (delta > REQUEST_TTL) return res.status(408).json({ error: 'stale' })
  next()
}

function authorize (req, res, next) {
  const deviceId = req.deviceId

  return pairing.isPaired(deviceId)
    .then(deviceName => {
      if (deviceName) {
        req.deviceId = deviceId
        req.deviceName = deviceName
        return next()
      }

      return res.status(403).json({ error: 'Forbidden' })
    })
    .catch(next)
}

const skip = (req, res) => _.includes(req.path, ['/poll', '/state', '/logs']) && _.includes(res.statusCode, [200, 408])

const configRequiredRoutes = [
  '/poll',
  '/event',
  '/phone_code',
  '/customer',
  '/tx'
]

const app = express()
const localApp = express()

app.use(helmet({ noCache: true }))
app.use(bodyParser.json({ limit: '2mb' }))
app.use(morgan('dev', { skip, stream: logger.stream }))

// These two have their own authorization
app.post('/pair', populateDeviceId, pair)
app.get('/ca', ca)

app.use(populateDeviceId)
if (!devMode) app.use(authorize)
app.use(configRequiredRoutes, populateSettings)
app.use(filterOldRequests)

app.get('/poll', poll)
app.post('/state', stateChange)

app.post('/verify_user', verifyUser)
app.post('/verify_transaction', verifyTx)

app.post('/phone_code', getCustomerWithPhoneCode)
app.patch('/customer/:id', updateCustomer)

app.post('/tx', postTx)
app.get('/tx/:id', getTx)
app.get('/tx', getPhoneTx)
app.get('/logs', getLastSeen)
app.post('/logs', updateLogs)

app.use(errorHandler)
app.use((req, res) => {
  res.status(404).json({ error: 'No such route' })
})

localApp.get('/pid', (req, res) => {
  const deviceId = req.query.device_id
  const pidRec = pids[deviceId]
  res.json(pidRec)
})

localApp.post('/reboot', (req, res) => {
  const deviceId = req.query.device_id
  const pid = pids[deviceId] && pids[deviceId].pid

  if (!deviceId || !pid) {
    return res.sendStatus(400)
  }

  reboots[deviceId] = pid
  res.sendStatus(200)
})

localApp.post('/restartServices', (req, res) => {
  const deviceId = req.query.device_id
  const pid = pids[deviceId] && pids[deviceId].pid

  if (!deviceId || !pid) {
    return res.sendStatus(400)
  }

  restartServicesMap[deviceId] = pid
  res.sendStatus(200)
})

localApp.post('/dbChange', (req, res, next) => {
  return settingsLoader.loadLatest()
    .then(poller.reload)
    .then(() => logger.info('Config reloaded'))
    .catch(err => {
      logger.error(err)
      res.sendStatus(500)
    })
})

function sha256 (buf) {
  const crypto = require('crypto')
  const hash = crypto.createHash('sha256')

  hash.update(buf)
  return hash.digest('hex').toString('hex')
}

function populateDeviceId (req, res, next) {
  const deviceId = _.isFunction(req.connection.getPeerCertificate)
    ? sha256(req.connection.getPeerCertificate().raw)
    : null

  req.deviceId = deviceId
  req.deviceTime = req.get('date')

  next()
}

let oldVersionId = 'initial'

function populateSettings (req, res, next) {
  const versionId = req.headers['config-version']
  if (versionId !== oldVersionId) {
    oldVersionId = versionId
  }

  if (!versionId) {
    return settingsLoader.loadLatest()
      .then(settings => { req.settings = settings })
      .then(() => next())
      .catch(next)
  }

  settingsLoader.load(versionId)
    .then(settings => { req.settings = settings })
    .then(() => helpers.updateDeviceConfigVersion(versionId))
    .then(() => next())
    .catch(next)
}

function createTerms (config) {
  return {
    active: config.termsScreenActive,
    title: config.termsScreenTitle,
    text: nmd(config.termsScreenText),
    accept: config.termsAcceptButtonText,
    cancel: config.termsCancelButtonText
  }
}

module.exports = { app, localApp }
<commit_msg>perf - Cache settings on router.js<commit_after>'use strict'

const morgan = require('morgan')
const helmet = require('helmet')
const bodyParser = require('body-parser')
const _ = require('lodash/fp')
const express = require('express')
const nmd = require('nano-markdown')

const dbErrorCodes = require('./db-error-codes')
const options = require('./options')
const logger = require('./logger')
const configManager = require('./config-manager')
const pairing = require('./pairing')
const settingsLoader = require('./settings-loader')
const plugins = require('./plugins')
const helpers = require('./route-helpers')
const poller = require('./poller')
const Tx = require('./tx')
const E = require('./error')
const customers = require('./customers')
const logs = require('./logs')
const compliance = require('./compliance')

const argv = require('minimist')(process.argv.slice(2))

const CLOCK_SKEW = 60 * 1000
const REQUEST_TTL = 3 * 60 * 1000
const THROTTLE_LOGS_QUERY = 30 * 1000
const THROTTLE_CLOCK_SKEW = 60 * 1000
const SETTINGS_CACHE_REFRESH = 60 * 60 * 1000

const pids = {}
const reboots = {}
const restartServicesMap = {}
const canGetLastSeenMap = {}
const canLogClockSkewMap = {}
const settingsCache = {}

const devMode = argv.dev || options.http

function checkHasLightning (settings) {
  return configManager.cryptoScoped('BTC', settings.config).layer2 !== 'no-layer2'
}

function poll (req, res, next) {
  const deviceId = req.deviceId
  const deviceTime = req.deviceTime
  const serialNumber = req.query.sn
  const pid = req.query.pid
  const settings = req.settings
  const config = configManager.machineScoped(deviceId, settings.config)
  const pi = plugins(settings, deviceId)
  const hasLightning = checkHasLightning(settings)

  pids[deviceId] = { pid, ts: Date.now() }

  return pi.pollQueries(serialNumber, deviceTime, req.query)
    .then(results => {
      const cassettes = results.cassettes

      const reboot = pid && reboots[deviceId] && reboots[deviceId] === pid
      const restartServices = pid && restartServicesMap[deviceId] && restartServicesMap[deviceId] === pid
      const langs = config.machineLanguages

      const locale = {
        fiatCode: config.fiatCurrency,
        localeInfo: {
          primaryLocale: langs[0],
          primaryLocales: langs,
          country: config.country
        }
      }

      const terms = config.termsScreenActive && config.termsScreenText ? createTerms(config) : null

      const response = {
        error: null,
        locale,
        txLimit: config.cashInTransactionLimit,
        idVerificationEnabled: config.idVerificationEnabled,
        smsVerificationActive: config.smsVerificationActive,
        smsVerificationThreshold: config.smsVerificationThreshold,
        hardLimitVerificationActive: config.hardLimitVerificationActive,
        hardLimitVerificationThreshold: config.hardLimitVerificationThreshold,
        idCardDataVerificationActive: config.idCardDataVerificationActive,
        idCardDataVerificationThreshold: config.idCardDataVerificationThreshold,
        idCardPhotoVerificationActive: config.idCardPhotoVerificationActive,
        idCardPhotoVerificationThreshold: config.idCardPhotoVerificationThreshold,
        sanctionsVerificationActive: config.sanctionsVerificationActive,
        sanctionsVerificationThreshold: config.sanctionsVerificationThreshold,
        crossRefVerificationActive: config.crossRefVerificationActive,
        crossRefVerificationThreshold: config.crossRefVerificationThreshold,
        frontCameraVerificationActive: config.frontCameraVerificationActive,
        frontCameraVerificationThreshold: config.frontCameraVerificationThreshold,
        receiptPrintingActive: config.receiptPrintingActive,
        terms,
        cassettes,
        twoWayMode: config.cashOutEnabled,
        zeroConfLimit: config.zeroConfLimit,
        reboot,
        restartServices,
        hasLightning,
        operatorInfo: {
          active: config.operatorInfoActive,
          name: config.operatorInfoName,
          phone: config.operatorInfoPhone,
          email: config.operatorInfoEmail,
          website: config.operatorInfoWebsite,
          companyNumber: config.operatorInfoCompanyNumber
        }
      }

      if (response.idVerificationEnabled) {
        response.idVerificationLimit = config.idVerificationLimit
      }

      return res.json(_.assign(response, results))
    })
    .catch(next)
}

function getTx (req, res, next) {
  if (req.query.status) {
    return helpers.fetchStatusTx(req.params.id, req.query.status)
      .then(r => res.json(r))
      .catch(next)
  }

  return next(httpError('Not Found', 404))
}

function getPhoneTx (req, res, next) {
  if (req.query.phone) {
    return helpers.fetchPhoneTx(req.query.phone)
      .then(r => res.json(r))
      .catch(next)
  }

  return next(httpError('Not Found', 404))
}

function postTx (req, res, next) {
  const pi = plugins(req.settings, req.deviceId)

  return Tx.post(_.set('deviceId', req.deviceId, req.body), pi)
    .then(tx => {
      if (tx.errorCode) {
        logger.error(tx.error)
        throw httpError(tx.error, 500)
      }

      return res.json(tx)
    })
    .catch(err => {
      // 204 so that l-m can ignore the error
      // this is fine because the request is polled and will be retried if needed.
      if (err.code === dbErrorCodes.SERIALIZATION_FAILURE) {
        logger.warn('Harmless DB conflict, the query will be retried.')
        return res.status(204).json({})
      }
      if (err instanceof E.StaleTxError) return res.status(409).json({ errorType: 'stale' })
      if (err instanceof E.RatchetError) return res.status(409).json({ errorType: 'ratchet' })

      throw err
    })
    .catch(next)
}

function stateChange (req, res, next) {
  helpers.stateChange(req.deviceId, req.deviceTime, req.body)
    .then(() => respond(req, res))
    .catch(next)
}

function verifyUser (req, res, next) {
  const pi = plugins(req.settings, req.deviceId)
  pi.verifyUser(req.body)
    .then(idResult => respond(req, res, idResult))
    .catch(next)
}

function verifyTx (req, res, next) {
  const pi = plugins(req.settings, req.deviceId)
  pi.verifyTransaction(req.body)
    .then(idResult => respond(req, res, idResult))
    .catch(next)
}

function addOrUpdateCustomer (req) {
  const customerData = req.body
  const config = configManager.unscoped(req.settings.config)

  return customers.get(customerData.phone)
    .then(customer => {
      if (customer) return customer

      return customers.add(req.body)
    })
    .then(customer => {
      return compliance.validationPatch(req.deviceId, config, customer)
        .then(patch => {
          if (_.isEmpty(patch)) return customer
          return customers.update(customer.id, patch)
        })
    })
}

function getCustomerWithPhoneCode (req, res, next) {
  const pi = plugins(req.settings, req.deviceId)
  const phone = req.body.phone

  return pi.getPhoneCode(phone)
    .then(code => {
      return addOrUpdateCustomer(req)
        .then(customer => respond(req, res, { code, customer }))
    })
    .catch(err => {
      if (err.name === 'BadNumberError') throw httpError('Bad number', 401)
      throw err
    })
    .catch(next)
}

function updateCustomer (req, res, next) {
  const id = req.params.id
  const txId = req.query.txId
  const patch = req.body
  const config = configManager.unscoped(req.settings.config)

  customers.getById(id)
    .then(customer => {
      if (!customer) { throw httpError('Not Found', 404) }

      const mergedCustomer = _.merge(customer, patch)
      return compliance.validationPatch(req.deviceId, config, mergedCustomer)
        .then(_.merge(patch))
        .then(newPatch => customers.updatePhotoCard(id, newPatch))
        .then(newPatch => customers.updateFrontCamera(id, newPatch))
        .then(newPatch => customers.update(id, newPatch, null, txId))
    })
    .then(customer => respond(req, res, { customer }))
    .catch(next)
}

function getLastSeen (req, res, next) {
  const deviceId = req.deviceId
  const timestamp = Date.now()
  const shouldTrigger = !canGetLastSeenMap[deviceId] ||
    timestamp - canGetLastSeenMap[deviceId] >= THROTTLE_LOGS_QUERY

  if (shouldTrigger) {
    canGetLastSeenMap[deviceId] = timestamp
    return logs.getLastSeen(deviceId)
      .then(r => res.json(r))
      .catch(next)
  }

  return res.status(408).json({})
}

function updateLogs (req, res, next) {
  return logs.update(req.deviceId, req.body.logs)
    .then(status => res.json({ success: status }))
    .catch(next)
}

function ca (req, res) {
  const token = req.query.token

  return pairing.authorizeCaDownload(token)
    .then(ca => res.json({ ca }))
    .catch(() => res.status(403).json({ error: 'forbidden' }))
}

function pair (req, res, next) {
  const token = req.query.token
  const deviceId = req.deviceId
  const model = req.query.model

  return pairing.pair(token, deviceId, model)
    .then(valid => {
      if (valid) {
        return helpers.updateMachineDefaults(deviceId)
          .then(() => res.json({ status: 'paired' }))
      }

      throw httpError('Pairing failed')
    })
    .catch(next)
}

function errorHandler (err, req, res, next) {
  const statusCode = err.name === 'HTTPError'
    ? err.code || 500
    : 500

  const json = { error: err.message }

  if (statusCode >= 400) logger.error(err)

  return res.status(statusCode).json(json)
}

function respond (req, res, _body, _status) {
  const status = _status || 200
  const body = _body || {}

  return res.status(status).json(body)
}

function httpError (msg, code) {
  const err = new Error(msg)
  err.name = 'HTTPError'
  err.code = code || 500

  return err
}

function filterOldRequests (req, res, next) {
  const deviceTime = req.deviceTime
  const deviceId = req.deviceId
  const timestamp = Date.now()
  const delta = timestamp - Date.parse(deviceTime)

  const shouldTrigger = !canLogClockSkewMap[deviceId] ||
    timestamp - canLogClockSkewMap[deviceId] >= THROTTLE_CLOCK_SKEW

  if (delta > CLOCK_SKEW && shouldTrigger) {
    canLogClockSkewMap[deviceId] = timestamp
    logger.error('Clock skew with lamassu-machine[%s] too high [%ss], adjust lamassu-machine clock',
      req.deviceName, (delta / 1000).toFixed(2))
  }

  if (delta > REQUEST_TTL) return res.status(408).json({ error: 'stale' })
  next()
}

function authorize (req, res, next) {
  const deviceId = req.deviceId

  return pairing.isPaired(deviceId)
    .then(deviceName => {
      if (deviceName) {
        req.deviceId = deviceId
        req.deviceName = deviceName
        return next()
      }

      return res.status(403).json({ error: 'Forbidden' })
    })
    .catch(next)
}

const skip = (req, res) => _.includes(req.path, ['/poll', '/state', '/logs']) && _.includes(res.statusCode, [200, 408])

const configRequiredRoutes = [
  '/poll',
  '/event',
  '/phone_code',
  '/customer',
  '/tx'
]

const app = express()
const localApp = express()

app.use(helmet({ noCache: true }))
app.use(bodyParser.json({ limit: '2mb' }))
app.use(morgan('dev', { skip, stream: logger.stream }))

// These two have their own authorization
app.post('/pair', populateDeviceId, pair)
app.get('/ca', ca)

app.use(populateDeviceId)
if (!devMode) app.use(authorize)
app.use(configRequiredRoutes, populateSettings)
app.use(filterOldRequests)

app.get('/poll', poll)
app.post('/state', stateChange)

app.post('/verify_user', verifyUser)
app.post('/verify_transaction', verifyTx)

app.post('/phone_code', getCustomerWithPhoneCode)
app.patch('/customer/:id', updateCustomer)

app.post('/tx', postTx)
app.get('/tx/:id', getTx)
app.get('/tx', getPhoneTx)
app.get('/logs', getLastSeen)
app.post('/logs', updateLogs)

app.use(errorHandler)
app.use((req, res) => {
  res.status(404).json({ error: 'No such route' })
})

localApp.get('/pid', (req, res) => {
  const deviceId = req.query.device_id
  const pidRec = pids[deviceId]
  res.json(pidRec)
})

localApp.post('/reboot', (req, res) => {
  const deviceId = req.query.device_id
  const pid = pids[deviceId] && pids[deviceId].pid

  if (!deviceId || !pid) {
    return res.sendStatus(400)
  }

  reboots[deviceId] = pid
  res.sendStatus(200)
})

localApp.post('/restartServices', (req, res) => {
  const deviceId = req.query.device_id
  const pid = pids[deviceId] && pids[deviceId].pid

  if (!deviceId || !pid) {
    return res.sendStatus(400)
  }

  restartServicesMap[deviceId] = pid
  res.sendStatus(200)
})

localApp.post('/dbChange', (req, res, next) => {
  settingsCache.cache = null
  return settingsLoader.loadLatest()
    .then(poller.reload)
    .then(() => logger.info('Config reloaded'))
    .catch(err => {
      logger.error(err)
      res.sendStatus(500)
    })
})

function sha256 (buf) {
  const crypto = require('crypto')
  const hash = crypto.createHash('sha256')

  hash.update(buf)
  return hash.digest('hex').toString('hex')
}

function populateDeviceId (req, res, next) {
  const deviceId = _.isFunction(req.connection.getPeerCertificate)
    ? sha256(req.connection.getPeerCertificate().raw)
    : null

  req.deviceId = deviceId
  req.deviceTime = req.get('date')

  next()
}

let oldVersionId = 'initial'

function populateSettings (req, res, next) {
  const versionId = req.headers['config-version']
  if (versionId !== oldVersionId) {
    oldVersionId = versionId
  }

  // Clear cache every hour
  if (Date.now() - settingsCache.timestamp > SETTINGS_CACHE_REFRESH) {
    settingsCache.cache = null
  }

  if (!versionId && settingsCache.cache) {
    req.settings = settingsCache.cache
    return next()
  }

  if (!versionId && !settingsCache.cache) {
    return settingsLoader.loadLatest()
      .then(settings => {
        settingsCache.cache = settings
        settingsCache.timestamp = Date.now()
        req.settings = settings
      })
      .then(() => next())
      .catch(next)
  }

  settingsLoader.load(versionId)
    .then(settings => { req.settings = settings })
    .then(() => helpers.updateDeviceConfigVersion(versionId))
    .then(() => next())
    .catch(next)
}

function createTerms (config) {
  return {
    active: config.termsScreenActive,
    title: config.termsScreenTitle,
    text: nmd(config.termsScreenText),
    accept: config.termsAcceptButtonText,
    cancel: config.termsCancelButtonText
  }
}

module.exports = { app, localApp }
<|endoftext|>


<commit_before>exports = module.exports = function (kabam) {

  kabam.app.get('/h', function(request,response){
    request.model.groups.find({tier:1},function(err,schoolsFound){
      response.render('groups/hierarchy',{
        'title':'Schools',
        'doIndex':true,
        'schools':schoolsFound
      });
    });
  });

  kabam.app.get(/\/hhh\/([a-z0-9_]+)\/?([a-z0-9_]*)\/?([a-z0-9_]*)\/?$/, function (request, response) {
    response.json(request.params);
  });

  //get group homepage
  kabam.app.get(/\/h\/([a-z0-9_]+)\/?([a-z0-9_]*)\/?([a-z0-9_]*)\/?$/, function (request, response) {
    //tier 1 - school
    request.model.groups.findGroup(request.params[0], request.params[1], request.params[2], function (err, groupFound) {
      if (err) {
        throw err;
      }
      if(groupFound){
        groupFound.export(request.user, function(err,groupPrepared){
          if(request.is('json')){
            response.json(groupPrepared);
          } else {
            console.log(groupPrepared);
            response.render('groups/group', {
              'title':groupPrepared.name,
              'doIndex':true,
              'group':groupPrepared
            });
          }
        });
      } else {
        response.send(404);
      }
    });
  });

  //get group blog
  kabam.app.get(/\/h\/([a-z0-9_]+)\/blog$/, function (request, response) {
    request.model.groups.findGroup(request.params[0],  function (err, groupFound) {
      if (err) {
        throw err;
      }
      groupFound.getBlog(request.user,function(err,blogFound){
        response.json(blogFound);//todo - create template later
      });
    });

  });

  //get group members
  kabam.app.get(/\/h\/([a-z0-9_]+)\/members$/, function (request, response) {
    response.send('tier1 group blog ' + request.params[0]);
  });

};<commit_msg>[feature] - route for comments<commit_after>exports = module.exports = function (kabam) {

  kabam.app.get('/h', function(request,response){
    request.model.groups.find({tier:1},function(err,schoolsFound){
      response.render('groups/hierarchy',{
        'title':'Schools',
        'doIndex':true,
        'schools':schoolsFound
      });
    });
  });

  //get group homepage
  kabam.app.get(/\/h\/([a-z0-9_]+)\/?([a-z0-9_]*)\/?([a-z0-9_]*)\/?$/, function (request, response) {
    //tier 1 - school
    request.model.groups.findGroup(request.params[0], request.params[1], request.params[2], function (err, groupFound) {
      if (err) {
        throw err;
      }
      if(groupFound){
        groupFound.export(request.user, function(err,groupPrepared){
          if(request.is('json')){
            response.json(groupPrepared);
          } else {
            console.log(groupPrepared);
            response.render('groups/group', {
              'title':groupPrepared.name,
              'doIndex':true,
              'group':groupPrepared
            });
          }
        });
      } else {
        response.send(404);
      }
    });
  });

  //get group blog
  kabam.app.get(/\/h\/([a-z0-9_]+)\/?([a-z0-9_]*)\/?([a-z0-9_]*)\/blog$/, function (request, response) {
    request.model.groups.findGroup(
        request.params[0],
        request.params[1],
        request.params[2],
        function (err, groupFound) {
          if (err) {
            throw err;
          }
        if(groupFound){
          groupFound.getBlog(request.user,function(err,blogFound){
            console.log(request.params);
            response.json(blogFound);//todo - create template later
          });
        } else {
          response.send(404);
        }
        });
  });

  //get group members
  kabam.app.get(/\/h\/([a-z0-9_]+)\/members$/, function (request, response) {
    response.send('tier1 group blog ' + request.params[0]);
  });

};
<|endoftext|>


<commit_before>var sys = require("sys");

if (!GLOBAL.assertEquals) {
  process.mixin(GLOBAL, require("mjsunit"));
}

exports.run = function(testSuites) {

  // Setup ARGS
  var args = {};

  var current = "";

  for (var i=0; i<process.ARGV.length; i++)
  {
    current = process.ARGV[i];
    if (current.substring(0,2) === "--") {
      if (process.ARGV[i+1]) {
        if (process.ARGV[i+1].substring(0,1) === "-") {
          args[current.substring(2)] = true;
        } else {
          args[current.substring(2)] = process.ARGV[++i] || true;
        }
      } else {
        args[current.substring(2)] = true;
      }
    } else if (current.substring(0,1) === "-") {
      args[current.substring(1)] = process.ARGV[++i] || true;
    }
  }

  var errors = [];
  var total = 0;

  var runTest  = function(suiteName, suite, test) {
    total++;

    if (suite.setUp) {
      suite.setUp();
    }

    try {
      suite.cases[test].call(GLOBAL);
    } catch (e) {
      if (!suite.errors) {
        suite.errors = [];
      }
      suite.errors.push({ suiteName:suiteName, method: test, error: e});
      errors.push({ suiteName:suiteName, method: test, error: e});
    }

    if (suite.tearDown) {
      suite.tearDown();
    }
  };

  var runSuite = function(suiteName) {
    testSuites[suiteName].total = 0;
    testSuites[suiteName].errors = [];
    for (var test in testSuites[suiteName].cases)
    {
      if (testSuites[suiteName].cases.hasOwnProperty(test)) {
        testSuites[suiteName].total++;
        runTest(suiteName, testSuites[suiteName], test);
      }
    }
  }

  if (args.suite) {
    if (testSuites[args.suite]) {
      if (args.test) {
        runTest(args.suite, testSuites[args.suite], args.test);
      } else {
        runSuite(args.suite);
      }
    } else {
      sys.puts("Invalid Suite Name\n\n Existing suites:")
      for (var suiteName in testSuites) {
        if (testSuites.hasOwnProperty(suiteName)) {
          sys.puts("  " + suiteName);
        }
      }
      sys.puts("");
      process.exit(1);
    }

  } else {

    for (suiteName in testSuites)
    {
      if (testSuites.hasOwnProperty(suiteName)) {
        runSuite(suiteName);
      }
    }
  }

  if (args.verbose) {
    for (var i = 0; i<errors.length; i++)
    {
      sys.puts('\nFailure in ' + errors[i].suiteName + ' suite; method: ' + errors[i].method);
      sys.puts(errors[i].error.stack);
    }
  }

  if (args.suite) {
    sys.puts(" ");
    sys.puts(errors.length + " of " + total + " failed (" + Math.round(((total-errors.length)/total)*100)  + "% success)" );
    sys.puts(" ");
  } else {
  var numerator, denominator, percent;
  sys.puts("");

  var padName = function(name) {
    while(name.length < 13) {
      name+=" ";
    }
    return name;
  }

  for (var suiteName in testSuites)
  {
    if (testSuites.hasOwnProperty(suiteName)) {
      numerator   = testSuites[suiteName].total - testSuites[suiteName].errors.length;
      denominator = testSuites[suiteName].total;
      percent     = Math.round((numerator / denominator)*100,2) || 100;
      sys.puts(padName(suiteName) + "\t" + numerator + "/" + denominator + "\t\t" + percent + "% passing");
    }
  }

  sys.puts("--------------------------------------------");
  numerator   = total-errors.length;
  denominator = total;
  percent     = Math.round((numerator / denominator)*100,2) || 100;
  sys.puts("TOTALS:\t\t" + numerator + "/" + denominator + "\t\t" + percent + "% passing\r\n");
  }
};
<commit_msg>fixed totals display<commit_after>var sys = require("sys");

if (!GLOBAL.assertEquals) {
  process.mixin(GLOBAL, require("mjsunit"));
}

exports.run = function(testSuites) {

  // Setup ARGS
  var args = {};

  var current = "";

  for (var i=0; i<process.ARGV.length; i++)
  {
    current = process.ARGV[i];
    if (current.substring(0,2) === "--") {
      if (process.ARGV[i+1]) {
        if (process.ARGV[i+1].substring(0,1) === "-") {
          args[current.substring(2)] = true;
        } else {
          args[current.substring(2)] = process.ARGV[++i] || true;
        }
      } else {
        args[current.substring(2)] = true;
      }
    } else if (current.substring(0,1) === "-") {
      args[current.substring(1)] = process.ARGV[++i] || true;
    }
  }

  var errors = [];
  var total = 0;

  var runTest  = function(suiteName, suite, test) {
    total++;

    if (suite.setUp) {
      suite.setUp();
    }

    try {
      suite.cases[test].call(GLOBAL);
    } catch (e) {
      if (!suite.errors) {
        suite.errors = [];
      }
      suite.errors.push({ suiteName:suiteName, method: test, error: e});
      errors.push({ suiteName:suiteName, method: test, error: e});
    }

    if (suite.tearDown) {
      suite.tearDown();
    }
  };

  var runSuite = function(suiteName) {
    testSuites[suiteName].total = 0;
    testSuites[suiteName].errors = [];
    for (var test in testSuites[suiteName].cases)
    {
      if (testSuites[suiteName].cases.hasOwnProperty(test)) {
        testSuites[suiteName].total++;
        runTest(suiteName, testSuites[suiteName], test);
      }
    }
  }

  if (args.suite) {
    if (testSuites[args.suite]) {
      if (args.test) {
        runTest(args.suite, testSuites[args.suite], args.test);
      } else {
        runSuite(args.suite);
      }
    } else {
      sys.puts("Invalid Suite Name\n\n Existing suites:")
      for (var suiteName in testSuites) {
        if (testSuites.hasOwnProperty(suiteName)) {
          sys.puts("  " + suiteName);
        }
      }
      sys.puts("");
      process.exit(1);
    }

  } else {

    for (suiteName in testSuites)
    {
      if (testSuites.hasOwnProperty(suiteName)) {
        runSuite(suiteName);
      }
    }
  }

  if (args.verbose) {
    for (var i = 0; i<errors.length; i++)
    {
      sys.puts('\nFailure in ' + errors[i].suiteName + ' suite; method: ' + errors[i].method);
      sys.puts(errors[i].error.stack);
    }
  }

  if (args.suite) {
    sys.puts(" ");
    sys.puts(errors.length + " of " + total + " failed (" + Math.round(((total-errors.length)/total)*100)  + "% success)" );
    sys.puts(" ");
  } else {
  var numerator, denominator, percent;
  sys.puts("");

  var padName = function(name) {
    while(name.length < 13) {
      name+=" ";
    }
    return name;
  }

  for (var suiteName in testSuites)
  {
    if (testSuites.hasOwnProperty(suiteName)) {
      numerator   = testSuites[suiteName].total - testSuites[suiteName].errors.length;
      denominator = testSuites[suiteName].total;
      percent     = Math.round((numerator / denominator)*100,2) || 0;
      sys.puts(padName(suiteName) + "\t" + numerator + "/" + denominator + "\t\t" + percent + "% passing");
    }
  }

  sys.puts("--------------------------------------------");
  numerator   = total-errors.length;
  denominator = total;
  percent     = Math.round((numerator / denominator)*100,2) || 0;
  sys.puts("TOTALS:\t\t" + numerator + "/" + denominator + "\t" + percent + "% passing\r\n");
  }
};
<|endoftext|>


<commit_before>var Benchmark = require('benchmark');
var Table     = require('easy-table');
var chalk     = require('chalk');
var path      = require('path');
var format    = require('util').format;

function pad(num, chr) {
  return Array(num + 1).join(chr || ' ');
}

var Runner = module.exports = function Runner(files) {
  if (! (this instanceof Runner)) {
    return new Runner(files);
  }
  this.files = files;
};

Runner.prototype.run = function() {
  this._run(this.files.shift());
};

Runner.prototype._run = function(file) {
  // No more files to run?
  if (file === undefined) {
    return;
  }

  var runner       = this;
  this.suites      = [];
  global.suite     = this.addSuite.bind(this);
  global.benchmark = this.addBenchmark.bind(this);

  require(path.resolve(process.cwd(), file));

  function runSuite(suite) {
    // No more suites to run?
    if (suite === undefined) {
      return runner._run(runner.files.shift());
    }
    suite.run({ async : true }).on('complete', function() {
      return runSuite(runner.suites.shift());
    });
  }
  if (! this.suites.length) {
    return this._run(this.files.shift());
  }
  console.log('• %s:\n', file);
  return runSuite(this.suites.shift());
};

Runner.prototype.addSuite = function(name, fn) {
  if (typeof name === Function) {
    fn   = name;
    name = 'Suite#' + (this.suites.length + 1);
  }

  // Create a new suite
  var suite = this.suites[this.suites.length] = new Benchmark.Suite(name);

  // Register benchmarks and hooks.
  fn();

  // Set up event handlers and start the suite.
  var idx = 0;
  suite.on('start', function() {
    process.stdout.write(format('%s• %s', pad(2), this.name));
  }).on('cycle', function() {
    process.stdout.write('.');
  }).on('error', function(err) {
  }).on('complete', function() {
    var t        = new Table();
    var finished = 0;
    this.forEach(function(bench) {
      if (bench.aborted) {
        t.cell('status', pad(4) + chalk.red('× ABORTED'));
      } else {
        finished++;
        t.cell('status', pad(4) + chalk.green('✔'));
      }
      t.cell('name',   bench.name + ': ');
      if (! bench.aborted) {
        t.cell('hz',     bench.hz.toFixed(2).toString().replace(/\B(?=(\d{3})+\b)/g, ','), function(str, width) {
          return Table.padLeft(str, width);
        });
        t.cell('label',  'ops/sec');
        t.cell('rme',    '±' + bench.stats.rme.toFixed(2) + '%');
        t.cell('runs',   '(' + bench.stats.sample.length + ' runs sampled)');
      }
      t.newRow();
    });
    console.log('\n\n' + t.print());
    if (finished > 1) {
      console.log(pad(4) + chalk.green('➔ Fastest is ' + this.filter('fastest').pluck('name')) + '\n');
    }
  });
};

Runner.prototype.addBenchmark = function(name, fn) {
  var suite = this.suites[this.suites.length - 1];
  if (typeof name === Function) {
    fn   = name;
    name = 'Benchmark#' + (suite.length + 1);
  }
  // async?
  var async = fn.length === 1;
  var bench = new Benchmark({
    name  : name,
    defer : async,
    fn    : function(deferred) {
      return fn(function(err) {
        if (err) {
          deferred.benchmark._error = err;
          deferred.benchmark.abort();
        }
        deferred && deferred.resolve();
      });
    }
  });
  suite.add(bench);
};
<commit_msg>Some output changes<commit_after>var Benchmark = require('benchmark');
var Table     = require('easy-table');
var chalk     = require('chalk');
var path      = require('path');
var format    = require('util').format;

function pad(num, chr) {
  return Array(num + 1).join(chr || ' ');
}

var Runner = module.exports = function Runner(files) {
  if (! (this instanceof Runner)) {
    return new Runner(files);
  }
  this.files = files;
};

Runner.prototype.run = function() {
  this._run(this.files.shift());
};

Runner.prototype._run = function(file) {
  // No more files to run?
  if (file === undefined) {
    return;
  }

  var runner       = this;
  this.suites      = [];
  global.suite     = this.addSuite.bind(this);
  global.benchmark = this.addBenchmark.bind(this);

  require(path.resolve(process.cwd(), file));

  function runSuite(suite) {
    // No more suites to run?
    if (suite === undefined) {
      return runner._run(runner.files.shift());
    }
    suite.run({ async : true }).on('complete', function() {
      return runSuite(runner.suites.shift());
    });
  }
  if (! this.suites.length) {
    return this._run(this.files.shift());
  }
  console.log('• %s:\n', file);
  return runSuite(this.suites.shift());
};

Runner.prototype.addSuite = function(name, fn) {
  if (typeof name === Function) {
    fn   = name;
    name = 'Suite#' + (this.suites.length + 1);
  }

  // Create a new suite
  var suite = this.suites[this.suites.length] = new Benchmark.Suite(name);

  // Register benchmarks and hooks.
  fn();

  // Set up event handlers and start the suite.
  suite.on('start', function() {
    process.stdout.write(format('%s• %s', pad(2), this.name));
  }).on('cycle', function() {
    process.stdout.write('.');
  }).on('error', function(err) {
  }).on('complete', function() {
    var table    = new Table();
    var finished = 0;
    this.forEach(function(bench) {
      if (bench.aborted) {
        table.cell('status', pad(4) + chalk.red('×'));
      } else {
        finished++;
        table.cell('status', pad(4) + chalk.green('✔'));
      }
      table.cell('name', chalk[ bench.aborted ? 'red' : 'green' ](bench.name + ': '));
      if (bench.aborted) {
        table.cell('hz', chalk.red('ABORTED'));
      } else {
        table.cell('hz', chalk.green(bench.hz.toFixed(2).toString().replace(/\B(?=(\d{3})+\b)/g, ',')), function(str, width) {
          return Table.padLeft(str, width);
        });
        table.cell('label',  chalk.green('ops/sec'));
        table.cell('rme',    '±' + bench.stats.rme.toFixed(2) + '%');
        table.cell('runs',   '(' + bench.stats.sample.length + ' runs sampled)');
      }
      table.newRow();
    });
    console.log('\n\n' + table.print());
    if (finished > 1) {
      console.log(pad(4) + chalk.green('➔ Fastest is ' + this.filter('fastest').pluck('name')) + '\n');
    }
  });
};

Runner.prototype.addBenchmark = function(name, fn) {
  var suite = this.suites[this.suites.length - 1];
  if (typeof name === Function) {
    fn   = name;
    name = 'Benchmark#' + (suite.length + 1);
  }
  // async?
  var async = fn.length === 1;
  var bench = new Benchmark({
    name  : name,
    defer : async,
    fn    : function(deferred) {
      return fn(function(err) {
        if (err) {
          deferred.benchmark._error = err;
          deferred.benchmark.abort();
        }
        deferred && deferred.resolve();
      });
    }
  });
  suite.add(bench);
};
<|endoftext|>


<commit_before>var _ = require('lodash');
var path = require('path');
var resolve = require('resolve');
var glob = require('glob');

module.exports = GruntBrowserifyRunner;

function GruntBrowserifyRunner(options) {
  this.browserify = options.browserify;
  this.watchify = options.watchify;
  this.logger = options.logger;
  this.writer = options.writer;
  this.firstBuild = true;
}

GruntBrowserifyRunner.prototype = _.create(GruntBrowserifyRunner.prototype, {
  run: function (files, destination, options, next) {
    var self = this;

    //set constructor options and instantiate
    var bOpts = options.browserifyOptions || {};
    bOpts.entries = files;

    // Watchify requires specific arguments
    if(options.watch) {
      bOpts = _.extend({ cache: {}, packageCache: {}, fullPaths: true }, bOpts);
    }

    //determine watchify or browserify
    var b = options.watch ? this.watchify(this.browserify(bOpts)) : this.browserify(bOpts);

    b.on('error', function (err) {
      self.logger.fail.warn(err);
    });

    if(options.bundleOptions) {
      throw new Error('bundleOptions is no longer used. Move all option in browserifyOptions.');
    }

    if (options.alias) {
      _.forEach(options.alias, function (aliasPair) {
        if (!aliasPair.match(':')) {
          self.logger.fail.warn('Aliases must be split with a colon');
        }
        else {
          aliasPair = aliasPair.split(':');
          b.require(aliasPair[0], {expose: aliasPair[1]});
        }
      });

    }

    if (options.require) {
      _.forEach(options.require, function (file) {
        b.require(file);
      });
    }

    if (options.exclude) {
      _.forEach(options.exclude, function (file) {
        runOptionForGlob(b, 'exclude', file);
      });
    }

    if (options.ignore) {
      _.forEach(options.ignore, function (file) {
        runOptionForGlob(b, 'ignore', file);
      });
    }

    if (options.external) {
      _.forEach(options.external, function (id) {
        //allow externalizing of alias lists
        if (id.match(':')) {
          id = id.split(':')[1];
        }

        if (testForGlob(id)) {
          runOptionForGlob(b, 'external', id);
        }
        else {
          b.external(id);
        }
      });
    }

    if (options.transform) {
      _.forEach(options.transform, function (transformer) {
        if (typeof transformer !== 'object') {
          b.transform(transformer);
        }
        else {
          b.transform(transformer[1], transformer[0]);
        }
      });
    }

    if (options.plugin) {
      _.forEach(options.plugin, function (plugin) {
        if (typeof plugin !== 'object') {
          b.plugin(plugin);
        }
        else {
          b.plugin(plugin[0], plugin[1]);
        }
      });
    }

    var destPath = this.createDestDir(destination);
    var keepAlive = this.keepAliveFn.bind(this, options);
    var done = options.keepAlive? keepAlive : next;
    var bundleComplete = this.onBundleComplete(destination, options, done);

    if (options.watch) {
      var bundleUpdate = this.onBundleComplete(destination, options, keepAlive);
      b.on('update', function (ids) {
        ids.forEach(function (id) {
          self.logger.log.ok(id + ' changed, updating browserify bundle.');
        });
        doBundle(b, options, bundleUpdate);
      });
    }

    doBundle(b, options, bundleComplete);
  },

  createDestDir: function (destination) {
    var destPath = path.dirname(path.resolve(destination));
    if (!this.writer.exists(destPath)) {
      this.writer.mkdir(destPath);
    }
    return destPath;
  },

  keepAliveFn: function (options) {
    this.logger.log.ok('Watchifying...');
  },

  onBundleComplete: function (destination, options, next) {
    var self = this;
    return function (err, buf) {
      if (err) {
        self.logger.log.error(err);
        if (self.firstBuild || !options.keepAlive) {
          self.logger.fail.warn('Error running grunt-browserify.');
        }
      }
      else if (buf) {
        self.writer.write(destination, buf);
      }

      self.firstBuild = false;
      next();
    };
  }
});

function doBundle(browserifyInstance, opts, bundleComplete) {
  if (opts.preBundleCB) {
    opts.preBundleCB(browserifyInstance);
  }

  browserifyInstance.bundle(function (err, buf) {
    if (opts.postBundleCB) {
      opts.postBundleCB(err, buf, bundleComplete);
    }
    else {
      bundleComplete(err, buf);
    }
  });
}

function testForGlob(id) {
  return (/\*/.test(id));
}

function runOptionForGlob(browserifyInstance, method, pattern) {
  var files = glob.sync(pattern);
  if (!files || files.length < 1) {
    //it's not a glob, it's a file / module path
    files = [pattern];
  }
  files.forEach(function (f) {
    browserifyInstance[method].call(browserifyInstance, f);
  });
}
<commit_msg>- Added support for Browserify `entries` option.<commit_after>var _ = require('lodash');
var path = require('path');
var resolve = require('resolve');
var glob = require('glob');

module.exports = GruntBrowserifyRunner;

function GruntBrowserifyRunner(options) {
  this.browserify = options.browserify;
  this.watchify = options.watchify;
  this.logger = options.logger;
  this.writer = options.writer;
  this.firstBuild = true;
}

GruntBrowserifyRunner.prototype = _.create(GruntBrowserifyRunner.prototype, {
  run: function (files, destination, options, next) {
    var self = this;

    //set constructor options and instantiate
    var bOpts = options.browserifyOptions || {};
    bOpts.entries = options.browserifyOptions.entries || files;

    // Watchify requires specific arguments
    if(options.watch) {
      bOpts = _.extend({ cache: {}, packageCache: {}, fullPaths: true }, bOpts);
    }

    //determine watchify or browserify
    var b = options.watch ? this.watchify(this.browserify(bOpts)) : this.browserify(bOpts);

    b.on('error', function (err) {
      self.logger.fail.warn(err);
    });

    if(options.bundleOptions) {
      throw new Error('bundleOptions is no longer used. Move all option in browserifyOptions.');
    }

    if (options.alias) {
      _.forEach(options.alias, function (aliasPair) {
        if (!aliasPair.match(':')) {
          self.logger.fail.warn('Aliases must be split with a colon');
        }
        else {
          aliasPair = aliasPair.split(':');
          b.require(aliasPair[0], {expose: aliasPair[1]});
        }
      });

    }

    if (options.require) {
      _.forEach(options.require, function (file) {
        b.require(file);
      });
    }

    if (options.exclude) {
      _.forEach(options.exclude, function (file) {
        runOptionForGlob(b, 'exclude', file);
      });
    }

    if (options.ignore) {
      _.forEach(options.ignore, function (file) {
        runOptionForGlob(b, 'ignore', file);
      });
    }

    if (options.external) {
      _.forEach(options.external, function (id) {
        //allow externalizing of alias lists
        if (id.match(':')) {
          id = id.split(':')[1];
        }

        if (testForGlob(id)) {
          runOptionForGlob(b, 'external', id);
        }
        else {
          b.external(id);
        }
      });
    }

    if (options.transform) {
      _.forEach(options.transform, function (transformer) {
        if (typeof transformer !== 'object') {
          b.transform(transformer);
        }
        else {
          b.transform(transformer[1], transformer[0]);
        }
      });
    }

    if (options.plugin) {
      _.forEach(options.plugin, function (plugin) {
        if (typeof plugin !== 'object') {
          b.plugin(plugin);
        }
        else {
          b.plugin(plugin[0], plugin[1]);
        }
      });
    }

    var destPath = this.createDestDir(destination);
    var keepAlive = this.keepAliveFn.bind(this, options);
    var done = options.keepAlive? keepAlive : next;
    var bundleComplete = this.onBundleComplete(destination, options, done);

    if (options.watch) {
      var bundleUpdate = this.onBundleComplete(destination, options, keepAlive);
      b.on('update', function (ids) {
        ids.forEach(function (id) {
          self.logger.log.ok(id + ' changed, updating browserify bundle.');
        });
        doBundle(b, options, bundleUpdate);
      });
    }

    doBundle(b, options, bundleComplete);
  },

  createDestDir: function (destination) {
    var destPath = path.dirname(path.resolve(destination));
    if (!this.writer.exists(destPath)) {
      this.writer.mkdir(destPath);
    }
    return destPath;
  },

  keepAliveFn: function (options) {
    this.logger.log.ok('Watchifying...');
  },

  onBundleComplete: function (destination, options, next) {
    var self = this;
    return function (err, buf) {
      if (err) {
        self.logger.log.error(err);
        if (self.firstBuild || !options.keepAlive) {
          self.logger.fail.warn('Error running grunt-browserify.');
        }
      }
      else if (buf) {
        self.writer.write(destination, buf);
      }

      self.firstBuild = false;
      next();
    };
  }
});

function doBundle(browserifyInstance, opts, bundleComplete) {
  if (opts.preBundleCB) {
    opts.preBundleCB(browserifyInstance);
  }

  browserifyInstance.bundle(function (err, buf) {
    if (opts.postBundleCB) {
      opts.postBundleCB(err, buf, bundleComplete);
    }
    else {
      bundleComplete(err, buf);
    }
  });
}

function testForGlob(id) {
  return (/\*/.test(id));
}

function runOptionForGlob(browserifyInstance, method, pattern) {
  var files = glob.sync(pattern);
  if (!files || files.length < 1) {
    //it's not a glob, it's a file / module path
    files = [pattern];
  }
  files.forEach(function (f) {
    browserifyInstance[method].call(browserifyInstance, f);
  });
}
<|endoftext|>


<commit_before><commit_msg>remove symlink<commit_after><|endoftext|>


<commit_before>// lib/schema.js
//
// Get all the files
//
// Copyright 2011-2012, E14N https://e14n.com/
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

var express = require("express"),
    _ = require("underscore"),
    DialbackClient = require("dialback-client"),
    Activity = require("./model/activity").Activity,
    User = require("./model/user").User,
    Edge = require("./model/edge").Edge,
    Share = require("./model/share").Share,
    Stream = require("./model/stream").Stream,
    Client = require("./model/client").Client,
    RequestToken = require("./model/requesttoken").RequestToken,
    AccessToken = require("./model/accesstoken").AccessToken,
    Nonce = require("./model/nonce").Nonce,
    Credentials = require("./model/credentials").Credentials,
    Confirmation = require("./model/confirmation").Confirmation,
    Favorite = require("./model/favorite").Favorite,
    Host = require("./model/host").Host,
    Membership = require("./model/membership").Membership,
    Proxy = require("./model/proxy").Proxy,
    Recovery = require("./model/recovery").Recovery,
    RemoteAccessToken = require("./model/remoteaccesstoken").RemoteAccessToken,
    RemoteRequestToken = require("./model/remoterequesttoken").RemoteRequestToken,
    ActivityObject = require("./model/activityobject").ActivityObject,
    DatabankStore = require('connect-databank')(express),
    Other = require("./model/other").Other;

var getSchema = function() {

    var i, type, Cls, schema = {};

    schema.activity = Activity.schema;
    schema.user = User.schema;
    schema.edge = Edge.schema;
    schema.share = Share.schema;
    schema.userlist = {"pkey": "id"};
    schema.usercount = {"pkey": "id"};

    _.extend(schema, Stream.schema);
    schema[Client.type] = Client.schema;
    schema[RequestToken.type] = RequestToken.schema;
    schema[AccessToken.type] = AccessToken.schema;
    schema[Nonce.type] = Nonce.schema;
    schema[Credentials.type] = Credentials.schema;
    schema[Other.type] = Other.schema;
    schema[Confirmation.type] = Confirmation.schema;
    schema[Favorite.type] = Favorite.schema;
    schema[Host.type] = Host.schema;
    schema[Membership.type] = Membership.schema;
    schema[Proxy.type] = Proxy.schema;
    schema[Recovery.type] = Recovery.schema;
    schema[RemoteAccessToken.type] = RemoteAccessToken.schema;
    schema[RemoteRequestToken.type] = RemoteRequestToken.schema;

    _.extend(schema, DatabankStore.schema);

    _.extend(schema, DialbackClient.schema);

    for (i = 0; i < ActivityObject.objectTypes.length; i++) {
        type = ActivityObject.objectTypes[i];
        Cls = ActivityObject.toClass(type);
        if (Cls.schema) {
            schema[type] = Cls.schema;
        } else {
            schema[type] = {"pkey": "id",
                            "fields": ["updated", "published", "displayName", "url"],
                            "indices": ["_uuid", "author.id"]};
        }
    }

    return schema;
};

exports.schema = getSchema();
<commit_msg>Use express-session.<commit_after>// lib/schema.js
//
// Get all the files
//
// Copyright 2011-2012, E14N https://e14n.com/
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

var express = require("express"),
    _ = require("underscore"),
    DialbackClient = require("dialback-client"),
    Activity = require("./model/activity").Activity,
    User = require("./model/user").User,
    Edge = require("./model/edge").Edge,
    Share = require("./model/share").Share,
    Stream = require("./model/stream").Stream,
    Client = require("./model/client").Client,
    RequestToken = require("./model/requesttoken").RequestToken,
    AccessToken = require("./model/accesstoken").AccessToken,
    Nonce = require("./model/nonce").Nonce,
    Credentials = require("./model/credentials").Credentials,
    Confirmation = require("./model/confirmation").Confirmation,
    Favorite = require("./model/favorite").Favorite,
    Host = require("./model/host").Host,
    Membership = require("./model/membership").Membership,
    Proxy = require("./model/proxy").Proxy,
    Recovery = require("./model/recovery").Recovery,
    RemoteAccessToken = require("./model/remoteaccesstoken").RemoteAccessToken,
    RemoteRequestToken = require("./model/remoterequesttoken").RemoteRequestToken,
    ActivityObject = require("./model/activityobject").ActivityObject,
    session = require("express-session"),
    DatabankStore = require('connect-databank')(session),
    Other = require("./model/other").Other;

var getSchema = function() {

    var i, type, Cls, schema = {};

    schema.activity = Activity.schema;
    schema.user = User.schema;
    schema.edge = Edge.schema;
    schema.share = Share.schema;
    schema.userlist = {"pkey": "id"};
    schema.usercount = {"pkey": "id"};

    _.extend(schema, Stream.schema);
    schema[Client.type] = Client.schema;
    schema[RequestToken.type] = RequestToken.schema;
    schema[AccessToken.type] = AccessToken.schema;
    schema[Nonce.type] = Nonce.schema;
    schema[Credentials.type] = Credentials.schema;
    schema[Other.type] = Other.schema;
    schema[Confirmation.type] = Confirmation.schema;
    schema[Favorite.type] = Favorite.schema;
    schema[Host.type] = Host.schema;
    schema[Membership.type] = Membership.schema;
    schema[Proxy.type] = Proxy.schema;
    schema[Recovery.type] = Recovery.schema;
    schema[RemoteAccessToken.type] = RemoteAccessToken.schema;
    schema[RemoteRequestToken.type] = RemoteRequestToken.schema;

    _.extend(schema, DatabankStore.schema);

    _.extend(schema, DialbackClient.schema);

    for (i = 0; i < ActivityObject.objectTypes.length; i++) {
        type = ActivityObject.objectTypes[i];
        Cls = ActivityObject.toClass(type);
        if (Cls.schema) {
            schema[type] = Cls.schema;
        } else {
            schema[type] = {"pkey": "id",
                            "fields": ["updated", "published", "displayName", "url"],
                            "indices": ["_uuid", "author.id"]};
        }
    }

    return schema;
};

exports.schema = getSchema();
<|endoftext|>


<commit_before>var app = require('cantina')
  , nested = require('nested-objects')
  , extend = require('./extend')
  , indexers = require('./indexers');

/**
 * Schema object.
 *
 * A schema 'definition' is parsed and converted into a full-fledged schema
 * object, which is then used in the creation of model collections.
 */
var Schema = module.exports = function Schema (schema) {
  if (!(this instanceof Schema)) return new Schema(schema);
  if (!schema.name) throw new Error('A schema requires a name property');
  if (!schema.version) throw new Error('A schema requires a version');
  this._schema = schema;
  this.name = schema.name;
  this.indexes = schema.indexes;
  this.properties = schema.properties;
  this.version = schema.version;
  this.strict = schema.strict !== false;
  this.privateProperties = [];
  this._parse();
  return this;
};

/**
 * Deep-extend the target schema, with one or more new schemas
 */
Schema.extend = function (/* target, source1, source2, etc. */) {
  if (arguments.length < 2) throw new Error('Extend method requires two or more schemas');
  return new Schema(extend.apply(null, [ true ].concat( [].slice.call(arguments).map(function (schema, idx) { return deinitialize(schema, idx > 0); }) )));
};

/**
 * Create a new deep-extended schema from this schema, using the passed-in schemas.
 */
Schema.prototype.extend = function () {
  return Schema.extend.apply(null, [ {}, this._schema ].concat( [].slice.call(arguments) ));
};

/**
 * Attach schema-defined model methods to target collection
 */
Schema.prototype.attach = function (collection, cb) {
  cb || (cb = function (err) { app.emit('error', err); });
  extend(collection, {
    sanitize: this.sanitize,
    defaults: this.defaults,
    prepare: this.prepare,
    validate: this.validate
  });
  var indexes = this.indexes && this.indexes[collection.__type];
  if (indexes) {
    if (!indexers[collection.__type]) cb(new Error('Index type not supported'));
    else indexers[collection.__type](collection, indexes, cb);
  }
  else process.nextTick(cb);
};

/**
 * Produce collection options from this schema.
 *
 * Non-hook options will be copied over directly. Hook options will be merged.
 *
 * If there are collisions on the model 'hooks', we run our functions first
 * and then call the collision.
 */
Schema.prototype.getOptions = function (options) {
  var self = this;

  options = options || {};

  if (!this._hooks) {
    this._hooks = this._createHooks();
  }

  // After running this._createHooks() we can copy over any properties to pass along
  options.privateProperties = this.privateProperties.concat(); // make a copy

  // Merge in our hooks.
  Object.keys(this._hooks).forEach(function (hook) {
    var handler = self._hooks[hook];
    if (options[hook]) {
      if (hook === 'create') {
        options.create = function (model) {
          handler(model);
          options.create(model);
        };
      }
      else {
        options[hook] = function (model, cb) {
          handler(model, function (err) {
            if (err) return cb(err);
            options[hook](model, cb);
          });
        };
      }
    }
    else {
      options[hook] = handler;
    }
  });

  return options;
};

/**
 * Create `cantina-models` hooks from parsed schema definitions.
 */
Schema.prototype._createHooks = function () {
  var hooks = {}
    , defs
    , self = this;

  if (!this._defs) {
    this._parse();
  }

  defs = this._defs;
  Object.keys(defs).forEach(function (name) {
    var handlers = defs[name]
      , hookName = 'schema:' + self.name + ':' + name;

    if (handlers.length) {
      if (name === 'create') {
        // Register event listeners.
        handlers.forEach(function (handler) {
          app.on(hookName, handler);
        });

        // Add create hook.
        hooks.create = function schemaHandler (model) {
          app.emit(hookName, model);
        };
      }
      else {
        // Register hook handlers.
        handlers.forEach(function (handler) {
          app.hook(hookName).add(function (model, cb) {
            var result = handler(model);
            if ((result instanceof Error) ||
              (Array.isArray(result) && result.length && result[0] instanceof Error)) return cb(result);
            cb();
          });
        });

        // Add hook.
        hooks[name] = function (model, cb) {
          app.hook(hookName).runSeries(model, cb);
        };
      }
    }
  });

  return hooks;
};

/**
 * Parse a schema and return the hook 'definitions'.
 */
Schema.prototype._parse = function () {
  var self = this
    , defs = {
        create: [],
        save: [],
        afterSave: [],
        load: [],
        destroy: [],
        afterDestroy: []
      }
    , methods = {
        defaults: [],
        prepare: [],
        validate: []
      };

  if (this.properties) {
    Object.keys(this.properties).forEach(function (name) {
      parseMethods(name, self.properties[name]);
    });

    if (self.strict) {
      defs.save.push(function (model) {
        deepFilterProperties(model, self.properties);
      });
    }
  }

  defs.save.push(function (model) {
    model._version = self.version;
  });

  extend(this, {
    sanitize: function sanitize (model) {
      self.privateProperties.forEach(function (name) {
        nested.delete(model, name);
      });
      if (self.strict) {
        deepFilterProperties(model, self.properties);
      }
    },
    defaults: function defaults (model) {
      return methods.defaults.forEach(function (fn) {
        fn(model);
      });
    },
    prepare: function prepare (model) {
      return methods.prepare.forEach(function (fn) {
        fn(model);
      });
    },
    validate: function validate (model) {
      return methods.validate.reduce(function (errors, fn) {
        var err = fn(model);
        if (err instanceof Error) errors.push(err);
        return errors;
      }, []);
    }
  });

  defs.create.push(this.defaults);
  defs.save.push(this.prepare, this.validate);

  return this._defs = defs;

  function parseMethods (name, attrs) {
    if (!attrs.type) {
      if (Array.isArray(attrs)) {
        return attrs.map(function (attrs_member) {
          return parseMethods(name, attrs_member);
        });
      }
      else {
        return Object.keys(attrs).map(function (child_name) {
          return parseMethods(name + '.' + child_name, attrs[child_name]);
        });
      }
    }
    else {
      // private
      if (attrs.private && self.privateProperties.indexOf(name) === -1) {
        self.privateProperties.push(name);
      }
      // defaults
      if ('default' in attrs) {
        // defaults will run on create, so no callback
        methods.defaults.push(function (model) {
          if ('undefined' === typeof nested.get(model, name)) {
            nested.set(model, name, attrs.default);
          }
        });
      }
      // prepare
      if (attrs.prepare && 'function' === typeof attrs.prepare) {
        methods.prepare.push(function (model) {
          nested.set(model, name, attrs.prepare(model));
        });
      }
      // required -- a type of validate
      if (attrs.required) {
        methods.validate.unshift(function (model) {
          if ('undefined' === typeof nested.get(model, name)) {
            return new Error('Missing required property ' + name);
          }
        });
      }
      // validators
      if (attrs.validators && Array.isArray(attrs.validators) && attrs.validators.length) {
        methods.validate.push(function (model) {
          var fn
            , val = nested.get(model, name);
          if (val) {
            for (var i = 0, len = attrs.validators.length; i < len; i++) {
              fn = attrs.validators[i];
              if (!fn(nested.get(model, name))) {
                return new Error('Validator ' + fn.name + ' failed for property ' + name);
              }
            }
          }
        });
      }
    }
  }

  function deepFilterProperties (attrs, schemaProperties) {
    Object.keys(attrs).forEach(function (prop) {
      if (!schemaProperties[prop]) {
        delete attrs[prop];
      }
      else if (!schemaProperties[prop].type) {
        deepFilterProperties(attrs[prop], schemaProperties[prop]);
      }
    });
  }
};

var uninheritableProps = [ '_defs', '_hooks' ];

function deinitialize (schema, clone) {
  if (!clone) {
    uninheritableProps.forEach(function (prop) {
      delete schema[prop];
    });
    return schema;
  }
  else {
    // shallow copy excluding uninheritableProps
    return Object.keys(schema).reduce(function (copy, prop) {
      if (uninheritableProps.indexOf(prop) === -1) {
        copy[prop] = schema[prop];
      }
      return copy;
    }, {});
  }
}
<commit_msg>Don't unleash Zalgo. Make sure cb always waits for nextTick.<commit_after>var app = require('cantina')
  , nested = require('nested-objects')
  , extend = require('./extend')
  , indexers = require('./indexers');

/**
 * Schema object.
 *
 * A schema 'definition' is parsed and converted into a full-fledged schema
 * object, which is then used in the creation of model collections.
 */
var Schema = module.exports = function Schema (schema) {
  if (!(this instanceof Schema)) return new Schema(schema);
  if (!schema.name) throw new Error('A schema requires a name property');
  if (!schema.version) throw new Error('A schema requires a version');
  this._schema = schema;
  this.name = schema.name;
  this.indexes = schema.indexes;
  this.properties = schema.properties;
  this.version = schema.version;
  this.strict = schema.strict !== false;
  this.privateProperties = [];
  this._parse();
  return this;
};

/**
 * Deep-extend the target schema, with one or more new schemas
 */
Schema.extend = function (/* target, source1, source2, etc. */) {
  if (arguments.length < 2) throw new Error('Extend method requires two or more schemas');
  return new Schema(extend.apply(null, [ true ].concat( [].slice.call(arguments).map(function (schema, idx) { return deinitialize(schema, idx > 0); }) )));
};

/**
 * Create a new deep-extended schema from this schema, using the passed-in schemas.
 */
Schema.prototype.extend = function () {
  return Schema.extend.apply(null, [ {}, this._schema ].concat( [].slice.call(arguments) ));
};

/**
 * Attach schema-defined model methods to target collection
 */
Schema.prototype.attach = function (collection, cb) {
  cb || (cb = function (err) { app.emit('error', err); });
  extend(collection, {
    sanitize: this.sanitize,
    defaults: this.defaults,
    prepare: this.prepare,
    validate: this.validate
  });
  var indexes = this.indexes && this.indexes[collection.__type];
  var error;
  if (indexes) {
    if (!indexers[collection.__type]) error = new Error('Index type not supported');
    else indexers[collection.__type](collection, indexes, cb);
  }
  else {
    process.nextTick(function () {
      cb(error);
    });
  }
};

/**
 * Produce collection options from this schema.
 *
 * Non-hook options will be copied over directly. Hook options will be merged.
 *
 * If there are collisions on the model 'hooks', we run our functions first
 * and then call the collision.
 */
Schema.prototype.getOptions = function (options) {
  var self = this;

  options = options || {};

  if (!this._hooks) {
    this._hooks = this._createHooks();
  }

  // After running this._createHooks() we can copy over any properties to pass along
  options.privateProperties = this.privateProperties.concat(); // make a copy

  // Merge in our hooks.
  Object.keys(this._hooks).forEach(function (hook) {
    var handler = self._hooks[hook];
    if (options[hook]) {
      if (hook === 'create') {
        options.create = function (model) {
          handler(model);
          options.create(model);
        };
      }
      else {
        options[hook] = function (model, cb) {
          handler(model, function (err) {
            if (err) return cb(err);
            options[hook](model, cb);
          });
        };
      }
    }
    else {
      options[hook] = handler;
    }
  });

  return options;
};

/**
 * Create `cantina-models` hooks from parsed schema definitions.
 */
Schema.prototype._createHooks = function () {
  var hooks = {}
    , defs
    , self = this;

  if (!this._defs) {
    this._parse();
  }

  defs = this._defs;
  Object.keys(defs).forEach(function (name) {
    var handlers = defs[name]
      , hookName = 'schema:' + self.name + ':' + name;

    if (handlers.length) {
      if (name === 'create') {
        // Register event listeners.
        handlers.forEach(function (handler) {
          app.on(hookName, handler);
        });

        // Add create hook.
        hooks.create = function schemaHandler (model) {
          app.emit(hookName, model);
        };
      }
      else {
        // Register hook handlers.
        handlers.forEach(function (handler) {
          app.hook(hookName).add(function (model, cb) {
            var result = handler(model);
            if ((result instanceof Error) ||
              (Array.isArray(result) && result.length && result[0] instanceof Error)) return cb(result);
            cb();
          });
        });

        // Add hook.
        hooks[name] = function (model, cb) {
          app.hook(hookName).runSeries(model, cb);
        };
      }
    }
  });

  return hooks;
};

/**
 * Parse a schema and return the hook 'definitions'.
 */
Schema.prototype._parse = function () {
  var self = this
    , defs = {
        create: [],
        save: [],
        afterSave: [],
        load: [],
        destroy: [],
        afterDestroy: []
      }
    , methods = {
        defaults: [],
        prepare: [],
        validate: []
      };

  if (this.properties) {
    Object.keys(this.properties).forEach(function (name) {
      parseMethods(name, self.properties[name]);
    });

    if (self.strict) {
      defs.save.push(function (model) {
        deepFilterProperties(model, self.properties);
      });
    }
  }

  defs.save.push(function (model) {
    model._version = self.version;
  });

  extend(this, {
    sanitize: function sanitize (model) {
      self.privateProperties.forEach(function (name) {
        nested.delete(model, name);
      });
      if (self.strict) {
        deepFilterProperties(model, self.properties);
      }
    },
    defaults: function defaults (model) {
      return methods.defaults.forEach(function (fn) {
        fn(model);
      });
    },
    prepare: function prepare (model) {
      return methods.prepare.forEach(function (fn) {
        fn(model);
      });
    },
    validate: function validate (model) {
      return methods.validate.reduce(function (errors, fn) {
        var err = fn(model);
        if (err instanceof Error) errors.push(err);
        return errors;
      }, []);
    }
  });

  defs.create.push(this.defaults);
  defs.save.push(this.prepare, this.validate);

  return this._defs = defs;

  function parseMethods (name, attrs) {
    if (!attrs.type) {
      if (Array.isArray(attrs)) {
        return attrs.map(function (attrs_member) {
          return parseMethods(name, attrs_member);
        });
      }
      else {
        return Object.keys(attrs).map(function (child_name) {
          return parseMethods(name + '.' + child_name, attrs[child_name]);
        });
      }
    }
    else {
      // private
      if (attrs.private && self.privateProperties.indexOf(name) === -1) {
        self.privateProperties.push(name);
      }
      // defaults
      if ('default' in attrs) {
        // defaults will run on create, so no callback
        methods.defaults.push(function (model) {
          if ('undefined' === typeof nested.get(model, name)) {
            nested.set(model, name, attrs.default);
          }
        });
      }
      // prepare
      if (attrs.prepare && 'function' === typeof attrs.prepare) {
        methods.prepare.push(function (model) {
          nested.set(model, name, attrs.prepare(model));
        });
      }
      // required -- a type of validate
      if (attrs.required) {
        methods.validate.unshift(function (model) {
          if ('undefined' === typeof nested.get(model, name)) {
            return new Error('Missing required property ' + name);
          }
        });
      }
      // validators
      if (attrs.validators && Array.isArray(attrs.validators) && attrs.validators.length) {
        methods.validate.push(function (model) {
          var fn
            , val = nested.get(model, name);
          if (val) {
            for (var i = 0, len = attrs.validators.length; i < len; i++) {
              fn = attrs.validators[i];
              if (!fn(nested.get(model, name))) {
                return new Error('Validator ' + fn.name + ' failed for property ' + name);
              }
            }
          }
        });
      }
    }
  }

  function deepFilterProperties (attrs, schemaProperties) {
    Object.keys(attrs).forEach(function (prop) {
      if (!schemaProperties[prop]) {
        delete attrs[prop];
      }
      else if (!schemaProperties[prop].type) {
        deepFilterProperties(attrs[prop], schemaProperties[prop]);
      }
    });
  }
};

var uninheritableProps = [ '_defs', '_hooks' ];

function deinitialize (schema, clone) {
  if (!clone) {
    uninheritableProps.forEach(function (prop) {
      delete schema[prop];
    });
    return schema;
  }
  else {
    // shallow copy excluding uninheritableProps
    return Object.keys(schema).reduce(function (copy, prop) {
      if (uninheritableProps.indexOf(prop) === -1) {
        copy[prop] = schema[prop];
      }
      return copy;
    }, {});
  }
}
<|endoftext|>


<commit_before>/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Code licensed under the BSD License:
http://yuilibrary.com/license/
*/
"use strict";

var parse = require('url').parse;
var qs = require('querystring');
var http = require('http');
var headers = {
    'Content-Type': 'text/plain'
};

exports.headers = headers;

exports.status = function (code, res) {
    var body = http.STATUS_CODES[code] || 'unknown';

    res.writeHead(code, 'HTTP/1.1 ' + body, headers);
    res.end(body);
};

exports.get = function (req, res) {
    var p = parse(req.url),
        code = ((req.method === 'GET' || req.method === 'HEAD' || req.method === 'OPTIONS') ? 200 : 403);

    res.writeHead(code, headers);
    res.end(p.query || '');
};

exports['delete'] = function (req, res) {
    var p = parse(req.url),
        code = ((req.method === 'DELETE') ? 200 : 403);

    res.writeHead(code, headers);
    res.end(p.query || '');
};

var post = function (method, req, res) {
    var code = ((req.method === method) ? 200 : 403),
        data = '',
        gotData = false,
        sent = false,
        body = '',
        end = function () {
            if (!sent) {
                sent = true;
                var b = qs.parse(data);
                if (Object.keys(b).length > 0) {
                    body = data;
                } else {
                    body = String(Object.keys(b).length);
                }
                res.writeHead(code, headers);
                res.end(body);
            }
        };

    if (req.body) { //Express
        gotData = true;
        data = qs.stringify(req.body);
        end();
    } else {

        req.on('data', function (c) {
            gotData = true;
            data += c;
        });

        req.on('end', end);

        //Seems as if the post doesn't have data
        //the end event doesn't fire??
        setTimeout(function () {
            if (!gotData) {
                end();
            }
        }, 750);
    }
};

exports.post = function (req, res) {
    post('POST', req, res);
};

exports.put = function (req, res) {
    post('PUT', req, res);
};

exports.delay = function (req, res) {

    var delay = parseInt(req.url.split('/').pop(), 10),
        seconds = (delay * 1000);

    setTimeout(function () {
        res.writeHead(200, headers);
        res.end('waited for ' + delay + ' seconds');
    }, seconds);

};

exports.json = function (req, res) {
    var p = parse(req.url),
        json = (p.query ? JSON.stringify(qs.parse(p.query)) : '{ "echo": true }');

    if (req.body) {
        json = JSON.stringify(req.body);
    }

    res.writeHead(200, {
        'Content-type': 'application/json'
    });
    res.end(json);
};

exports.jsonp = function (req, res) {
    var p = parse(req.url),
        code = 200,
        callback = '',
        query = qs.parse(p.query),
        content = { "echo": true },
        json = '';

    if (query.callback) {
        callback = query.callback;
        delete query.callback;
    }

    if (req.body) {
        query = req.body;
    }

    if (Object.keys(query).length) {
        content = query;
    }

    if (!callback) {
        code = 403;
        json = JSON.stringify('{ "echo": "error, no callback" }');
    } else {
        json = callback + '(' + JSON.stringify(content) + ');';
    }

    res.writeHead(code, {
        'Content-type': 'application/json'
    });
    res.end(json);
};
<commit_msg>Various bugs fixes from coverage reports<commit_after>/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Code licensed under the BSD License:
http://yuilibrary.com/license/
*/
"use strict";

var parse = require('url').parse;
var qs = require('querystring');
var http = require('http');
var headers = {
    'Content-Type': 'text/plain'
};

exports.headers = headers;

exports.status = function (code, res) {
    var body = http.STATUS_CODES[code];

    if (!body) {
        code = 500;
        body = 'Unknown Echo Status';
    }

    res.writeHead(code, 'HTTP/1.1 ' + body, headers);
    res.end(body);
};

exports.get = function (req, res) {
    var p = parse(req.url),
        code = ((req.method === 'GET' || req.method === 'HEAD' || req.method === 'OPTIONS') ? 200 : 403);

    res.writeHead(code, headers);
    res.end(p.query || '');
};

exports['delete'] = function (req, res) {
    var p = parse(req.url),
        code = ((req.method === 'DELETE') ? 200 : 403);

    res.writeHead(code, headers);
    res.end(p.query || '');
};

var post = function (method, req, res) {
    var code = ((req.method === method) ? 200 : 403),
        data = '',
        gotData = false,
        sent = false,
        body = '',
        end = function () {
            if (!sent) {
                sent = true;
                var b = qs.parse(data);
                if (Object.keys(b).length > 0) {
                    body = data;
                } else {
                    body = String(Object.keys(b).length);
                }
                res.writeHead(code, headers);
                res.end(body);
            }
        };

    if (req.body) { //Express
        gotData = true;
        data = qs.stringify(req.body);
        end();
    } else {

        req.on('data', function (c) {
            gotData = true;
            data += c;
        });

        req.on('end', end);

        //Seems as if the post doesn't have data
        //the end event doesn't fire??
        setTimeout(function () {
            if (!gotData) {
                end();
            }
        }, 750);
    }
};

exports.post = function (req, res) {
    post('POST', req, res);
};

exports.put = function (req, res) {
    post('PUT', req, res);
};

exports.delay = function (req, res) {

    var delay = parseInt(req.url.split('/').pop(), 10),
        seconds = (delay * 1000);

    setTimeout(function () {
        res.writeHead(200, headers);
        res.end('waited for ' + delay + ' seconds');
    }, seconds);

};

exports.json = function (req, res) {
    var p = parse(req.url),
        json = (p.query ? JSON.stringify(qs.parse(p.query)) : '{ "echo": true }');

    if (req.body) {
        json = JSON.stringify(req.body);
    }

    res.writeHead(200, {
        'Content-type': 'application/json'
    });
    res.end(json);
};

exports.jsonp = function (req, res) {
    var p = parse(req.url),
        code = 200,
        callback = '',
        query = qs.parse(p.query),
        content = { "echo": true },
        json = '';

    if (query.callback) {
        callback = query.callback;
        delete query.callback;
    }

    if (req.body) {
        query = req.body;
        if (query.callback) {
            callback = query.callback;
            delete query.callback;
        }
    }

    if (Object.keys(query).length) {
        content = query;
    }

    if (!callback) {
        code = 403;
        json = JSON.stringify({ "echo": "error, no callback" });
    } else {
        json = callback + '(' + JSON.stringify(content) + ');';
    }

    res.writeHead(code, {
        'Content-type': 'application/json'
    });
    res.end(json);
};
<|endoftext|>