<commit_before>'use strict'

// setup
const http = require('http')
const tinyParams = require('tiny-params')
const zlib = require('zlib')
const port = process.env.PORT || process.env.port || 8091
const defaultTimeout = 120000
const checkId = {read: true, update: true, delete: true}
const primitives = {string: true, number: true, boolean: true}
const noop = () => {}
const dummyRes = {
  addTrailers: noop,
  end: noop,
  getHeader: noop,
  getHeaderNames: noop,
  getHeaders: noop,
  hasHeader: noop,
  removeHeader: noop,
  setHeader: noop,
  setTimeout: noop,
  write: noop,
  writeContinue: noop,
  writeHead: noop
}
const scrud = {
  GET: 'search',
  'GET?': 'search',
  POST: 'create',
  'POST/': 'create',
  'GET/': 'read',
  'PUT/': 'update',
  'DELETE/': 'delete'
}
const hasBody = {create: true, update: true}
const wlSign = [
  'algorithm',
  'expiresIn',
  'notBefore',
  'audience',
  'issuer',
  'jwtid',
  'subject',
  'noTimestamp',
  'header'
]

// globals
let jsonwebtoken
let logger
let pgPool
let jwtOpts
let authTrans
let pgPrefix = ''
let base = '/'
let baseChars = base.length
let maxBodyBytes = 1e6
let resources = {}
let allowOrigins = {}
let gzipThreshold = 1000
let getIp
let setScrudHeader
let noCache

// local helpers
const logIt = (e, level = 'fatal') => {
  typeof logger === 'function' ? logger(e, level) : console.log(e)
}

let lastParsed = {}
const parseUrl = (req) => {
  let tmp = lastParsed
  let sig = `${req.method}${req.url}`
  if (tmp.sig === sig) return tmp.data
  let url = decodeURIComponent(req.url).slice(baseChars)
  let sIdx = url.indexOf('/')
  let qIdx = url.indexOf('?')
  let modIdx = (sIdx === -1 || (qIdx !== -1 && sIdx > qIdx)) ? qIdx : sIdx
  let lastIdx = url.length - 1
  let id
  if (sIdx === modIdx) {
    let postMod = url.slice(sIdx + 1)
    if (postMod) {
      let nextMod = postMod.indexOf('/')
      if (nextMod === -1) nextMod = postMod.indexOf('?')
      id = nextMod === -1 ? postMod : postMod.slice(0, nextMod)
    }
  }
  let noMod = modIdx === -1
  let name = noMod ? url : url.slice(0, modIdx)
  let modifier = noMod || modIdx === lastIdx ? '' : url.charAt(modIdx)
  let action = scrud[`${req.method}${modifier}`]
  let params = tinyParams(url)
  let data = { url, name, action, id, params }
  lastParsed = { sig, data }
  return data
}

const callPgFunc = (name, params, req) => {
  let q = `SELECT * FROM ${name}($1);`
  if (!pgPool) return Promise.reject(new Error('No database configured'))
  return pgPool.connect().then((client) => {
    let close = () => { if (client && client.end) client.end().catch(() => {}) }
    if (req && req.on) req.once('close', close)
    return client.query(q, [params]).then((data) => {
      if (req && req.removeListener) req.removeListener('close', close)
      client.release()
      return Promise.resolve((data.rows[0] || {})[name] || [])
    }).catch((err) => {
      if (req && req.removeListener) req.removeListener('close', close)
      client.release()
      return Promise.reject(err)
    })
  }).catch((err) => {
    try {
      err.meta = err.meta || {}
      err.meta.pgFunction = name
      let errObj = JSON.parse(err.message)
      err.message = errObj.error ? errObj.error : errObj
    } catch (ex) {}
    return Promise.reject(err)
  })
}

const bodyParse = (req) => new Promise((resolve, reject) => {
  let body = ''
  req.on('data', (d) => {
    body += d.toString()
    if (body.length > maxBodyBytes) return reject(new Error('Body too large'))
  })
  let parse = () => {
    try { resolve(body ? JSON.parse(body) : {}) } catch (ex) { resolve({}) }
  }
  req.on('end', parse)
})

const filterObj = (obj, ary) => {
  let base = {}
  ary.forEach((o) => { if (o in obj) base[o] = obj[o] })
  return base
}

// database action handlers
const handlers = {}
const find = handlers.read = (rsrc, req) => pgActions(rsrc, 'read', req)
const findAll = handlers.search = (rsrc, req) => pgActions(rsrc, 'search', req)
const create = handlers.create = (rsrc, req) => pgActions(rsrc, 'create', req)
const save = handlers.update = (rsrc, req) => pgActions(rsrc, 'update', req)
const destroy = handlers.delete = (rsrc, req) => pgActions(rsrc, 'delete', req)

// exports
module.exports = {
  register,
  start,
  sendData,
  sendErr,
  logIt,
  fourOhOne,
  fourOhFour,
  genToken,
  authenticate,
  find,
  findAll,
  insert: create,
  save,
  destroy,
  callPgFunc,
  read: (rsrc, req) => actionHandler(req, null, rsrc, 'read', true),
  create: (rsrc, req) => actionHandler(req, null, rsrc, 'create', true),
  search: (rsrc, req) => actionHandler(req, null, rsrc, 'search', true),
  update: (rsrc, req) => actionHandler(req, null, rsrc, 'update', true),
  delete: (rsrc, req) => actionHandler(req, null, rsrc, 'delete', true)
}

// register resource
function register (name, opts = {}) {
  if (!name) return Promise.reject(new Error(`No name specified in register`))
  return new Promise((resolve, reject) => {
    let r = resources[name] = Object.assign({}, opts, {name})
    if (Array.isArray(r.skipAuth)) {
      let skippers = {}
      r.skipAuth.forEach((a) => { skippers[a] = true })
      r.skipAuth = skippers
    }
    return resolve(r)
  })
}

// start server
function start (opts = {}) {
  if (opts.namespace) pgPrefix = `${opts.namespace.toLowerCase()}_`
  if (opts.maxBodyBytes) maxBodyBytes = opts.maxBodyBytes
  if (opts.jsonwebtoken) {
    jsonwebtoken = require('jsonwebtoken')
    jwtOpts = opts.jsonwebtoken
  }
  if (opts.logger) logger = opts.logger
  if (opts.base) {
    base = `/${opts.base}/`.replace(/\/+/g, '/')
    baseChars = base.length
  }
  if (opts.getIp) getIp = true
  if (opts.noCache) noCache = true
  if (opts.setScrudHeader) setScrudHeader = true
  if (opts.authTrans) authTrans = opts.authTrans
  if (opts.gzipThreshold) gzipThreshold = opts.gzipThreshold
  if (Array.isArray(opts.allowOrigins)) {
    opts.allowOrigins.forEach((k) => { allowOrigins[k] = true })
  }
  return new Promise((resolve, reject) => {
    let server = http.createServer(handleRequest)
    server.setTimeout(opts.timeout || defaultTimeout)
    server.listen(opts.port || port)
    if (opts.postgres) pgPool = new (require('pg')).Pool(opts.postgres)
    return resolve(server)
  })
}

// request handler
function handleRequest (req, res) {
  res.setHeader('Content-Type', 'application/json; charset=utf-8')
  let headers = req.headers || {}
  let origin = headers['origin']
  if (origin) {
    if (!allowOrigins[origin]) return rejectPreflight(res, origin)
    res.setHeader('Access-Control-Allow-Origin', origin)
  }
  if (req.method === 'OPTIONS' && headers['access-control-request-method']) {
    return ackPreflight(res, origin, headers['access-control-request-headers'])
  }
  let { name, action, id, params } = parseUrl(req)
  let resource = resources[name]
  if (!resource || !action) return fourOhFour(res)
  res.useGzip = (headers['accept-encoding'] || '').indexOf('gzip') !== -1
  if (setScrudHeader) res.setHeader('SCRUD', `${name}:${action}`)
  if (checkId[action]) req.id = id
  req.params = params
  if (getIp) {
    let connection = req.connection || {}
    req.params.ip = headers['x-forwarded-for'] || connection.remoteAddress
  }
  req.once('error', (err) => sendErr(res, err))
  let callHandler = () => {
    if (!hasBody[action]) return actionHandler(req, res, name, action)
    return bodyParse(req).then((body) => {
      req.params = Object.assign(body, req.params)
      return actionHandler(req, res, name, action)
    }).catch((e) => sendErr(res, e))
  }
  let noAuth = !jwtOpts || (resource.skipAuth && resource.skipAuth[action])
  if (noAuth) return callHandler()
  let jwt = (headers.authorization || '').replace(/^Bearer\s/, '')
  authenticate(jwt).then((authData) => {
    req.auth = req.params.auth = authTrans ? authTrans(authData) : authData
    return callHandler()
  }).catch((err) => fourOhOne(res, err))
}

let sendCache = {}
function sendData (res, data = null) {
  if (res.headersSent) {
    logIt(new Error(`Can't send data after headers sent`), 'warn')
    return Promise.resolve()
  }
  let tmp = sendCache
  let canCache = !noCache && (!data || primitives[typeof data])
  let hasCache = canCache && data === tmp.data
  return new Promise((resolve, reject) => {
    let out = hasCache ? tmp.out : JSON.stringify({data, error: null})
    let len = hasCache ? tmp.len : Buffer.byteLength(out)
    if (canCache && !hasCache) sendCache = {data, out, len}
    res.statusCode = 200
    if (!res.useGzip || len < gzipThreshold) {
      res.end(out)
      return resolve()
    }
    res.setHeader('Content-Encoding', 'gzip')
    if (tmp.gzip) {
      res.end(tmp.gzip)
      return resolve()
    }
    zlib.gzip(Buffer.from(out), (err, zipd) => {
      if (err) return reject(sendErr(res, err))
      if (canCache && !hasCache) sendCache.gzip = zipd
      res.end(zipd)
      return resolve()
    })
  })
}

function sendErr (res, err, code = 500) {
  res.statusCode = code
  if (res.headersSent) {
    logIt(err || new Error(`Can't send error after headers sent`), 'warn')
    return Promise.resolve()
  } else {
    res.removeHeader('content-encoding')
  }
  if (!err) {
    res.end(JSON.stringify({data: null, error: 'Unspecified error'}))
    return Promise.resolve()
  }
  return new Promise((resolve, reject) => {
    logIt(err, 'fatal')
    err = err instanceof Error ? (err.message || err.name) : err.toString()
    res.end(JSON.stringify({data: null, error: err}))
    return resolve()
  })
}

function fourOhOne (res, err = new Error(`Unable to authenticate request`)) {
  return sendErr(res, err, 401)
}

function fourOhFour (res, err = new Error(`No match for requested route`)) {
  return sendErr(res, err, 404)
}

function ackPreflight (res, origin, allowHeaders) {
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE')
  if (allowHeaders) res.setHeader('Access-Control-Allow-Headers', allowHeaders)
  res.statusCode = 200
  res.end()
}

function rejectPreflight (res, origin) {
  res.setHeader('Origin', origin || '')
  res.statusCode = 403
  res.end(JSON.stringify({data: null, error: 'Origin not allowed'}))
}

function genToken (payload = {}) {
  let key = jwtOpts.secret || jwtOpts.privateKey
  let noOpts = () => new Error('Missing required jsonwebtoken opts')
  if (!jwtOpts || !key) return Promise.reject(noOpts())
  let opts = filterObj(jwtOpts, wlSign)
  return new Promise((resolve, reject) => {
    jsonwebtoken.sign(payload, key, opts, (err, token) => {
      return err ? reject(err) : resolve(token)
    })
  })
}

function authenticate (jwt) {
  let key = (jwtOpts || {}).secret || (jwtOpts || {}).publicKey
  if (!jwtOpts || !key) return Promise.resolve()
  return new Promise((resolve, reject) => {
    jsonwebtoken.verify(jwt, key, jwtOpts, (err, d = {}) => {
      return err ? reject(err) : resolve(d)
    })
  })
}

// helper: handle all resource helpers
function pgActions (resource, action, req) {
  let {id, params} = req
  if (checkId[action]) {
    if (!id && id !== 0) return Promise.reject(new Error('No id passed'))
    if (action === 'read') params.id_array = [id]
    params.id = id
  }
  let firstRecord = (d) => Promise.resolve(d[0])
  let first = {create: true, read: true, update: true}
  if (action === 'search') {
    Object.keys(params).forEach((k) => {
      if (!Array.isArray(params[k])) return
      params[`${k}_array`] = params[k]
    })
  }
  let funcName = `${pgPrefix}${resource}_${action}`
  if (!first[action]) return callPgFunc(funcName, params, req)
  return callPgFunc(funcName, params, req).then(firstRecord)
}

// default handler for all resource methods
function actionHandler (req, res, name, action, skipRes) {
  let rsrc = resources[name]
  res = res || dummyRes
  let bq = rsrc.beforeQuery || {} // (req, res)
  if (typeof bq !== 'function') bq = bq[action]
  let act = () => rsrc[action]
      ? rsrc[action](req, res, name, action, skipRes)
      : handlers[action](name, req)
  if (rsrc[action]) return bq ? bq(req, res).then(act) : act()
  let bs = rsrc.beforeSend || {} // (req, res, data)
  if (typeof bs !== 'function') bs = bs[action]
  let send = (d) => skipRes ? Promise.resolve(d) : sendData(res, d)
  let finish = (d) => bs ? bs(req, res, d).then(send) : send(d)
  let run = () => bq ? bq(req, res).then(act).then(finish) : act().then(finish)
  let handleErr = (e) => skipRes ? Promise.reject(e) : sendErr(res, e)
  return run().catch(handleErr)
}
<commit_msg>Only call Buffer.byteLength if strlen*2 > limit<commit_after>'use strict'

// setup
const http = require('http')
const tinyParams = require('tiny-params')
const zlib = require('zlib')
const port = process.env.PORT || process.env.port || 8091
const defaultTimeout = 120000
const checkId = {read: true, update: true, delete: true}
const primitives = {string: true, number: true, boolean: true}
const noop = () => {}
const dummyRes = {
  addTrailers: noop,
  end: noop,
  getHeader: noop,
  getHeaderNames: noop,
  getHeaders: noop,
  hasHeader: noop,
  removeHeader: noop,
  setHeader: noop,
  setTimeout: noop,
  write: noop,
  writeContinue: noop,
  writeHead: noop
}
const scrud = {
  GET: 'search',
  'GET?': 'search',
  POST: 'create',
  'POST/': 'create',
  'GET/': 'read',
  'PUT/': 'update',
  'DELETE/': 'delete'
}
const hasBody = {create: true, update: true}
const wlSign = [
  'algorithm',
  'expiresIn',
  'notBefore',
  'audience',
  'issuer',
  'jwtid',
  'subject',
  'noTimestamp',
  'header'
]

// globals
let jsonwebtoken
let logger
let pgPool
let jwtOpts
let authTrans
let pgPrefix = ''
let base = '/'
let baseChars = base.length
let maxBodyBytes = 1e6
let resources = {}
let allowOrigins = {}
let gzipThreshold = 1000
let getIp
let setScrudHeader
let noCache

// local helpers
const logIt = (e, level = 'fatal') => {
  typeof logger === 'function' ? logger(e, level) : console.log(e)
}

let lastParsed = {}
const parseUrl = (req) => {
  let tmp = lastParsed
  let sig = `${req.method}${req.url}`
  if (tmp.sig === sig) return tmp.data
  let url = decodeURIComponent(req.url).slice(baseChars)
  let sIdx = url.indexOf('/')
  let qIdx = url.indexOf('?')
  let modIdx = (sIdx === -1 || (qIdx !== -1 && sIdx > qIdx)) ? qIdx : sIdx
  let lastIdx = url.length - 1
  let id
  if (sIdx === modIdx) {
    let postMod = url.slice(sIdx + 1)
    if (postMod) {
      let nextMod = postMod.indexOf('/')
      if (nextMod === -1) nextMod = postMod.indexOf('?')
      id = nextMod === -1 ? postMod : postMod.slice(0, nextMod)
    }
  }
  let noMod = modIdx === -1
  let name = noMod ? url : url.slice(0, modIdx)
  let modifier = noMod || modIdx === lastIdx ? '' : url.charAt(modIdx)
  let action = scrud[`${req.method}${modifier}`]
  let params = tinyParams(url)
  let data = { url, name, action, id, params }
  lastParsed = { sig, data }
  return data
}

const callPgFunc = (name, params, req) => {
  let q = `SELECT * FROM ${name}($1);`
  if (!pgPool) return Promise.reject(new Error('No database configured'))
  return pgPool.connect().then((client) => {
    let close = () => { if (client && client.end) client.end().catch(() => {}) }
    if (req && req.on) req.once('close', close)
    return client.query(q, [params]).then((data) => {
      if (req && req.removeListener) req.removeListener('close', close)
      client.release()
      return Promise.resolve((data.rows[0] || {})[name] || [])
    }).catch((err) => {
      if (req && req.removeListener) req.removeListener('close', close)
      client.release()
      return Promise.reject(err)
    })
  }).catch((err) => {
    try {
      err.meta = err.meta || {}
      err.meta.pgFunction = name
      let errObj = JSON.parse(err.message)
      err.message = errObj.error ? errObj.error : errObj
    } catch (ex) {}
    return Promise.reject(err)
  })
}

const bodyParse = (req) => new Promise((resolve, reject) => {
  let body = ''
  req.on('data', (d) => {
    body += d.toString()
    if (body.length > maxBodyBytes) return reject(new Error('Body too large'))
  })
  let parse = () => {
    try { resolve(body ? JSON.parse(body) : {}) } catch (ex) { resolve({}) }
  }
  req.on('end', parse)
})

const filterObj = (obj, ary) => {
  let base = {}
  ary.forEach((o) => { if (o in obj) base[o] = obj[o] })
  return base
}

// database action handlers
const handlers = {}
const find = handlers.read = (rsrc, req) => pgActions(rsrc, 'read', req)
const findAll = handlers.search = (rsrc, req) => pgActions(rsrc, 'search', req)
const create = handlers.create = (rsrc, req) => pgActions(rsrc, 'create', req)
const save = handlers.update = (rsrc, req) => pgActions(rsrc, 'update', req)
const destroy = handlers.delete = (rsrc, req) => pgActions(rsrc, 'delete', req)

// exports
module.exports = {
  register,
  start,
  sendData,
  sendErr,
  logIt,
  fourOhOne,
  fourOhFour,
  genToken,
  authenticate,
  find,
  findAll,
  insert: create,
  save,
  destroy,
  callPgFunc,
  read: (rsrc, req) => actionHandler(req, null, rsrc, 'read', true),
  create: (rsrc, req) => actionHandler(req, null, rsrc, 'create', true),
  search: (rsrc, req) => actionHandler(req, null, rsrc, 'search', true),
  update: (rsrc, req) => actionHandler(req, null, rsrc, 'update', true),
  delete: (rsrc, req) => actionHandler(req, null, rsrc, 'delete', true)
}

// register resource
function register (name, opts = {}) {
  if (!name) return Promise.reject(new Error(`No name specified in register`))
  return new Promise((resolve, reject) => {
    let r = resources[name] = Object.assign({}, opts, {name})
    if (Array.isArray(r.skipAuth)) {
      let skippers = {}
      r.skipAuth.forEach((a) => { skippers[a] = true })
      r.skipAuth = skippers
    }
    return resolve(r)
  })
}

// start server
function start (opts = {}) {
  if (opts.namespace) pgPrefix = `${opts.namespace.toLowerCase()}_`
  if (opts.maxBodyBytes) maxBodyBytes = opts.maxBodyBytes
  if (opts.jsonwebtoken) {
    jsonwebtoken = require('jsonwebtoken')
    jwtOpts = opts.jsonwebtoken
  }
  if (opts.logger) logger = opts.logger
  if (opts.base) {
    base = `/${opts.base}/`.replace(/\/+/g, '/')
    baseChars = base.length
  }
  if (opts.getIp) getIp = true
  if (opts.noCache) noCache = true
  if (opts.setScrudHeader) setScrudHeader = true
  if (opts.authTrans) authTrans = opts.authTrans
  if (opts.gzipThreshold) gzipThreshold = opts.gzipThreshold
  if (Array.isArray(opts.allowOrigins)) {
    opts.allowOrigins.forEach((k) => { allowOrigins[k] = true })
  }
  return new Promise((resolve, reject) => {
    let server = http.createServer(handleRequest)
    server.setTimeout(opts.timeout || defaultTimeout)
    server.listen(opts.port || port)
    if (opts.postgres) pgPool = new (require('pg')).Pool(opts.postgres)
    return resolve(server)
  })
}

// request handler
function handleRequest (req, res) {
  res.setHeader('Content-Type', 'application/json; charset=utf-8')
  let headers = req.headers || {}
  let origin = headers['origin']
  if (origin) {
    if (!allowOrigins[origin]) return rejectPreflight(res, origin)
    res.setHeader('Access-Control-Allow-Origin', origin)
  }
  if (req.method === 'OPTIONS' && headers['access-control-request-method']) {
    return ackPreflight(res, origin, headers['access-control-request-headers'])
  }
  let { name, action, id, params } = parseUrl(req)
  let resource = resources[name]
  if (!resource || !action) return fourOhFour(res)
  res.useGzip = (headers['accept-encoding'] || '').indexOf('gzip') !== -1
  if (setScrudHeader) res.setHeader('SCRUD', `${name}:${action}`)
  if (checkId[action]) req.id = id
  req.params = params
  if (getIp) {
    let connection = req.connection || {}
    req.params.ip = headers['x-forwarded-for'] || connection.remoteAddress
  }
  req.once('error', (err) => sendErr(res, err))
  let callHandler = () => {
    if (!hasBody[action]) return actionHandler(req, res, name, action)
    return bodyParse(req).then((body) => {
      req.params = Object.assign(body, req.params)
      return actionHandler(req, res, name, action)
    }).catch((e) => sendErr(res, e))
  }
  let noAuth = !jwtOpts || (resource.skipAuth && resource.skipAuth[action])
  if (noAuth) return callHandler()
  let jwt = (headers.authorization || '').replace(/^Bearer\s/, '')
  authenticate(jwt).then((authData) => {
    req.auth = req.params.auth = authTrans ? authTrans(authData) : authData
    return callHandler()
  }).catch((err) => fourOhOne(res, err))
}

let sendCache = {}
function sendData (res, data = null) {
  if (res.headersSent) {
    logIt(new Error(`Can't send data after headers sent`), 'warn')
    return Promise.resolve()
  }
  let tmp = sendCache
  let canCache = !noCache && (!data || primitives[typeof data])
  let hasCache = canCache && data === tmp.data
  return new Promise((resolve, reject) => {
    let { out, big } = tmp
    if (!hasCache) {
      out = JSON.stringify({data, error: null})
      let dblLen = out.length * 2 + 1
      big = !(dblLen < gzipThreshold || Buffer.byteLength(out) < gzipThreshold)
    }
    if (canCache && !hasCache) sendCache = {data, out, big}
    res.statusCode = 200
    if (!res.useGzip || !big) {
      res.end(out)
      return resolve()
    }
    res.setHeader('Content-Encoding', 'gzip')
    if (tmp.gzip) {
      res.end(tmp.gzip)
      return resolve()
    }
    zlib.gzip(Buffer.from(out), (err, zipd) => {
      if (err) return reject(sendErr(res, err))
      if (canCache && !hasCache) sendCache.gzip = zipd
      res.end(zipd)
      return resolve()
    })
  })
}

function sendErr (res, err, code = 500) {
  res.statusCode = code
  if (res.headersSent) {
    logIt(err || new Error(`Can't send error after headers sent`), 'warn')
    return Promise.resolve()
  } else {
    res.removeHeader('content-encoding')
  }
  if (!err) {
    res.end(JSON.stringify({data: null, error: 'Unspecified error'}))
    return Promise.resolve()
  }
  return new Promise((resolve, reject) => {
    logIt(err, 'fatal')
    err = err instanceof Error ? (err.message || err.name) : err.toString()
    res.end(JSON.stringify({data: null, error: err}))
    return resolve()
  })
}

function fourOhOne (res, err = new Error(`Unable to authenticate request`)) {
  return sendErr(res, err, 401)
}

function fourOhFour (res, err = new Error(`No match for requested route`)) {
  return sendErr(res, err, 404)
}

function ackPreflight (res, origin, allowHeaders) {
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE')
  if (allowHeaders) res.setHeader('Access-Control-Allow-Headers', allowHeaders)
  res.statusCode = 200
  res.end()
}

function rejectPreflight (res, origin) {
  res.setHeader('Origin', origin || '')
  res.statusCode = 403
  res.end(JSON.stringify({data: null, error: 'Origin not allowed'}))
}

function genToken (payload = {}) {
  let key = jwtOpts.secret || jwtOpts.privateKey
  let noOpts = () => new Error('Missing required jsonwebtoken opts')
  if (!jwtOpts || !key) return Promise.reject(noOpts())
  let opts = filterObj(jwtOpts, wlSign)
  return new Promise((resolve, reject) => {
    jsonwebtoken.sign(payload, key, opts, (err, token) => {
      return err ? reject(err) : resolve(token)
    })
  })
}

function authenticate (jwt) {
  let key = (jwtOpts || {}).secret || (jwtOpts || {}).publicKey
  if (!jwtOpts || !key) return Promise.resolve()
  return new Promise((resolve, reject) => {
    jsonwebtoken.verify(jwt, key, jwtOpts, (err, d = {}) => {
      return err ? reject(err) : resolve(d)
    })
  })
}

// helper: handle all resource helpers
function pgActions (resource, action, req) {
  let {id, params} = req
  if (checkId[action]) {
    if (!id && id !== 0) return Promise.reject(new Error('No id passed'))
    if (action === 'read') params.id_array = [id]
    params.id = id
  }
  let firstRecord = (d) => Promise.resolve(d[0])
  let first = {create: true, read: true, update: true}
  if (action === 'search') {
    Object.keys(params).forEach((k) => {
      if (!Array.isArray(params[k])) return
      params[`${k}_array`] = params[k]
    })
  }
  let funcName = `${pgPrefix}${resource}_${action}`
  if (!first[action]) return callPgFunc(funcName, params, req)
  return callPgFunc(funcName, params, req).then(firstRecord)
}

// default handler for all resource methods
function actionHandler (req, res, name, action, skipRes) {
  let rsrc = resources[name]
  res = res || dummyRes
  let bq = rsrc.beforeQuery || {} // (req, res)
  if (typeof bq !== 'function') bq = bq[action]
  let act = () => rsrc[action]
      ? rsrc[action](req, res, name, action, skipRes)
      : handlers[action](name, req)
  if (rsrc[action]) return bq ? bq(req, res).then(act) : act()
  let bs = rsrc.beforeSend || {} // (req, res, data)
  if (typeof bs !== 'function') bs = bs[action]
  let send = (d) => skipRes ? Promise.resolve(d) : sendData(res, d)
  let finish = (d) => bs ? bs(req, res, d).then(send) : send(d)
  let run = () => bq ? bq(req, res).then(act).then(finish) : act().then(finish)
  let handleErr = (e) => skipRes ? Promise.reject(e) : sendErr(res, e)
  return run().catch(handleErr)
}
<|endoftext|>


<commit_before>const cool = require('cool-ascii-faces')
const funky_intro = require('funny-words')
const express = require('express')
const emoji = require('emoji-cool');

const app = express()
app.engine('html', require('ejs').renderFile); app.set('view engine', 'html');

app.get('/', function(req, res) {
  res.send('Hello Mars!
 Days until next year:' + Math.round(( new Date().setFullYear(new Date().getFullYear() + 1, 0, 1) - new Date() )/(1000 * 60 * 60 * 24)));
})

app.get('/source', function(req, res){ res.redirect('https://github.com/jkup/one-line/');
  console.log("Here you can contribute to this project!")})
app.get('/hangUnlessSomeoneElseReturnsFromHere', function(req, res) {
  res.redirect('https://www.google.com/?tbm=isch&q=when+will+my+husband+return+from+war');
}); // To make this route work, someone else needs to return a response
app.get('/useless', function(req, res){ res.redirect('http://www.theuselessweb.com/'); })
app.get('/findMoreHacktoberfestLabelledIssues', function(req, res){ res.redirect('http://issuehub.io/?label[]=hacktoberfest'); })
app.get('/randNum/cake', function(req, res) { res.json({num: 44}); });
app.get('/hello/:name', function(req, res) { res.json({"hello" : req.params.name}); })
app.get('/goodbye/:name', function(req, res) { res.json({"goodbye" : req.params.name}); })
app.get('/admin', function(req, res) { res.json("ACCESS DENIED"); })
app.get('/haveSomeFunCats', function(req, res) { res.redirect('http://www.catgifpage.com/'); });
app.get('/leftpad/:val/:len', function(req, res) { res.json({ 'response': (+req.params.len > 0) ? (Array(+req.params.len-req.params.val.length).fill(0).join('') + req.params.val).slice(-req.params.res) : req.params.val })});
app.get('/add/:x/:y', function(req, res) { res.json({x: req.params.x, y: req.params.y, sum: (Number(req.params.x) + Number(req.params.y))}); });
app.get('/subtract/:x/:y', function(req, res) { res.json({x: req.params.x, y: req.params.y, difference: Number(req.params.x) - Number(req.params.y)}); });
app.get('/fizzbuzz', function(req, res) { for(var i = 1, arr = []; i<51 ;i++) { arr.push(((i % 3 ? '' : 'fizz') + (i % 5 ? '' : 'buzz')) || i)}; res.json(arr); });
app.get('/meaningOfLife', function(req, res) { res.json({ 1337: 42 }) });
app.get('/hacktoberfest-countdown', function (req, res) {
	res.send('Hacktoberfest end is in ' + new Date(new Date(2016, 09, 31).getTime() - Date.now()).getDate() + ' days.');
	console.log("Better get those PRs in soon, buddy!");
});
app.get('/currency/:from/:to', function(req, res) { res.redirect('http://api.fixer.io/latest?base=' + req.params.from + '&symbols='+ req.params.to); });
app.get('/duckduckgo/:search', function(req, res) { res.redirect('https://duckduckgo.com/?q=' + req.params.search)})
app.get('/beAwesome', function(req, res){ res.redirect('https://github.com/sindresorhus/awesome'); })
app.get('/motivateMe', function(req, res) {res.redirect('https://www.brainyquote.com/quotes/topics/topic_motivational.html')});
app.get('/cloud2butt/:text', function(req, res) {res.send(req.params.text.toLowerCase().split('cloud').join('butt'));});
app.get('/hacktoberfest-checker', function(req, res){ res.redirect('https://hacktoberfestchecker.herokuapp.com/'); })
app.get('/findme', function(req,res){ res.redirect('https://github.com/mbj36');})

app.use(function (req, res, next) { res.status(404); res.render('404'); });

console.log(emoji)
console.log(cool())
console.log(funky_intro("Welcome to jkup, a Node.js app where you can only contribute one line at a time. Inspired by jkup himself."))

var server = app.listen(process.env.PORT || 3000, function () {  // now we can close it if needed
  console.log("Server started");
});
app.use(express.static('public'));
console.log("I like this idea. :p");
<commit_msg>Add semicolon because semicolons need love and company.<commit_after>const cool = require('cool-ascii-faces')
const funky_intro = require('funny-words')
const express = require('express')
const emoji = require('emoji-cool');

const app = express();
app.engine('html', require('ejs').renderFile); app.set('view engine', 'html');

app.get('/', function(req, res) {
  res.send('Hello Mars!
 Days until next year:' + Math.round(( new Date().setFullYear(new Date().getFullYear() + 1, 0, 1) - new Date() )/(1000 * 60 * 60 * 24)));
})

app.get('/source', function(req, res){ res.redirect('https://github.com/jkup/one-line/');
  console.log("Here you can contribute to this project!")})
app.get('/hangUnlessSomeoneElseReturnsFromHere', function(req, res) {
  res.redirect('https://www.google.com/?tbm=isch&q=when+will+my+husband+return+from+war');
}); // To make this route work, someone else needs to return a response
app.get('/useless', function(req, res){ res.redirect('http://www.theuselessweb.com/'); })
app.get('/findMoreHacktoberfestLabelledIssues', function(req, res){ res.redirect('http://issuehub.io/?label[]=hacktoberfest'); })
app.get('/randNum/cake', function(req, res) { res.json({num: 44}); });
app.get('/hello/:name', function(req, res) { res.json({"hello" : req.params.name}); })
app.get('/goodbye/:name', function(req, res) { res.json({"goodbye" : req.params.name}); })
app.get('/admin', function(req, res) { res.json("ACCESS DENIED"); })
app.get('/haveSomeFunCats', function(req, res) { res.redirect('http://www.catgifpage.com/'); });
app.get('/leftpad/:val/:len', function(req, res) { res.json({ 'response': (+req.params.len > 0) ? (Array(+req.params.len-req.params.val.length).fill(0).join('') + req.params.val).slice(-req.params.res) : req.params.val })});
app.get('/add/:x/:y', function(req, res) { res.json({x: req.params.x, y: req.params.y, sum: (Number(req.params.x) + Number(req.params.y))}); });
app.get('/subtract/:x/:y', function(req, res) { res.json({x: req.params.x, y: req.params.y, difference: Number(req.params.x) - Number(req.params.y)}); });
app.get('/fizzbuzz', function(req, res) { for(var i = 1, arr = []; i<51 ;i++) { arr.push(((i % 3 ? '' : 'fizz') + (i % 5 ? '' : 'buzz')) || i)}; res.json(arr); });
app.get('/meaningOfLife', function(req, res) { res.json({ 1337: 42 }) });
app.get('/hacktoberfest-countdown', function (req, res) {
	res.send('Hacktoberfest end is in ' + new Date(new Date(2016, 09, 31).getTime() - Date.now()).getDate() + ' days.');
	console.log("Better get those PRs in soon, buddy!");
});
app.get('/currency/:from/:to', function(req, res) { res.redirect('http://api.fixer.io/latest?base=' + req.params.from + '&symbols='+ req.params.to); });
app.get('/duckduckgo/:search', function(req, res) { res.redirect('https://duckduckgo.com/?q=' + req.params.search)})
app.get('/beAwesome', function(req, res){ res.redirect('https://github.com/sindresorhus/awesome'); })
app.get('/motivateMe', function(req, res) {res.redirect('https://www.brainyquote.com/quotes/topics/topic_motivational.html')});
app.get('/cloud2butt/:text', function(req, res) {res.send(req.params.text.toLowerCase().split('cloud').join('butt'));});
app.get('/hacktoberfest-checker', function(req, res){ res.redirect('https://hacktoberfestchecker.herokuapp.com/'); })
app.get('/findme', function(req,res){ res.redirect('https://github.com/mbj36');})

app.use(function (req, res, next) { res.status(404); res.render('404'); });

console.log(emoji)
console.log(cool())
console.log(funky_intro("Welcome to jkup, a Node.js app where you can only contribute one line at a time. Inspired by jkup himself."))

var server = app.listen(process.env.PORT || 3000, function () {  // now we can close it if needed
  console.log("Server started");
});
app.use(express.static('public'));
console.log("I like this idea. :p");
<|endoftext|>


<commit_before>'use strict';

var chalk = require('chalk');
var moment = require('moment');
var open = require('open');
var editor = process.env.EDITOR;
var spawn = require('child_process').spawn;
var path = require('path');
var inquirer = require('inquirer');

hexo.extend.console.register('edit', 'Edit a post or draft with your favorite $EDITOR', {
  desc: 'Edit a post or draft with your favorite $EDITOR',
  usage: '[title] [-f|--folder] [-g|--gui]',
  arguments: [
    {name: 'title', desc: '(Part of) the title of a post. If more posts match this regex, a menu will be called.'},
  ],
  options: [
    {name: '-a, --after', desc: 'Only consider posts after this date (MM-DD-YYYY)'},
    {name: '-b, --before', desc: 'Only consider posts before this date (MM-DD-YYYY)'},
    {name: '-c, --category', desc: 'Category to filter on.'},
    {name: '-d, --draft', desc: 'Only consider drafts'},
    {name: '-f, --folder', desc: 'Name of subfolder to filter on.'},
    {name: '-g, --gui', desc: 'Open file with associated GUI text editor. Default is to open in terminal using $EDITOR. If no $EDITOR is found, it will fall back to gui mode.'},
    {name: '-t, --tag', desc: 'Tag to filter on.'},
  ],
}, edit);

// extend `hexo new` to open newly created post/draft
hexo.on('new', function(post) {
  var edit;
  var content = post.content;

  // only open a new empty post -- prevent opening on publishing an already written one
  if (content.substr(content.indexOf('\n---\n')).length == 5) {
    if (!editor) {
      open(file);
    } else {
      edit = spawn(editor, [post.path], {stdio: 'inherit'});
      edit.on('exit', process.exit);
    }
  }
});

function edit(args) {

  var title = args._.join(' ').replace(/ /g, '-') || '';
  var folder = args.f || args.folder || '';
  var draft = args.d || args.draft || '';
  var tag = args.t || args.tag || '';
  var cat = args.c || args.category || '';
  var before = args.b || args.before || '';
  before = before.replace(/\//g, '-');
  var after = args.a || args.after || '';
  after = after.replace(/\//g, '-');

  var gui = args.g || args.gui || !editor;

  // load in the posts before processing them
  hexo.load().then(function() {
    var sourceDir = hexo.source_dir;
    var searchDir = sourceDir;
    var editor = process.env.EDITOR;

    var selected;
    var entries;

    Promise.resolve(hexo.locals.get('posts').sort('-date').toArray()).then(function(posts) {
      var filtered = posts.slice();

      // allow omission of leading underscore or trailing s for the common _drafts and _posts folders
      if (/post|draft/.test(folder)) {
        folder = (/^_/.test(folder)) ? folder : '_' + folder;
        folder = (/s$/.test(folder)) ? folder : folder + 's';
      }

      filtered = (draft) ? filterDrafts(filtered) : filtered;

      filtered = (folder) ? filterFolder(filtered) : filtered;

      filtered = (title) ? filterTitle(filtered) : filtered;

      filtered = (tag) ? filterTag(filtered) : filtered;

      filtered = (cat) ? filterCategory(filtered) : filtered;

      filtered = (before) ? filterBefore(filtered) : filtered;

      filtered = (after) ? filterAfter(filtered) : filtered;

      if (filtered.length == 0) {
        console.log('Sorry, no filtered matched your query. Exiting.');
        process.exit();
      } else if (filtered.length == 1) {

        // no menu necessary if there is only one matching file
        selected = path.join(searchDir, filtered[0].source);
        openFile(selected);
      } else {

        // get a list of entries to put in the menu -- slugs are easy because it shows the subfolder and can easily be put back together with the searchDir to open it
        entries = filtered.map(function(post) {
          var entry = '';
          var folder = post.source.substr(0, post.source.lastIndexOf(path.sep));
          if (post.published) {
            entry = ['[', chalk.gray(post.date.format('MM-DD-YYYY')), '] ', post.title, ' (', chalk.green(folder), ')'].join('');
          } else {
            entry = ['[', chalk.yellow.bgBlack('draft'), '] ', post.title].join('');
          }

          return entry;
        });

        inquirer.prompt([
          {
            type: 'list',
            name: 'file',
            message: 'Select the file you wish to edit.',
            choices: entries,
          },
        ], function(answer) {
          var pos = entries.indexOf(answer.file);
          selected = path.join(sourceDir, filtered[pos].source);
          openFile(selected);
        });
      }

    });

    // filter the posts using a subfolder if supplied
    function filterFolder(posts) {
      var reFolder = new RegExp(folder);
      return posts.filter(function(post) {
        return reFolder.test(post.source.substr(0, post.source.lastIndexOf(path.sep)));
      });
    }

    // filter the posts using a tag if supplied
    function filterTag(posts) {
      var reTag = new RegExp(tag);
      return posts.filter(function(post) {
        return post.tags.data.some(function(postTag) {
          return reTag.test(postTag.name);
        });
      });
    }

    // filter the posts using a category if supplied
    function filterCategory(posts) {
      var reCat = new RegExp(cat);
      return posts.filter(function(post) {
        return post.categories.data.some(function(postCat) {
          return reCat.test(postCat.name);
        });
      });
    }

    // filter out all non-published posts
    function filterDrafts(posts) {
      return posts.filter(function(post) {
        return !post.published;
      });
    }

    // filter the posts using a before date if supplied
    function filterBefore(posts) {
      before = moment(before, 'MM-DD-YYYY', true);
      if (!before.isValid()) {
        console.log(chalk.red('Before date is not valid (expecting `MM-DD-YYYY`), ignoring argument.'));
        return posts;
      }

      return posts.filter(function(post) {
        return moment(post.date).isBefore(before);
      });
    }

    // filter the posts using an after date if supplied
    function filterAfter(posts) {
      after = moment(after, 'MM-DD-YYYY', true);
      if (!after.isValid()) {
        console.log(chalk.red('After date is not valid (expecting `MM-DD-YYYY`), ignoring argument.'));
        return posts;
      }

      return posts.filter(function(post) {
        return moment(post.date).isAfter(after);
      });
    }

    // filter the posts with the supplied regular expression -- uses the slug to avoid capitalization and spacing shenanigans
    function filterTitle(posts) {
      var reTitle = new RegExp(title, 'i');
      return posts.filter(function(post) {
        return reTitle.test(post.slug);
      });
    }

    // spawn process and open with associated gui or terminal editor
    function openFile(file) {
      if (!editor || gui) {
        open(file);
      } else {
        var edit = spawn(editor, [file], {stdio: 'inherit'});
        edit.on('exit', process.exit);
      }
    }

  });
};
<commit_msg>exlude drafts when filtering on date<commit_after>'use strict';

var chalk = require('chalk');
var moment = require('moment');
var open = require('open');
var editor = process.env.EDITOR;
var spawn = require('child_process').spawn;
var path = require('path');
var inquirer = require('inquirer');

hexo.extend.console.register('edit', 'Edit a post or draft with your favorite $EDITOR', {
  desc: 'Edit a post or draft with your favorite $EDITOR',
  usage: '[title] [-f|--folder] [-g|--gui]',
  arguments: [
    {name: 'title', desc: '(Part of) the title of a post. If more posts match this regex, a menu will be called.'},
  ],
  options: [
    {name: '-a, --after', desc: 'Only consider posts after this date (MM-DD-YYYY)'},
    {name: '-b, --before', desc: 'Only consider posts before this date (MM-DD-YYYY)'},
    {name: '-c, --category', desc: 'Category to filter on.'},
    {name: '-d, --draft', desc: 'Only consider drafts'},
    {name: '-f, --folder', desc: 'Name of subfolder to filter on.'},
    {name: '-g, --gui', desc: 'Open file with associated GUI text editor. Default is to open in terminal using $EDITOR. If no $EDITOR is found, it will fall back to gui mode.'},
    {name: '-t, --tag', desc: 'Tag to filter on.'},
  ],
}, edit);

// extend `hexo new` to open newly created post/draft
hexo.on('new', function(post) {
  var edit;
  var content = post.content;

  // only open a new empty post -- prevent opening on publishing an already written one
  if (content.substr(content.indexOf('\n---\n')).length == 5) {
    if (!editor) {
      open(file);
    } else {
      edit = spawn(editor, [post.path], {stdio: 'inherit'});
      edit.on('exit', process.exit);
    }
  }
});

function edit(args) {

  var title = args._.join(' ').replace(/ /g, '-') || '';
  var folder = args.f || args.folder || '';
  var draft = args.d || args.draft || '';
  var tag = args.t || args.tag || '';
  var cat = args.c || args.category || '';
  var before = args.b || args.before || '';
  before = before.replace(/\//g, '-');
  var after = args.a || args.after || '';
  after = after.replace(/\//g, '-');

  var gui = args.g || args.gui || !editor;

  // load in the posts before processing them
  hexo.load().then(function() {
    var sourceDir = hexo.source_dir;
    var searchDir = sourceDir;
    var editor = process.env.EDITOR;

    var selected;
    var entries;

    Promise.resolve(hexo.locals.get('posts').sort('-date').toArray()).then(function(posts) {
      var filtered = posts.slice();

      // allow omission of leading underscore or trailing s for the common _drafts and _posts folders
      if (/post|draft/.test(folder)) {
        folder = (/^_/.test(folder)) ? folder : '_' + folder;
        folder = (/s$/.test(folder)) ? folder : folder + 's';
      }

      filtered = (draft) ? filterDrafts(filtered) : filtered;

      filtered = (folder) ? filterFolder(filtered) : filtered;

      filtered = (title) ? filterTitle(filtered) : filtered;

      filtered = (tag) ? filterTag(filtered) : filtered;

      filtered = (cat) ? filterCategory(filtered) : filtered;

      filtered = (before) ? filterBefore(filtered) : filtered;

      filtered = (after) ? filterAfter(filtered) : filtered;

      if (filtered.length == 0) {
        console.log('Sorry, no filtered matched your query. Exiting.');
        process.exit();
      } else if (filtered.length == 1) {

        // no menu necessary if there is only one matching file
        selected = path.join(searchDir, filtered[0].source);
        openFile(selected);
      } else {

        // get a list of entries to put in the menu -- slugs are easy because it shows the subfolder and can easily be put back together with the searchDir to open it
        entries = filtered.map(function(post) {
          var entry = '';
          var folder = post.source.substr(0, post.source.lastIndexOf(path.sep));
          if (post.published) {
            entry = ['[', chalk.gray(post.date.format('MM-DD-YYYY')), '] ', post.title, ' (', chalk.green(folder), ')'].join('');
          } else {
            entry = ['[', chalk.yellow.bgBlack('draft'), '] ', post.title].join('');
          }

          return entry;
        });

        inquirer.prompt([
          {
            type: 'list',
            name: 'file',
            message: 'Select the file you wish to edit.',
            choices: entries,
          },
        ], function(answer) {
          var pos = entries.indexOf(answer.file);
          selected = path.join(sourceDir, filtered[pos].source);
          openFile(selected);
        });
      }

    });

    // filter the posts using a subfolder if supplied
    function filterFolder(posts) {
      var reFolder = new RegExp(folder);
      return posts.filter(function(post) {
        return reFolder.test(post.source.substr(0, post.source.lastIndexOf(path.sep)));
      });
    }

    // filter the posts using a tag if supplied
    function filterTag(posts) {
      var reTag = new RegExp(tag);
      return posts.filter(function(post) {
        return post.tags.data.some(function(postTag) {
          return reTag.test(postTag.name);
        });
      });
    }

    // filter the posts using a category if supplied
    function filterCategory(posts) {
      var reCat = new RegExp(cat);
      return posts.filter(function(post) {
        return post.categories.data.some(function(postCat) {
          return reCat.test(postCat.name);
        });
      });
    }

    // filter out all non-published posts
    function filterDrafts(posts) {
      return posts.filter(function(post) {
        return !post.published;
      });
    }

    // filter the posts using a before date if supplied
    // dates don't make much sense for drafts, so they will be excluded
    function filterBefore(posts) {
      before = moment(before, 'MM-DD-YYYY', true);
      if (!before.isValid()) {
        console.log(chalk.red('Before date is not valid (expecting `MM-DD-YYYY`), ignoring argument.'));
        return posts;
      }

      return posts.filter(function(post) {
        return post.published && moment(post.date).isBefore(before);
      });
    }

    // filter the posts using an after date if supplied
    // dates don't make much sense for drafts, so they will be excluded
    function filterAfter(posts) {
      after = moment(after, 'MM-DD-YYYY', true);
      if (!after.isValid()) {
        console.log(chalk.red('After date is not valid (expecting `MM-DD-YYYY`), ignoring argument.'));
        return posts;
      }

      return posts.filter(function(post) {
        return post.published && moment(post.date).isAfter(after);
      });
    }

    // filter the posts with the supplied regular expression -- uses the slug to avoid capitalization and spacing shenanigans
    function filterTitle(posts) {
      var reTitle = new RegExp(title, 'i');
      return posts.filter(function(post) {
        return reTitle.test(post.slug);
      });
    }

    // spawn process and open with associated gui or terminal editor
    function openFile(file) {
      if (!editor || gui) {
        open(file);
      } else {
        var edit = spawn(editor, [file], {stdio: 'inherit'});
        edit.on('exit', process.exit);
      }
    }

  });
};
<|endoftext|>


<commit_before>require('dotenv').load();

var express = require('express');
var app = express();
var http = require('http').Server(app);
var path = require('path');
var io = require('socket.io')(http);
var Twitter = require('twitter');
var havenondemand = require('havenondemand');
var async = require("async");
var RateLimiter = require('limiter').RateLimiter;

if (process.env.staging === 'true') {
  var staging = true
} else {
  var staging = false
}

// var hodClient = new havenondemand.HODClient(process.env.hpe_apikey, 'v1', staging);
var hodClient = new havenondemand.HODClient(process.env.hpe_apikey);

var twitterClient = new Twitter({
  consumer_key: process.env.consumer_key,
  consumer_secret: process.env.consumer_secret,
  access_token_key: process.env.access_token,
  access_token_secret: process.env.access_token_secret
});

port = process.env.PORT || 5000;
var limiter = new RateLimiter(2, 'second') //first parameter is max number of calls per second

app.use(express.static(path.join(__dirname, 'public')));
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

var candidateString = "HillaryClinton,realDonaldTrump"
var window1 = 10;

// Data used to store and calculate sentiment for candidates
// Each key is the candidate's Twitter handle which is checked after a Tweet is streamed in so we know which candidate we're talking about
var candidateNumbers = {
  "HillaryClinton": {
    averages: {newAvg: 0, oldAvg: 0},
    n: 0,
    nPositive: 0,
    nNegative: 0,
    nNeutral: 0,
    runningAverageWindow1: 0,
    nWindow1: 0,
    runningAverageWindow1Array: []
  },
  "realDonaldTrump": {
      averages: {newAvg: 0, oldAvg: 0},
      n: 0,
      nPositive: 0,
      nNegative: 0,
      nNeutral: 0,
      runningAverageWindow1: 0,
      nWindow1: 0,
      runningAverageWindow1Array: []
  }
}

var articleUpdateInterval = 60000*15; //15 minutes

// Object which stores the articles and concepts from these articles for each of the candidates
var candidateArticles = {
  "Hillary Clinton": {articles: [], concepts: []},
  "Donald Trump": {articles: [], concepts: []},
};

app.get("/", function(req, res) {
  res.render('index', {
    candidateArticles: candidateArticles,
    candidateConcepts: candidateArticles["Hillary Clinton"].concepts
  });
});

// Route for HTML of candidate articles
app.get("/candidatehtml", function(req, res) {
  var payload = candidateArticles
  res.status(200).send(payload)
})

// Route for mobile app HTML of candidate articles
app.get("/candidatehtml_mobile", function(req, res) {
  var payload ={candidates: [
      {
        name: "Hillary Clinton",
        articles: candidateArticles["Hillary Clinton"].articles
      },
      {
        name: "Donald Trump",
        articles: candidateArticles["Donald Trump"].articles
      },
    ]}
    res.status(200).send(payload)
})

// Route for the app explanation at the top
app.get("/whats_this", function(req, res) {
  res.status(200).sendFile(path.join(__dirname, 'views', 'whats_this.html'))
})

// Route for open source contribution explanation
app.get("/contribute", function(req, res) {
  res.status(200).sendFile(path.join(__dirname, 'views', 'contribute.html'))
})

// Route for third party developers to hit if they want the candidate sentiment data
app.get('/candidatedata', function(req, res) {
  // var payload = candidateNumbers
  var payload = {candidates: [
    {
      name: "HillaryClinton",
      averages: {newAvg: candidateNumbers["HillaryClinton"].averages.newAvg, oldAvg: candidateNumbers["HillaryClinton"].averages.oldAvg},
      n: candidateNumbers["HillaryClinton"].n,
      nPositive: candidateNumbers["HillaryClinton"].nPositive,
      nNegative: candidateNumbers["HillaryClinton"].nNegative,
      nNeutral: candidateNumbers["HillaryClinton"].nNeutral,
      runningAverageWindow1: candidateNumbers["HillaryClinton"].runningAverageWindow1,
      runningAverageWindow1Array: candidateNumbers["HillaryClinton"].runningAverageWindow1Array
    },
    {
      name: "realDonaldTrump",
      averages: {newAvg: candidateNumbers["realDonaldTrump"].averages.newAvg, oldAvg: candidateNumbers["realDonaldTrump"].averages.oldAvg},
      n: candidateNumbers["realDonaldTrump"].n,
      nPositive: candidateNumbers["realDonaldTrump"].nPositive,
      nNegative: candidateNumbers["realDonaldTrump"].nNegative,
      nNeutral: candidateNumbers["realDonaldTrump"].nNeutral,
      runningAverageWindow1: candidateNumbers["realDonaldTrump"].runningAverageWindow1,
      runningAverageWindow1Array: candidateNumbers["realDonaldTrump"].runningAverageWindow1Array
    },
  ]}
  res.status(200).send(payload)
})

http.listen(port, function(){
  console.log("Listening on port: "+port);
});

//Stream Tweets
twitterClient.stream('statuses/filter', {track: candidateString}, function(stream) {
  stream.on('data', function(tweet) {
    if (tweet.entities !== undefined) {
      var userMentions = tweet.entities.user_mentions;
      for (var i=0; i<userMentions.length; i++) {
        var screenName = userMentions[i].screen_name;
        if (candidateNumbers[screenName] !== undefined) {
          twitterStream(screenName, candidateNumbers[screenName], tweet)
        }
      }
    }
  });

  stream.on('disconnect', function (disconnectMessage) {
    console.log(disconnectMessage);
  });

  stream.on('error', function(error) {
    throw error;
  });
});

//Helper function to process a tweet for a particular candidate
// Accepts the candidate Twitter handle, an object of his or her sentiment data, and the tweet object returned from the Twitter Stream API
//Analyzes the sentiment, computes the average, computes the Instantaneous average (averageWindow1), maps to colors (no longer using in front end), emits all to the client through websockets and stores it in a Haven OnDemand Index
function twitterStream(candidate, candidateData, tweetObject) {
  var data = {text: tweetObject.text};
  limiter.removeTokens(1, function(err, remainingRequests) {
    console.log("Remaining requests per second: " + remainingRequests);
    hodClient.call('analyzesentiment', data, function(err, resp){
      // debugger;
      if (!err) {
        console.log(resp.body)
        candidateData.n += 1; //increase n by one
        candidateData.nWindow1 +=1 ; //increase by one
        var sentiment = resp.body.aggregate.sentiment;
        // var score = 10.0/3.0*(resp.body.aggregate.score*100.0)+50.0; //map from -15 to 15 to 0 to 100 ... y =10/3*x+50
        // var score = 50.0*(resp.body.aggregate.score)+50.0; //map from -1.0 to 1.0 to 0 to 100 ... y =50*x+50
        var score = 100.0*(resp.body.aggregate.score)+50.0; //map from -0.5 to 0.5 to 0 to 100 ... y =50*x+50
        if (score > 50) {
          candidateData.nPositive += 1;
        } else if(score < 50) {
          candidateData.nNegative += 1;
        } else {
          candidateData.nNeutral += 1;
        }
        //perform running averages window
        candidateData.runningAverageWindow1Array.push(score);
        if (candidateData.runningAverageWindow1Array.length > window1) { //if there is enough data points in the window
          candidateData.runningAverageWindow1Array.splice(0,1);
          candidateData.runningAverageWindow1 = calculateRunningAverageWindow(candidateData.runningAverageWindow1Array, window1)
        }
        //
        candidateData.averages = calculateRunningAverage(score, candidateData.n, candidateData.averages);
        rgbInstantaneous = mapColor(score);
        rgbAverage = mapColor(candidateData.averages.newAvg);
        console.log("------------------------------");
        console.log(tweetObject.text + " | " + sentiment + " | " + score);
        var tweetData = {candidate: candidate, tweet: tweetObject, positive: resp.body.positive, negative: resp.body.negative, aggregate: resp.body.aggregate, rgbInstantaneous: rgbInstantaneous, rgbAverage: rgbAverage, average: candidateData.averages.newAvg, averageWindow1: candidateData.runningAverageWindow1, n: candidateData.n, nNeutral: candidateData.nNeutral, nNegative: candidateData.nNegative, nPositive: candidateData.nPositive};
        io.emit('message', tweetData);
        var data2 = {
          index: 'ourfeelingsaboutpoliticiansa',
          json: JSON.stringify({
            document: [{
              title: candidate + candidateData.n,
              content: tweetObject.text,
              candidate: candidate, tweet: tweetObject, positive: resp.body.positive, negative: resp.body.negative, aggregate: resp.body.aggregate, rgbInstantaneous: rgbInstantaneous, rgbAverage: rgbAverage, average: candidateData.averages.newAvg, averageWindow1: candidateData.runningAverageWindow1, n: candidateData.n, nNeutral: candidateData.nNeutral, nNegative: candidateData.nNegative, nPositive: candidateData.nPositive,
              score: score,
              date: Date.now()
            }]
          })
        }
        if (process.env.add_text_index === 'true') {
          hodClient.call('addtotextindex', data2, function(err2, resp2, body2) {
            if (resp2) {
              if (resp2.body) {
                console.log(resp2.body)
              }
            }
          })
        }
      } else {
        console.log(err);
      }
    });
  });
}

// Functions for updating articles
updateCandidateArticles();
// setInterval(updateCandidateArticles(), articleUpdateInterval);

// Function to retrieve articles about each candidate. Results are stored in the object, candidateArticles, created at the top of this document
function updateCandidateArticles() {
  // Loop through each candidate and obtain articles pertaining to them from Haven OnDemand using Query Text Index API
  async.forEachOf(candidateArticles, function (value1, key1, callback1) {
    candidateArticles[key1].concepts = []; //delete the old concepts for the candidate
    var data1 = {text: key1, indexes: ['news_eng'], summary: 'quick', total_results: 'false'};
    hodClient.call('querytextindex', data1, function(err1, resp1) {
      if (!err1 && !resp1.body.error) {
        console.log(resp1.body.documents);
        var articles = resp1.body.documents;
        candidateArticles[key1].articles = articles;
        // Loop through each article and obtain the HTML and concepts from Haven OnDemand using View Document API and Extract Concepts API
        async.each(articles, function (article, callback2) {
          console.log(article)
          var data2 = {url: article.reference};
          hodClient.call('viewdocument', data2, function(err3, resp3) {
            var articleIndex = candidateArticles[key1].articles.indexOf(article);
            if (!err3 && !resp3.body.error && articleIndex >= 0) {
              // add real html content
              var html = resp3.body;
              candidateArticles[key1].articles[articleIndex].html = html
              console.log("worked")
            }
          });
          hodClient.call('extractconcepts', data2, function(err2, resp2) {
            var concepts = resp2.body.concepts;
            async.each(concepts, function(concept, callback) {
              var newDict = {"text": concept.concept, "size": concept.occurrences}
              candidateArticles[key1].concepts.push(newDict);
            }, function (err) {
              if (err) console.error(err.message);
            });
          });
        }, function (err) {
          if (err) console.error(err.message);
        });
      } else {
        console.log("------------------");
        if (resp1.body.error) {console.log(resp1.body.error);}
        console.log(err1);
      }
    });
  }, function (err) {
      if (err) console.error(err.message);
  });
}

// For debugging
// setInterval(function(){debugger;}, 6000);

// Function for mapping the color of the sentiment (no longer usingin front-end)
// Accepts the sentiment score
function mapColor(score) {
  weight = Math.floor(((0.5*score + 0.5)*100));
  r = Math.floor( (255 * (100 - weight)) / 100 );
  g = Math.floor( (255 * weight) / 100 );
  b = 0;
  return {r: r, g: g, b:b};
}

// Function for finding the daily average sentiment
// Accepts the new score, the number of total tweets, and the an object of the previous average and the new average
function calculateRunningAverage(score, n, averages) {
  averages.newAvg = averages.oldAvg * (n-1)/n + score/n;   // New average = old average * (n-1)/n + new value /n
  averages.oldAvg = averages.newAvg; //set equal to new average for next go around of calling this function
  return averages;
}

// Function for calculating the instantaneous average sentiment
// Accepts the array of sentiments and the window used for calculate (i.e. look at most recent 10 tweets)
function calculateRunningAverageWindow(array, win) {
  var runsum = 0.0;
  for (var i=0; i<win; i++) {
    runsum += array[i];
  }
  var avg = runsum/win;
  return avg;
}
<commit_msg>updated query text index<commit_after>require('dotenv').load();

var express = require('express');
var app = express();
var http = require('http').Server(app);
var path = require('path');
var io = require('socket.io')(http);
var Twitter = require('twitter');
var havenondemand = require('havenondemand');
var async = require("async");
var RateLimiter = require('limiter').RateLimiter;

if (process.env.staging === 'true') {
  var staging = true
} else {
  var staging = false
}

// var hodClient = new havenondemand.HODClient(process.env.hpe_apikey, 'v1', staging);
var hodClient = new havenondemand.HODClient(process.env.hpe_apikey);

var twitterClient = new Twitter({
  consumer_key: process.env.consumer_key,
  consumer_secret: process.env.consumer_secret,
  access_token_key: process.env.access_token,
  access_token_secret: process.env.access_token_secret
});

port = process.env.PORT || 5000;
var limiter = new RateLimiter(2, 'second') //first parameter is max number of calls per second

app.use(express.static(path.join(__dirname, 'public')));
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

var candidateString = "HillaryClinton,realDonaldTrump"
var window1 = 10;

// Data used to store and calculate sentiment for candidates
// Each key is the candidate's Twitter handle which is checked after a Tweet is streamed in so we know which candidate we're talking about
var candidateNumbers = {
  "HillaryClinton": {
    averages: {newAvg: 0, oldAvg: 0},
    n: 0,
    nPositive: 0,
    nNegative: 0,
    nNeutral: 0,
    runningAverageWindow1: 0,
    nWindow1: 0,
    runningAverageWindow1Array: []
  },
  "realDonaldTrump": {
      averages: {newAvg: 0, oldAvg: 0},
      n: 0,
      nPositive: 0,
      nNegative: 0,
      nNeutral: 0,
      runningAverageWindow1: 0,
      nWindow1: 0,
      runningAverageWindow1Array: []
  }
}

var articleUpdateInterval = 60000*15; //15 minutes

// Object which stores the articles and concepts from these articles for each of the candidates
var candidateArticles = {
  "Hillary Clinton": {articles: [], concepts: []},
  "Donald Trump": {articles: [], concepts: []},
};

app.get("/", function(req, res) {
  res.render('index', {
    candidateArticles: candidateArticles,
    candidateConcepts: candidateArticles["Hillary Clinton"].concepts
  });
});

// Route for HTML of candidate articles
app.get("/candidatehtml", function(req, res) {
  var payload = candidateArticles
  res.status(200).send(payload)
})

// Route for mobile app HTML of candidate articles
app.get("/candidatehtml_mobile", function(req, res) {
  var payload ={candidates: [
      {
        name: "Hillary Clinton",
        articles: candidateArticles["Hillary Clinton"].articles
      },
      {
        name: "Donald Trump",
        articles: candidateArticles["Donald Trump"].articles
      },
    ]}
    res.status(200).send(payload)
})

// Route for the app explanation at the top
app.get("/whats_this", function(req, res) {
  res.status(200).sendFile(path.join(__dirname, 'views', 'whats_this.html'))
})

// Route for open source contribution explanation
app.get("/contribute", function(req, res) {
  res.status(200).sendFile(path.join(__dirname, 'views', 'contribute.html'))
})

// Route for third party developers to hit if they want the candidate sentiment data
app.get('/candidatedata', function(req, res) {
  // var payload = candidateNumbers
  var payload = {candidates: [
    {
      name: "HillaryClinton",
      averages: {newAvg: candidateNumbers["HillaryClinton"].averages.newAvg, oldAvg: candidateNumbers["HillaryClinton"].averages.oldAvg},
      n: candidateNumbers["HillaryClinton"].n,
      nPositive: candidateNumbers["HillaryClinton"].nPositive,
      nNegative: candidateNumbers["HillaryClinton"].nNegative,
      nNeutral: candidateNumbers["HillaryClinton"].nNeutral,
      runningAverageWindow1: candidateNumbers["HillaryClinton"].runningAverageWindow1,
      runningAverageWindow1Array: candidateNumbers["HillaryClinton"].runningAverageWindow1Array
    },
    {
      name: "realDonaldTrump",
      averages: {newAvg: candidateNumbers["realDonaldTrump"].averages.newAvg, oldAvg: candidateNumbers["realDonaldTrump"].averages.oldAvg},
      n: candidateNumbers["realDonaldTrump"].n,
      nPositive: candidateNumbers["realDonaldTrump"].nPositive,
      nNegative: candidateNumbers["realDonaldTrump"].nNegative,
      nNeutral: candidateNumbers["realDonaldTrump"].nNeutral,
      runningAverageWindow1: candidateNumbers["realDonaldTrump"].runningAverageWindow1,
      runningAverageWindow1Array: candidateNumbers["realDonaldTrump"].runningAverageWindow1Array
    },
  ]}
  res.status(200).send(payload)
})

http.listen(port, function(){
  console.log("Listening on port: "+port);
});

//Stream Tweets
twitterClient.stream('statuses/filter', {track: candidateString}, function(stream) {
  stream.on('data', function(tweet) {
    if (tweet.entities !== undefined) {
      var userMentions = tweet.entities.user_mentions;
      for (var i=0; i<userMentions.length; i++) {
        var screenName = userMentions[i].screen_name;
        if (candidateNumbers[screenName] !== undefined) {
          twitterStream(screenName, candidateNumbers[screenName], tweet)
        }
      }
    }
  });

  stream.on('disconnect', function (disconnectMessage) {
    console.log(disconnectMessage);
  });

  stream.on('error', function(error) {
    throw error;
  });
});

//Helper function to process a tweet for a particular candidate
// Accepts the candidate Twitter handle, an object of his or her sentiment data, and the tweet object returned from the Twitter Stream API
//Analyzes the sentiment, computes the average, computes the Instantaneous average (averageWindow1), maps to colors (no longer using in front end), emits all to the client through websockets and stores it in a Haven OnDemand Index
function twitterStream(candidate, candidateData, tweetObject) {
  var data = {text: tweetObject.text};
  limiter.removeTokens(1, function(err, remainingRequests) {
    console.log("Remaining requests per second: " + remainingRequests);
    hodClient.call('analyzesentiment', data, function(err, resp){
      // debugger;
      if (!err) {
        console.log(resp.body)
        candidateData.n += 1; //increase n by one
        candidateData.nWindow1 +=1 ; //increase by one
        var sentiment = resp.body.aggregate.sentiment;
        // var score = 10.0/3.0*(resp.body.aggregate.score*100.0)+50.0; //map from -15 to 15 to 0 to 100 ... y =10/3*x+50
        // var score = 50.0*(resp.body.aggregate.score)+50.0; //map from -1.0 to 1.0 to 0 to 100 ... y =50*x+50
        var score = 100.0*(resp.body.aggregate.score)+50.0; //map from -0.5 to 0.5 to 0 to 100 ... y =50*x+50
        if (score > 50) {
          candidateData.nPositive += 1;
        } else if(score < 50) {
          candidateData.nNegative += 1;
        } else {
          candidateData.nNeutral += 1;
        }
        //perform running averages window
        candidateData.runningAverageWindow1Array.push(score);
        if (candidateData.runningAverageWindow1Array.length > window1) { //if there is enough data points in the window
          candidateData.runningAverageWindow1Array.splice(0,1);
          candidateData.runningAverageWindow1 = calculateRunningAverageWindow(candidateData.runningAverageWindow1Array, window1)
        }
        //
        candidateData.averages = calculateRunningAverage(score, candidateData.n, candidateData.averages);
        rgbInstantaneous = mapColor(score);
        rgbAverage = mapColor(candidateData.averages.newAvg);
        console.log("------------------------------");
        console.log(tweetObject.text + " | " + sentiment + " | " + score);
        var tweetData = {candidate: candidate, tweet: tweetObject, positive: resp.body.positive, negative: resp.body.negative, aggregate: resp.body.aggregate, rgbInstantaneous: rgbInstantaneous, rgbAverage: rgbAverage, average: candidateData.averages.newAvg, averageWindow1: candidateData.runningAverageWindow1, n: candidateData.n, nNeutral: candidateData.nNeutral, nNegative: candidateData.nNegative, nPositive: candidateData.nPositive};
        io.emit('message', tweetData);
        var data2 = {
          index: 'ourfeelingsaboutpoliticiansa',
          json: JSON.stringify({
            document: [{
              title: candidate + candidateData.n,
              content: tweetObject.text,
              candidate: candidate, tweet: tweetObject, positive: resp.body.positive, negative: resp.body.negative, aggregate: resp.body.aggregate, rgbInstantaneous: rgbInstantaneous, rgbAverage: rgbAverage, average: candidateData.averages.newAvg, averageWindow1: candidateData.runningAverageWindow1, n: candidateData.n, nNeutral: candidateData.nNeutral, nNegative: candidateData.nNegative, nPositive: candidateData.nPositive,
              score: score,
              date: Date.now()
            }]
          })
        }
        if (process.env.add_text_index === 'true') {
          hodClient.call('addtotextindex', data2, function(err2, resp2, body2) {
            if (resp2) {
              if (resp2.body) {
                console.log(resp2.body)
              }
            }
          })
        }
      } else {
        console.log(err);
      }
    });
  });
}

// Functions for updating articles
updateCandidateArticles();
// setInterval(updateCandidateArticles(), articleUpdateInterval);

// Function to retrieve articles about each candidate. Results are stored in the object, candidateArticles, created at the top of this document
function updateCandidateArticles() {
  // Loop through each candidate and obtain articles pertaining to them from Haven OnDemand using Query Text Index API
  async.forEachOf(candidateArticles, function (value1, key1, callback1) {
    candidateArticles[key1].concepts = []; //delete the old concepts for the candidate
    var data1 = {text: key1, indexes: ['news_eng'], summary: 'quick', total_results: 'false'};
    hodClient.call('querytextindex', data1, function(err1, resp1) {
      if (!err1) {
        console.log(resp1.body.documents);
        var articles = resp1.body.documents;
        candidateArticles[key1].articles = articles;
        // Loop through each article and obtain the HTML and concepts from Haven OnDemand using View Document API and Extract Concepts API
        async.each(articles, function (article, callback2) {
          console.log(article)
          var data2 = {url: article.reference};
          hodClient.call('viewdocument', data2, function(err3, resp3) {
            var articleIndex = candidateArticles[key1].articles.indexOf(article);
            if (!err3 && articleIndex >= 0) {
              // add real html content
              var html = resp3.body;
              candidateArticles[key1].articles[articleIndex].html = html
              console.log("worked")
            }
          });
          hodClient.call('extractconcepts', data2, function(err2, resp2) {
            var concepts = resp2.body.concepts;
            async.each(concepts, function(concept, callback) {
              var newDict = {"text": concept.concept, "size": concept.occurrences}
              candidateArticles[key1].concepts.push(newDict);
            }, function (err) {
              if (err) console.error(err.message);
            });
          });
        }, function (err) {
          if (err) console.error(err.message);
        });
      } else {
        console.log("------------------");
        if (err1) {console.log(err1);}
        console.log(err1);
      }
    });
  }, function (err) {
      if (err) console.error(err.message);
  });
}

// For debugging
// setInterval(function(){debugger;}, 6000);

// Function for mapping the color of the sentiment (no longer usingin front-end)
// Accepts the sentiment score
function mapColor(score) {
  weight = Math.floor(((0.5*score + 0.5)*100));
  r = Math.floor( (255 * (100 - weight)) / 100 );
  g = Math.floor( (255 * weight) / 100 );
  b = 0;
  return {r: r, g: g, b:b};
}

// Function for finding the daily average sentiment
// Accepts the new score, the number of total tweets, and the an object of the previous average and the new average
function calculateRunningAverage(score, n, averages) {
  averages.newAvg = averages.oldAvg * (n-1)/n + score/n;   // New average = old average * (n-1)/n + new value /n
  averages.oldAvg = averages.newAvg; //set equal to new average for next go around of calling this function
  return averages;
}

// Function for calculating the instantaneous average sentiment
// Accepts the array of sentiments and the window used for calculate (i.e. look at most recent 10 tweets)
function calculateRunningAverageWindow(array, win) {
  var runsum = 0.0;
  for (var i=0; i<win; i++) {
    runsum += array[i];
  }
  var avg = runsum/win;
  return avg;
}
<|endoftext|>


<commit_before>'use strict';

var path = require('path');
var mapnik = require('@carto/mapnik');
var mapnik_pool = require('mapnik-pool');
var timeoutDecorator = require('./utils/timeout-decorator')

// Register datasource plugins
mapnik.register_default_input_plugins();

var mapnikPool = mapnik_pool(mapnik);

module.exports = Bridge;

function Bridge(uri, callback) {
    if (!uri.xml) {
        return callback(new Error('No xml'));
    }

    this._uri = uri;
    this._base = path.resolve(uri.base || __dirname);

    // 'blank' option forces all solid tiles to be interpreted as blank.
    this._blank = typeof uri.blank === 'boolean' ? uri.blank : false;

    // whether to compress the vector tiles or not
    this._gzip = typeof uri.gzip === 'boolean' ? uri.gzip : true;

    this._bufferSize = (uri.query && Number.isFinite(uri.query.bufferSize) && uri.query.bufferSize >= 0) ? uri.query.bufferSize : 256;

    this._uri.limits = (uri.query && uri.query.limits) ? uri.query.limits : {};
    if (typeof this._uri.limits.render === 'undefined') this._uri.limits.render = 0;

    if (this._uri.limits.render > 0) {
        this.getTile = timeoutDecorator(this.getTile.bind(this), this._uri.limits.render);
    }

    // Unset maxzoom. Will be re-set on first getTile.
    this._maxzoom = undefined;
    this._xml = uri.xml;

    var mopts = { strict: false, base: this._base + '/' };

    this._map = mapnikPool.fromString(this._xml, { size: 256, bufferSize: this._bufferSize }, mopts);

    return callback(null, this);
}

Bridge.registerProtocols = function(tilelive) {
    tilelive.protocols['bridge:'] = Bridge;
};

function poolDrain(pool,callback) {
    if (!pool) {
        return callback();
    }
    pool.drain(function() {
        pool.destroyAllNow(callback);
    });
}

Bridge.prototype.close = function(callback) {
    // For currently unknown reasons map objects can currently be acquired
    // without being released under certain circumstances. When this occurs
    // a source cannot be closed fully during a copy or other operation. For
    // now error out in these scenarios as a close timeout.
    setTimeout(function() {
        if (!callback) return;
        console.warn(new Error('Source resource pool drain timed out after 5s'));
        callback();
        callback = false;
    }, 5000);

    poolDrain(this._map, function () {
        if (!callback) return;
        callback();
        callback = false;
    });
};

Bridge.prototype.getTile = function (z, x, y, callback) {
    if (!this._map) {
        return callback(new Error('Tilesource not loaded'));
    }

    this._map.acquire((err, map) => {
        if (err) {
            return callback(err);
        }

        // set _maxzoom cache to prevent repeat calls to map.parameters
        if (this._maxzoom === undefined) {
            this._maxzoom = map.parameters.maxzoom ? parseInt(map.parameters.maxzoom, 10) : 14;
        }

        if (this._threading_mode === undefined) {
            var threading_type = map.parameters.threading_mode;
            if (threading_type === 'auto') {
                this._threading_mode = mapnik.threadingMode.auto;
            } else if (threading_type === 'async') {
                this._threading_mode = mapnik.threadingMode.async;
            } else {
                this._threading_mode = mapnik.threadingMode.deferred;
            }
        }

        this.getVector(map, z, x, y, callback);
    });
};

Bridge.prototype.getVector = function (map, z, x, y, callback) {
    var opts = {};

    var headers = {};
    headers['Content-Type'] = 'application/x-protobuf';

    // The buffer size is in vector tile coordinates, while the buffer size on the
    // map object is in image coordinates. Therefore, lets multiply the buffer_size
    // by the old "path_multiplier" value of 16 to get a proper buffer size.
    try {
        // Try-catch is necessary here because the constructor will throw if x and y
        // are out of bounds at zoom-level z
        var vtile = new mapnik.VectorTile(+z,+x,+y, {buffer_size:16*map.bufferSize});
    } catch(err) {
        return callback(err, null, headers);
    }

    map.extent = vtile.extent();

    // Since we (CARTO) are already simplifying the geometries in the Postgresql query
    // we don't want another simplification as it will have a visual impact
    opts.simplify_distance = 0;

    opts.threading_mode = this._threading_mode;

    // enable strictly_simple
    opts.strictly_simple = true;

    // make zoom, x, y and bbox variables available to mapnik postgis datasource
    opts.variables = {
        zoom_level: z, // for backwards compatibility
        zoom: z,
        x: x,
        y: y,
        bbox: JSON.stringify(map.extent)
    };

    map.render(vtile, opts, (err, vtile) => {
        this._map.release(map);
        if (err) {
            return callback(err);
        }
        headers['x-tilelive-contains-data'] = vtile.painted();
        if (vtile.empty()) {
            return callback(null, new Buffer(0), headers);
        }
        vtile.getData({ compression: this._gzip ? 'gzip' : 'none' }, (err, pbfz) => {
            if (err) {
                return callback(err);
            }

            if (this._gzip) {
                headers['Content-Encoding'] = 'gzip';
            }

            return callback(err, pbfz, headers);
        });
    });
};
<commit_msg>Extract threading mode<commit_after>'use strict';

var path = require('path');
var mapnik = require('@carto/mapnik');
var mapnik_pool = require('mapnik-pool');
var timeoutDecorator = require('./utils/timeout-decorator')

// Register datasource plugins
mapnik.register_default_input_plugins();

var mapnikPool = mapnik_pool(mapnik);

module.exports = Bridge;

function Bridge(uri, callback) {
    if (!uri.xml) {
        return callback(new Error('No xml'));
    }

    this._uri = uri;
    this._base = path.resolve(uri.base || __dirname);

    // 'blank' option forces all solid tiles to be interpreted as blank.
    this._blank = typeof uri.blank === 'boolean' ? uri.blank : false;

    // whether to compress the vector tiles or not
    this._gzip = typeof uri.gzip === 'boolean' ? uri.gzip : true;

    this._bufferSize = (uri.query && Number.isFinite(uri.query.bufferSize) && uri.query.bufferSize >= 0) ? uri.query.bufferSize : 256;

    this._uri.limits = (uri.query && uri.query.limits) ? uri.query.limits : {};
    if (typeof this._uri.limits.render === 'undefined') this._uri.limits.render = 0;

    if (this._uri.limits.render > 0) {
        this.getTile = timeoutDecorator(this.getTile.bind(this), this._uri.limits.render);
    }

    // Unset maxzoom. Will be re-set on first getTile.
    this._maxzoom = undefined;
    this._xml = uri.xml;

    var mopts = { strict: false, base: this._base + '/' };

    this._map = mapnikPool.fromString(this._xml, { size: 256, bufferSize: this._bufferSize }, mopts);

    return callback(null, this);
}

Bridge.registerProtocols = function(tilelive) {
    tilelive.protocols['bridge:'] = Bridge;
};

function poolDrain(pool,callback) {
    if (!pool) {
        return callback();
    }
    pool.drain(function() {
        pool.destroyAllNow(callback);
    });
}

Bridge.prototype.close = function(callback) {
    // For currently unknown reasons map objects can currently be acquired
    // without being released under certain circumstances. When this occurs
    // a source cannot be closed fully during a copy or other operation. For
    // now error out in these scenarios as a close timeout.
    setTimeout(function() {
        if (!callback) return;
        console.warn(new Error('Source resource pool drain timed out after 5s'));
        callback();
        callback = false;
    }, 5000);

    poolDrain(this._map, function () {
        if (!callback) return;
        callback();
        callback = false;
    });
};

Bridge.prototype.getTile = function (z, x, y, callback) {
    if (!this._map) {
        return callback(new Error('Tilesource not loaded'));
    }

    this._map.acquire((err, map) => {
        if (err) {
            return callback(err);
        }

        // set _maxzoom cache to prevent repeat calls to map.parameters
        if (this._maxzoom === undefined) {
            this._maxzoom = map.parameters.maxzoom ? parseInt(map.parameters.maxzoom, 10) : 14;
        }

        this.getVector(map, z, x, y, callback);
    });
};

Bridge.prototype.getVector = function (map, z, x, y, callback) {
    var opts = {};

    var headers = {};
    headers['Content-Type'] = 'application/x-protobuf';

    // The buffer size is in vector tile coordinates, while the buffer size on the
    // map object is in image coordinates. Therefore, lets multiply the buffer_size
    // by the old "path_multiplier" value of 16 to get a proper buffer size.
    try {
        // Try-catch is necessary here because the constructor will throw if x and y
        // are out of bounds at zoom-level z
        var vtile = new mapnik.VectorTile(+z,+x,+y, {buffer_size:16*map.bufferSize});
    } catch(err) {
        return callback(err, null, headers);
    }

    map.extent = vtile.extent();

    // Since we (CARTO) are already simplifying the geometries in the Postgresql query
    // we don't want another simplification as it will have a visual impact
    opts.simplify_distance = 0;

    opts.threading_mode = getThreadingMode(map);

    // enable strictly_simple
    opts.strictly_simple = true;

    // make zoom, x, y and bbox variables available to mapnik postgis datasource
    opts.variables = {
        zoom_level: z, // for backwards compatibility
        zoom: z,
        x: x,
        y: y,
        bbox: JSON.stringify(map.extent)
    };

    map.render(vtile, opts, (err, vtile) => {
        this._map.release(map);
        if (err) {
            return callback(err);
        }
        headers['x-tilelive-contains-data'] = vtile.painted();
        if (vtile.empty()) {
            return callback(null, new Buffer(0), headers);
        }
        vtile.getData({ compression: this._gzip ? 'gzip' : 'none' }, (err, pbfz) => {
            if (err) {
                return callback(err);
            }

            if (this._gzip) {
                headers['Content-Encoding'] = 'gzip';
            }

            return callback(err, pbfz, headers);
        });
    });
};

function getThreadingMode (map) {
    const threadingType = map.parameters.threading_mode;

    if (threadingType === 'auto') {
        return mapnik.threadingMode.auto; // 3
    }

    if (threadingType === 'async') {
        return mapnik.threadingMode.async; // 1
    }

    return mapnik.threadingMode.deferred; // 2
}
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2017-2018  Valerii Zinchenko
 * Licensed under MIT (https://github.com/valerii-zinchenko/jsdoc-inheritance-diagram/blob/master/LICENSE.txt)
 * All source files are available at: https://github.com/valerii-zinchenko/jsdoc-inheritance-diagram
 */
'use strict';

const conf = require('jsdoc/env').conf.opts['inheritance-diagram'] || {};
const Diagram = require('inheritance-diagram');
// Map of nodes
let map;

function addClass(doclet) {
	const name = doclet.name;
	const node = {
		children: [],
		link: `${name}.html`,
		// Reference to doclet is needed here to modify his description with a diagram
		doclet: doclet
	};

	// If class does not extends any other class, then 'augments' property does not exist
	if (doclet.augments && doclet.augments.length > 0) {
		// inheritance-diagram@1.0 supports only single parent
		node.parent = doclet.augments[0];
	}

	// If class does not mxies anything, then 'mixes' property does not exist
	if (doclet.mixes && doclet.mixes.length > 0) {
		node.mixes = [].concat(doclet.mixes);
	}

	map[name] = node;

	return node;
}

let isDocForExternalLinksGenerated = false;
exports.handlers = {
	beforeParse: function(e) {
		// this event is fired for each file but additional documentation should be added only once
		if (isDocForExternalLinksGenerated) {
			return;
		}

		let extraDoc = '';
		Object.keys(conf.externalLinks).forEach(key => {
			extraDoc += `/**
			 * This is automatically generated documentation page by jsdoc-inheritance-diagram plugin. Please follow "see" link to see more details about this class.
			 * @class ${key}
			 * @see ${conf.externalLinks[key]}
			 */`;
		});

		e.source += extraDoc;

		isDocForExternalLinksGenerated = true;
	},

	parseBegin: function() {
		map = {};
	},

	newDoclet: function(event) {
		const doclet = event.doclet;
		if (doclet.kind !== 'class') {
			return;
		}

		addClass(doclet);
	},

	parseComplete: function() {
		// Populate children
		Object.keys(map).forEach((name) => {
			const node = map[name];
			const parentNode = map[node.parent] || {
				children: []
			};

			if (parentNode.children.indexOf(name) === -1) {
				parentNode.children.push(name);
			}
		});

		Object.keys(map).forEach((name) => {
			// Generate inheritance diagrams
			const diagram = new Diagram(name, map, conf);

			const doclet = map[name].doclet;
			doclet.description = '
' + diagram.getResult() + '
' + doclet.description;
		});
	}
};
<commit_msg>fix: crashing when "externalLinks" property is not defined<commit_after>/*
 * Copyright (c) 2017-2018  Valerii Zinchenko
 * Licensed under MIT (https://github.com/valerii-zinchenko/jsdoc-inheritance-diagram/blob/master/LICENSE.txt)
 * All source files are available at: https://github.com/valerii-zinchenko/jsdoc-inheritance-diagram
 */
'use strict';

const conf = require('jsdoc/env').conf.opts['inheritance-diagram'] || {};
const Diagram = require('inheritance-diagram');
// Map of nodes
let map;

function addClass(doclet) {
	const name = doclet.name;
	const node = {
		children: [],
		link: `${name}.html`,
		// Reference to doclet is needed here to modify his description with a diagram
		doclet: doclet
	};

	// If class does not extends any other class, then 'augments' property does not exist
	if (doclet.augments && doclet.augments.length > 0) {
		// inheritance-diagram@1.0 supports only single parent
		node.parent = doclet.augments[0];
	}

	// If class does not mxies anything, then 'mixes' property does not exist
	if (doclet.mixes && doclet.mixes.length > 0) {
		node.mixes = [].concat(doclet.mixes);
	}

	map[name] = node;

	return node;
}

let isDocForExternalLinksGenerated = !conf.externalLinks;
exports.handlers = {
	beforeParse: function(e) {
		// this event is fired for each file but additional documentation should be added only once
		if (isDocForExternalLinksGenerated) {
			return;
		}

		let extraDoc = '';
		Object.keys(conf.externalLinks).forEach(key => {
			extraDoc += `/**
			 * This is automatically generated documentation page by jsdoc-inheritance-diagram plugin. Please follow "see" link to see more details about this class.
			 * @class ${key}
			 * @see ${conf.externalLinks[key]}
			 */`;
		});

		e.source += extraDoc;

		isDocForExternalLinksGenerated = true;
	},

	parseBegin: function() {
		map = {};
	},

	newDoclet: function(event) {
		const doclet = event.doclet;
		if (doclet.kind !== 'class') {
			return;
		}

		addClass(doclet);
	},

	parseComplete: function() {
		// Populate children
		Object.keys(map).forEach((name) => {
			const node = map[name];
			const parentNode = map[node.parent] || {
				children: []
			};

			if (parentNode.children.indexOf(name) === -1) {
				parentNode.children.push(name);
			}
		});

		Object.keys(map).forEach((name) => {
			// Generate inheritance diagrams
			const diagram = new Diagram(name, map, conf);

			const doclet = map[name].doclet;
			doclet.description = '
' + diagram.getResult() + '
' + doclet.description;
		});
	}
};
<|endoftext|>


<commit_before>import React, {Component} from 'react';
import * as _ from 'lodash';
import ShCore from 'sh-core';
require('./sh-input-currency.scss');

class ShInputCurrency extends Component {

    constructor(props) {
        super(props);
        this.state = {
            value: '',
            display: '',
            classList: {
                shInputCurrency: true,
                empty: true
            },
            validStatus: 'unknown',
            placeholderText: '+',
            requiredField: {showRequired: false}
        };

        this.handleChange = this.handleChange.bind(this);
        this.handleBlur = this.handleBlur.bind(this);
        this.handleFocus = this.handleFocus.bind(this);
        this.validate = this.validate.bind(this);
    }

    validate(onSubmit) {
        if (onSubmit) {
            this.state.classList.shTouched = true;
        }
        let rtn = {isValid: true};

        this.state.classList.shInvalid = false;

        if (this.props.required && (this.state.value === null || this.state.value === '')) {
            this.state.classList.shInvalid = true;

            rtn.isValid = false;
            rtn.msg = 'Required';
        }

        var newState = _.clone(this.state);
        this.setState(newState);
        return rtn;
    };

    componentWillMount() {
        if (this.props.validator) {
            this.props.validator.register(this, this.validate);
        }
    };

    componentWillUnmount() {
        if (this.props.validator) {
            this.props.validator.unregister(this);
        }
    };

    runFormarters(txt) {
        return '$' + (this.formatNumber(Number(ShCore.getDecimal(txt)).toFixed(2)));
    }

    componentDidMount() {
        let newValue = ShCore.getDecimal(this.props.value);
        var newState = _.clone(this.state);
        newState.value = newValue;
        newState.display = this.runFormarters(newValue);
        newState.classList.empty = false;

        if (this.props.value) {
            this.setState(newState, this.validate);
        }
    }

    componentWillReceiveProps(props) {
        let newValue = ShCore.getDecimal(props.value);

        if (!_.isUndefined(props.value) && !_.isEqual(newValue, this.state.value)) {
            var newState = _.clone(this.state);
            newState.classList.empty = !newValue;
            newState.value = newValue;
            newState.display = this.runFormarters(newValue);
            this.setState(newState, this.validate);
        }
    }

    handleChange(event) {
        event.persist();

        var dec = ShCore.getDecimal(event.target.value);
        this.setState({value: dec, display: event.target.value}, ()=> {
            if (this.props.validator) {
                this.props.validator.validate()
            } else {
                this.validate();
            }

            event.target.value = dec;
            this.props.onChange(event);
        });
    };

    handleFocus(event) {
        var text = event.target.value;
        text = text.toString().replace(/[,$]/g, '');

        if (this.props.onFocus) {
            this.props.onFocus(event);
        }

        var newState = _.clone(this.state);
        newState.classList.shTouched = true;
        newState.value = text;
        newState.placeholderText = '';

        this.setState(newState,
            function(){
                this.refs.input.select();
            }
        );
    }

    formatNumber(num) {
        return num.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, "$1,")
    }

    handleBlur(event) {
        this.validate();
        var newState = _.clone(this.state);

        if (event.target.value.length > 0) {
            var text = event.target.value;
            text = this.runFormarters(text);
            newState.display = text;
        }

        newState.placeholderText = newState.placeholderHolder;
        newState.classList.empty = !this.state.value;
        newState.requiredField.showRequired = !this.state.value;

        this.setState(newState);

        if (this.props.onBlur) {
            this.props.onBlur(event);
        }
    }

    render() {
        var {onFocus, onBlur, className, validator, required, value, ...other} = this.props;

        return (

                    {this.props.label}
                    REQUIRED
                    <input ref="input"
                           className="sh-currency-input"
                           type="text"
                           {...other}
                           placeholder={this.state.placeholderText}
                           onChange={this.handleChange}
                           onFocus={this.handleFocus}
                           onBlur={this.handleBlur}
                           value={this.state.display}
                    />

        )
    }
}

ShInputCurrency.propTypes = {
    validator: React.PropTypes.object,
    value: React.PropTypes.any,
    onChange: React.PropTypes.func,
    label: React.PropTypes.string,
    required: React.PropTypes.bool,
};

ShInputCurrency.defaultProps = {
    validator: null,
    onChange: _.noop,
    label: '',
    required: false
};

export default ShInputCurrency;<commit_msg>moved set timeout<commit_after>import React, {Component} from 'react';
import * as _ from 'lodash';
import ShCore from 'sh-core';
require('./sh-input-currency.scss');

class ShInputCurrency extends Component {

    constructor(props) {
        super(props);
        this.state = {
            value: '',
            display: '',
            classList: {
                shInputCurrency: true,
                empty: true
            },
            validStatus: 'unknown',
            placeholderText: '+',
            requiredField: {showRequired: false}
        };

        this.handleChange = this.handleChange.bind(this);
        this.handleBlur = this.handleBlur.bind(this);
        this.handleFocus = this.handleFocus.bind(this);
        this.validate = this.validate.bind(this);
    }

    validate(onSubmit) {
        if (onSubmit) {
            this.state.classList.shTouched = true;
        }
        let rtn = {isValid: true};

        this.state.classList.shInvalid = false;

        if (this.props.required && (this.state.value === null || this.state.value === '')) {
            this.state.classList.shInvalid = true;

            rtn.isValid = false;
            rtn.msg = 'Required';
        }

        var newState = _.clone(this.state);
        this.setState(newState);
        return rtn;
    };

    componentWillMount() {
        if (this.props.validator) {
            this.props.validator.register(this, this.validate);
        }
    };

    componentWillUnmount() {
        if (this.props.validator) {
            this.props.validator.unregister(this);
        }
    };

    runFormarters(txt) {
        return '$' + (this.formatNumber(Number(ShCore.getDecimal(txt)).toFixed(2)));
    }

    componentDidMount() {
        let newValue = ShCore.getDecimal(this.props.value);
        var newState = _.clone(this.state);
        newState.value = newValue;
        newState.display = this.runFormarters(newValue);
        newState.classList.empty = false;

        if (this.props.value) {
            this.setState(newState, this.validate);
        }
    }

    componentWillReceiveProps(props) {
        let newValue = ShCore.getDecimal(props.value);

        if (!_.isUndefined(props.value) && !_.isEqual(newValue, this.state.value)) {
            var newState = _.clone(this.state);
            newState.classList.empty = !newValue;
            newState.value = newValue;
            newState.display = this.runFormarters(newValue);
            this.setState(newState, this.validate);
        }
    }

    handleChange(event) {
        event.persist();

        var dec = ShCore.getDecimal(event.target.value);
        this.setState({value: dec, display: event.target.value}, ()=> {
            if (this.props.validator) {
                this.props.validator.validate()
            } else {
                this.validate();
            }

            event.target.value = dec;
            this.props.onChange(event);
        });
    };

    handleFocus(event) {
        var text = event.target.value;
        text = text.toString().replace(/[,$]/g, '');

        if (this.props.onFocus) {
            this.props.onFocus(event);
        }

        var newState = _.clone(this.state);
        newState.classList.shTouched = true;
        newState.value = text;
        newState.placeholderText = '';

        this.setState(newState,
            ()=>{
                this.refs.input.select();
            }
        );
    }

    formatNumber(num) {
        return num.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, "$1,")
    }

    handleBlur(event) {
        this.validate();
        var newState = _.clone(this.state);

        if (event.target.value.length > 0) {
            var text = event.target.value;
            text = this.runFormarters(text);
            newState.display = text;
        }

        newState.placeholderText = newState.placeholderHolder;
        newState.classList.empty = !this.state.value;
        newState.requiredField.showRequired = !this.state.value;

        this.setState(newState);

        if (this.props.onBlur) {
            this.props.onBlur(event);
        }
    }

    render() {
        var {onFocus, onBlur, className, validator, required, value, ...other} = this.props;

        return (

                    {this.props.label}
                    REQUIRED
                    <input ref="input"
                           className="sh-currency-input"
                           type="text"
                           {...other}
                           placeholder={this.state.placeholderText}
                           onChange={this.handleChange}
                           onFocus={this.handleFocus}
                           onBlur={this.handleBlur}
                           value={this.state.display}
                    />

        )
    }
}

ShInputCurrency.propTypes = {
    validator: React.PropTypes.object,
    value: React.PropTypes.any,
    onChange: React.PropTypes.func,
    label: React.PropTypes.string,
    required: React.PropTypes.bool,
};

ShInputCurrency.defaultProps = {
    validator: null,
    onChange: _.noop,
    label: '',
    required: false
};

export default ShInputCurrency;<|endoftext|>


<commit_before>module.exports = {
  rules: {
    'named-import-newline': require('./lib/rules/named-import-newline'),
  },
};
<commit_msg>may want to add the rule<commit_after>module.exports = {
  rules: {
    'named-import-newline': require('./lib/rules/named-import-newline'),
    'sort-object-keys': require('./lib/rules/sort-object-keys'),
  },
};
<|endoftext|>