<commit_before>(function() {
  var sAsync = {},
    root = this,
    presiousSAsync;

  function _endFunction(end) {
    function _emptyFunction() {}

    return (typeof end === 'function') ? end : _emptyFunction;
  }

  if (root != null) {
    presiousSAsync = root.sAsync;
  }

  /**
   * Returns the simple-async object without overwriting other one.
   *
   * @return {object} Simple-async object.
   */
  sAsync.noConflict = function() {
    root.sAsync = presiousSAsync;
    return sAsync;
  };

  /**
   * Executes asynchronously the array of methods in series and finally call the
   * end method with the result parameter. If there is any error, the 'end'
   * method will be executed before finishing the remaining unfinished methods.
   *
   * @param {function[]} methods Array of methods, example: 'function(next) {}',
   *   the 'next' parameter should be invoqued after method is completed to
   *   execute the next method, if it has a parameter there is an error.
   * @param {function} end Ending method: 'function(result) {}', the 'result'
   *   parameter can be undefined or other value according to a success or
   *   unsuccess result respectively.
   */
  sAsync.doSeries = function(methods, end) {
    var _end = _endFunction(end),
      methodsLength = methods && methods.length,
      correct = !!methodsLength,
      methodIndex = 0,
      i = 0;

    function _execute(methodIndex) {
      if (methodIndex < methodsLength) {
        methods[methodIndex++](_next);
      } else {
        _end();
      }
    }

    function _next(error) {
      if (typeof error === 'undefined') {
        _execute(methodIndex++);
      } else {
        _end(error);
      }
    }

    while (correct && (i < methodsLength)) {
      correct &= (typeof methods[i] === 'function');
      i++;
    }

    if (correct) {
      _next();
    } else {
      _next(false);
    }
  };

  /**
   * Executes asynchronously the array of methods in parallel and finally call
   * the end method with the result parameter. If there is any error, the 'end'
   * method will be executed before finishing the remaining unfinished methods.
   *
   * @param {function[]} methods Array of methods, example: 'function(done) {}',
   *   the 'done' parameter should be invoqued after method is completed to
   *   execute the callback, if it has a parameter there is an error.
   * @param {function} end Ending method: 'function(result) {}', the 'result'
   *   parameter can be undefined or other value according to a success or
   *   unsuccess result respectively.
   */
  sAsync.doParallel = function(methods, end) {
    var _end = _endFunction(end),
      endCalled = false,
      methodsLength = methods && methods.length,
      correct = !!methodsLength,
      finishedMethods = 0,
      i = 0;

    function _done(error) {
      var _error = (typeof error !== 'undefined'),
        _finished = (++finishedMethods === methodsLength);

      if (!endCalled && (_finished || _error)) {
        endCalled = true;
        if (_error) {
          _end(error);
        } else {
          _end();
        }
      }
    }

    while (correct && (i < methodsLength)) {
      correct &= (typeof methods[i] === 'function');
      i++;
    }

    if (correct) {
      i = 0;
      while (i < methodsLength) {
        methods[i++](_done);
      }
    } else {
      _end(false);
    }
  };

  // Make sAsync library visible.
  if ((typeof define !== 'undefined') && define.amd) { // AMD / RequireJS.
    define([], function() {
      return sAsync;
    });
  } else if ((typeof module !== 'undefined') && module.exports) { // NodeJS.
    module.exports = sAsync;
  } else { // Browser.
    root.sAsync = sAsync;
  }
}());
<commit_msg>Refactor the checking parameters source-code block, #11<commit_after>(function() {
  var sAsync = {},
    root = this,
    presiousSAsync;

  function _endFunction(end) {
    function _emptyFunction() {}

    return (typeof end === 'function') ? end : _emptyFunction;
  }

  function _checkMethods(methods) {
    var methodsLength = methods && methods.length,
      correct = !!methodsLength,
      i = 0;

    while (correct && (i < methodsLength)) {
      correct &= (typeof methods[i] === 'function');
      i++;
    }
    return correct;
  }

  if (root != null) {
    presiousSAsync = root.sAsync;
  }

  /**
   * Returns the simple-async object without overwriting other one.
   *
   * @return {object} Simple-async object.
   */
  sAsync.noConflict = function() {
    root.sAsync = presiousSAsync;
    return sAsync;
  };

  /**
   * Executes asynchronously the array of methods in series and finally call the
   * end method with the result parameter. If there is any error, the 'end'
   * method will be executed before finishing the remaining unfinished methods.
   *
   * @param {function[]} methods Array of methods, example: 'function(next) {}',
   *   the 'next' parameter should be invoqued after method is completed to
   *   execute the next method, if it has a parameter there is an error.
   * @param {function} end Ending method: 'function(result) {}', the 'result'
   *   parameter can be undefined or other value according to a success or
   *   unsuccess result respectively.
   */
  sAsync.doSeries = function(methods, end) {
    var _end = _endFunction(end),
      methodsLength = methods && methods.length,
      methodIndex = 0;

    function _execute(methodIndex) {
      if (methodIndex < methodsLength) {
        methods[methodIndex++](_next);
      } else {
        _end();
      }
    }

    function _next(error) {
      if (typeof error === 'undefined') {
        _execute(methodIndex++);
      } else {
        _end(error);
      }
    }

    if (_checkMethods(methods)) {
      _next();
    } else {
      _next(false);
    }
  };

  /**
   * Executes asynchronously the array of methods in parallel and finally call
   * the end method with the result parameter. If there is any error, the 'end'
   * method will be executed before finishing the remaining unfinished methods.
   *
   * @param {function[]} methods Array of methods, example: 'function(done) {}',
   *   the 'done' parameter should be invoqued after method is completed to
   *   execute the callback, if it has a parameter there is an error.
   * @param {function} end Ending method: 'function(result) {}', the 'result'
   *   parameter can be undefined or other value according to a success or
   *   unsuccess result respectively.
   */
  sAsync.doParallel = function(methods, end) {
    var _end = _endFunction(end),
      endCalled = false,
      methodsLength = methods && methods.length,
      finishedMethods = 0,
      i = 0;

    function _done(error) {
      var _error = (typeof error !== 'undefined'),
        _finished = (++finishedMethods === methodsLength);

      if (!endCalled && (_finished || _error)) {
        endCalled = true;
        if (_error) {
          _end(error);
        } else {
          _end();
        }
      }
    }

    if (_checkMethods(methods)) {
      i = 0;
      while (i < methodsLength) {
        methods[i++](_done);
      }
    } else {
      _end(false);
    }
  };

  // Make sAsync library visible.
  if ((typeof define !== 'undefined') && define.amd) { // AMD / RequireJS.
    define([], function() {
      return sAsync;
    });
  } else if ((typeof module !== 'undefined') && module.exports) { // NodeJS.
    module.exports = sAsync;
  } else { // Browser.
    root.sAsync = sAsync;
  }
}());
<|endoftext|>


<commit_before>const _ = require('lodash/fp')
const Pdf417Parser = require('./compliance/parsepdf417')
const coinUtils = require('./coins/utils')
const v4l2camera = require('v4l2camera')
const jpg = require('jpeg-turbo')
const manatee = require('manatee')
const supyo = require('@lamassu/supyo')
const jsQR = require('jsqr')

const selectedCamResolutions = {}

let configuration = null
let cancelFlag = false
let opened = false
let processing = false
let closing = false
let openedFromOutside = false
let openSettings = null

module.exports = {
  config,
  prepareForCapture,
  scanPairingCode,
  scanMainQR,
  scanPDF417,
  scanPhotoCard,
  takeFacephoto,
  cancel,
  isOpened,
  scanPK,
  hasCamera
}

const maxCamResolutions = [
  {
    width: 2592,
    height: 1944
  }
]

const minCamResolutions = [
  {
    width: 1280,
    height: 1024
  },
  {
    width: 1280,
    height: 960
  },
  {
    width: 1280,
    height: 720
  },
  {
    width: 640,
    height: 480
  }
]

const maxCamResolutionQRCode = [
  {
    width: 1300,
    height: 1300
  }
]

function setConfig (cam, mode) {
  const isQRCodeMode = mode === 'qr'

  var format = null
  const cachedRes = selectedCamResolutions[mode]
  if (!_.isNil(cachedRes)) return cachedRes

  const pixelRes = format => format.width * format.height
  const sortByPixelRes = _.orderBy(pixelRes, ['desc'])

  const isSuitableRes = res => {
    const currentRes = pixelRes(res)

    const gteCurrent = _.gte(currentRes)
    const lteCurrent = _.lte(currentRes)

    const isAboveMinAcceptableResolutions = _.compose(_.some(_.identity), _.map(gteCurrent), _.map(pixelRes))
    const isUnderMaxAcceptableResolutions = _.compose(_.some(_.identity), _.map(lteCurrent), _.map(pixelRes))

    return isUnderMaxAcceptableResolutions(isQRCodeMode ? maxCamResolutionQRCode : maxCamResolutions) &&
     isAboveMinAcceptableResolutions(minCamResolutions)
  }

  const availableFormats = sortByPixelRes(cam.formats.filter(f => f.formatName === 'MJPG'))

  format = _.find(isSuitableRes, availableFormats)

  if (!format) throw new Error('Unsupported cam resolution!')

  selectedCamResolutions[mode] = format
  console.log('DEBUG: ** Best resolution selected! ** Format: ', format)
  console.log('DEBUG: ** Cached resolutions ** : ', selectedCamResolutions)

  return format
}

function config (_configuration) {
  configuration = _configuration

  const licenses = _.get('scanner.manatee.license', configuration)
  manatee.register('qr', _.get('qr.name', licenses), _.get('qr.key', licenses))
  manatee.register('pdf417', _.get('pdf417.name', licenses), _.get('pdf417.key', licenses))
}

function cancel () {
  cancelFlag = true
}

function isOpened () {
  return opened
}

function hasCamera (mode) {
  const cameraConf = mode === 'facephoto' ? 'frontFacingCamera' : 'scanner'

  const device = _.get(`${cameraConf}.device`, configuration)

  if (!device) return false

  console.log('Camera device available: ' + device)
  try {
    new v4l2camera.Camera(device)
    return true
  } catch (err) {
    return false
  }
}
// resultCallback returns final scan result
// captureCallback returns result of a frame capture
function scan (mode, resultCallback, captureCallback) {
  if (!openedFromOutside && opened) {
    console.log("Camera is already open. Shouldn't happen.")
    return resultCallback(new Error('Camera open'))
  }

  const {
    cam,
    modeConfig,
    width,
    height
  } = _prepareForCapture(mode)

  var handle = setInterval(capture, 100)

  function capture () {
    if (cancelFlag) {
      if (opened && !closing && !processing) {
        closing = true
        clearInterval(handle)
        cam.stop(() => {
          processing = false
          opened = false
          closing = false
          openedFromOutside = false
          openSettings = null
          return resultCallback()
        })
      }

      return
    }

    if (processing) return
    processing = true

    cam.capture(function (success) {
      if (!success) {
        processing = false
        return
      }

      var frame = Buffer.from(cam.frameRaw())
      let greyscale = null

      try {
        greyscale = jpg.decompressSync(frame, { format: jpg.FORMAT_GRAY })
      } catch (err) {
        processing = false
        return
      }

      captureCallback(modeConfig, width, height, frame, greyscale.data, function (err, result) {
        if (!err && !result && !cancelFlag) {
          processing = false
          openedFromOutside = false
          openSettings = null
          return
        }

        clearInterval(handle)
        cam.stop(() => {
          processing = false
          opened = false
          openedFromOutside = false
          openSettings = null
          return resultCallback(err, result)
        })
      })
    })
  }
}

function scanQR (callback) {
  scan('qr', callback, function (modeConfig, width, height, frame, greyscale, _callback) {
    const rgba8 = jpg.decompressSync(frame, { format: jpg.FORMAT_RGBA8 })
    const r = jsQR(rgba8.data, rgba8.width, rgba8.height, { inversionAttempts: 'dontInvert' })
    if (!r) return _callback()
    _callback(null, r.data)
  })
}

function scanPDF417 (callback) {
  scan('photoId', callback, function (modeConfig, width, height, frame, greyscale, _callback) {
    var result = manatee.scanPDF417(greyscale, width, height)
    if (!result) return _callback(null, null)
    var parsed = Pdf417Parser.parse(result)
    if (!parsed) return _callback(null, null)
    parsed.raw = result.toString()
    _callback(null, parsed)
  })
}

function scanPhoto (callback) {
  scan('photoId', callback, function (modeConfig, width, height, frame, greyscale, _callback) {
    const detected = supyo.detect(greyscale, width, height, {
      minSize: modeConfig.minFaceSize || 100,
      qualityThreshold: modeConfig.threshold || 20,
      verbose: false
    })

    if (!detected) return _callback(null, null)
    _callback(null, frame)
  })
}

function scanFacephoto (callback) {
  scan('facephoto', callback, function (modeConfig, width, height, frame, greyscale, _callback) {
    const detected = supyo.detect(greyscale, width, height, {
      minSize: modeConfig.minFaceSize || 100,
      qualityThreshold: modeConfig.threshold || 20,
      verbose: false
    })

    if (!detected) return _callback(null, null)
    _callback(null, frame)
  })
}

function scanPairingCode (callback) {
  scanQR((err, res) => {
    if (err) return callback(err)
    if (!res) return callback()
    return callback(null, res.toString())
  })
}

function scanMainQR (cryptoCode, callback) {
  scanQR(function (err, result) {
    if (err) return callback(err)
    if (!result) return callback(null, null)

    var resultStr = result.toString()
    console.log('DEBUG55: %s', resultStr)

    const network = 'main'
    try {
      callback(null, coinUtils.parseUrl(cryptoCode, network, resultStr))
    } catch (error) {
      callback(error)
    }
  })
}

function scanPK (callback) {
  scanQR(function (err, result) {
    if (err) return callback(err)
    if (!result) return callback(null, null)

    callback(null, result.toString())
  })
}

function scanPhotoCard (callback) {
  scanPhoto(function (err, result) {
    if (err) return callback(err)
    if (!result) return callback(null, null)

    callback(null, result)
  })
}

function takeFacephoto (callback) {
  scanFacephoto(function (err, result) {
    if (err) return callback(err)
    if (!result) return callback(null, null)

    callback(null, result)
  })
}

function _prepareForCapture (mode) {
  if (opened) return openSettings
  // Pick correct camera
  const cameraConf = mode === 'facephoto' ? 'frontFacingCamera' : 'scanner'

  const device = _.get([cameraConf, 'device'], configuration)
  const modeConfig = _.get([cameraConf, mode], configuration)

  const cam = new v4l2camera.Camera(device)

  cancelFlag = false

  const format = setConfig(cam, mode)

  cam.configSet(format)

  const width = format.width
  const height = format.height

  cam.start()

  opened = true
  openSettings = {
    cam,
    modeConfig,
    width,
    height
  }
  return openSettings
}

function prepareForCapture (mode) {
  openedFromOutside = true
  _prepareForCapture(mode)
}
<commit_msg>fix: remove debug logs<commit_after>const _ = require('lodash/fp')
const Pdf417Parser = require('./compliance/parsepdf417')
const coinUtils = require('./coins/utils')
const v4l2camera = require('v4l2camera')
const jpg = require('jpeg-turbo')
const manatee = require('manatee')
const supyo = require('@lamassu/supyo')
const jsQR = require('jsqr')

const selectedCamResolutions = {}

let configuration = null
let cancelFlag = false
let opened = false
let processing = false
let closing = false
let openedFromOutside = false
let openSettings = null

module.exports = {
  config,
  prepareForCapture,
  scanPairingCode,
  scanMainQR,
  scanPDF417,
  scanPhotoCard,
  takeFacephoto,
  cancel,
  isOpened,
  scanPK,
  hasCamera
}

const maxCamResolutions = [
  {
    width: 2592,
    height: 1944
  }
]

const minCamResolutions = [
  {
    width: 1280,
    height: 1024
  },
  {
    width: 1280,
    height: 960
  },
  {
    width: 1280,
    height: 720
  },
  {
    width: 640,
    height: 480
  }
]

const maxCamResolutionQRCode = [
  {
    width: 1300,
    height: 1300
  }
]

function setConfig (cam, mode) {
  const isQRCodeMode = mode === 'qr'

  var format = null
  const cachedRes = selectedCamResolutions[mode]
  if (!_.isNil(cachedRes)) return cachedRes

  const pixelRes = format => format.width * format.height
  const sortByPixelRes = _.orderBy(pixelRes, ['desc'])

  const isSuitableRes = res => {
    const currentRes = pixelRes(res)

    const gteCurrent = _.gte(currentRes)
    const lteCurrent = _.lte(currentRes)

    const isAboveMinAcceptableResolutions = _.compose(_.some(_.identity), _.map(gteCurrent), _.map(pixelRes))
    const isUnderMaxAcceptableResolutions = _.compose(_.some(_.identity), _.map(lteCurrent), _.map(pixelRes))

    return isUnderMaxAcceptableResolutions(isQRCodeMode ? maxCamResolutionQRCode : maxCamResolutions) &&
     isAboveMinAcceptableResolutions(minCamResolutions)
  }

  const availableFormats = sortByPixelRes(cam.formats.filter(f => f.formatName === 'MJPG'))

  format = _.find(isSuitableRes, availableFormats)

  if (!format) throw new Error('Unsupported cam resolution!')

  selectedCamResolutions[mode] = format

  return format
}

function config (_configuration) {
  configuration = _configuration

  const licenses = _.get('scanner.manatee.license', configuration)
  manatee.register('qr', _.get('qr.name', licenses), _.get('qr.key', licenses))
  manatee.register('pdf417', _.get('pdf417.name', licenses), _.get('pdf417.key', licenses))
}

function cancel () {
  cancelFlag = true
}

function isOpened () {
  return opened
}

function hasCamera (mode) {
  const cameraConf = mode === 'facephoto' ? 'frontFacingCamera' : 'scanner'

  const device = _.get(`${cameraConf}.device`, configuration)

  if (!device) return false

  console.log('Camera device available: ' + device)
  try {
    new v4l2camera.Camera(device)
    return true
  } catch (err) {
    return false
  }
}
// resultCallback returns final scan result
// captureCallback returns result of a frame capture
function scan (mode, resultCallback, captureCallback) {
  if (!openedFromOutside && opened) {
    console.log("Camera is already open. Shouldn't happen.")
    return resultCallback(new Error('Camera open'))
  }

  const {
    cam,
    modeConfig,
    width,
    height
  } = _prepareForCapture(mode)

  var handle = setInterval(capture, 100)

  function capture () {
    if (cancelFlag) {
      if (opened && !closing && !processing) {
        closing = true
        clearInterval(handle)
        cam.stop(() => {
          processing = false
          opened = false
          closing = false
          openedFromOutside = false
          openSettings = null
          return resultCallback()
        })
      }

      return
    }

    if (processing) return
    processing = true

    cam.capture(function (success) {
      if (!success) {
        processing = false
        return
      }

      var frame = Buffer.from(cam.frameRaw())
      let greyscale = null

      try {
        greyscale = jpg.decompressSync(frame, { format: jpg.FORMAT_GRAY })
      } catch (err) {
        processing = false
        return
      }

      captureCallback(modeConfig, width, height, frame, greyscale.data, function (err, result) {
        if (!err && !result && !cancelFlag) {
          processing = false
          openedFromOutside = false
          openSettings = null
          return
        }

        clearInterval(handle)
        cam.stop(() => {
          processing = false
          opened = false
          openedFromOutside = false
          openSettings = null
          return resultCallback(err, result)
        })
      })
    })
  }
}

function scanQR (callback) {
  scan('qr', callback, function (modeConfig, width, height, frame, greyscale, _callback) {
    const rgba8 = jpg.decompressSync(frame, { format: jpg.FORMAT_RGBA8 })
    const r = jsQR(rgba8.data, rgba8.width, rgba8.height, { inversionAttempts: 'dontInvert' })
    if (!r) return _callback()
    _callback(null, r.data)
  })
}

function scanPDF417 (callback) {
  scan('photoId', callback, function (modeConfig, width, height, frame, greyscale, _callback) {
    var result = manatee.scanPDF417(greyscale, width, height)
    if (!result) return _callback(null, null)
    var parsed = Pdf417Parser.parse(result)
    if (!parsed) return _callback(null, null)
    parsed.raw = result.toString()
    _callback(null, parsed)
  })
}

function scanPhoto (callback) {
  scan('photoId', callback, function (modeConfig, width, height, frame, greyscale, _callback) {
    const detected = supyo.detect(greyscale, width, height, {
      minSize: modeConfig.minFaceSize || 100,
      qualityThreshold: modeConfig.threshold || 20,
      verbose: false
    })

    if (!detected) return _callback(null, null)
    _callback(null, frame)
  })
}

function scanFacephoto (callback) {
  scan('facephoto', callback, function (modeConfig, width, height, frame, greyscale, _callback) {
    const detected = supyo.detect(greyscale, width, height, {
      minSize: modeConfig.minFaceSize || 100,
      qualityThreshold: modeConfig.threshold || 20,
      verbose: false
    })

    if (!detected) return _callback(null, null)
    _callback(null, frame)
  })
}

function scanPairingCode (callback) {
  scanQR((err, res) => {
    if (err) return callback(err)
    if (!res) return callback()
    return callback(null, res.toString())
  })
}

function scanMainQR (cryptoCode, callback) {
  scanQR(function (err, result) {
    if (err) return callback(err)
    if (!result) return callback(null, null)

    var resultStr = result.toString()
    console.log('DEBUG55: %s', resultStr)

    const network = 'main'
    try {
      callback(null, coinUtils.parseUrl(cryptoCode, network, resultStr))
    } catch (error) {
      callback(error)
    }
  })
}

function scanPK (callback) {
  scanQR(function (err, result) {
    if (err) return callback(err)
    if (!result) return callback(null, null)

    callback(null, result.toString())
  })
}

function scanPhotoCard (callback) {
  scanPhoto(function (err, result) {
    if (err) return callback(err)
    if (!result) return callback(null, null)

    callback(null, result)
  })
}

function takeFacephoto (callback) {
  scanFacephoto(function (err, result) {
    if (err) return callback(err)
    if (!result) return callback(null, null)

    callback(null, result)
  })
}

function _prepareForCapture (mode) {
  if (opened) return openSettings
  // Pick correct camera
  const cameraConf = mode === 'facephoto' ? 'frontFacingCamera' : 'scanner'

  const device = _.get([cameraConf, 'device'], configuration)
  const modeConfig = _.get([cameraConf, mode], configuration)

  const cam = new v4l2camera.Camera(device)

  cancelFlag = false

  const format = setConfig(cam, mode)

  cam.configSet(format)

  console.log('Opening v4l2camera device: ' + device + ' with mode: ' + mode, format)

  const width = format.width
  const height = format.height

  cam.start()

  opened = true
  openSettings = {
    cam,
    modeConfig,
    width,
    height
  }
  return openSettings
}

function prepareForCapture (mode) {
  openedFromOutside = true
  _prepareForCapture(mode)
}
<|endoftext|>


<commit_before>/*!
 * Lisp syntax scanner
 */

exports.tokenize = function tokenize(input) {
  var currentPosition = -1;
  var buffer = [];
  var tokens = [];
  var seperators = [' ', '(', ')', '\n'];
  var currLine = 1;

  function peek() {
    var nextIndex = currentPosition + 1;

    if (nextIndex >= input.length) {
      return 'EOF';
    } else {
      return input.charAt(nextIndex);
    }
  }

  function next() {
    var nextChar = peek();
    currentPosition += 1;
    return nextChar;
  }

  function clearBuffer() {
    var token = buffer.join('');
    var currToken = { token: token, type: null, line: currLine };
    var skip = false;

    // FIXME: refactor me
    if (token === '') {
      return;
    }

    if (token === '(') {
      currToken.type = 'L_PAREN';
    } else if (token === ')') {
      currToken.type = 'R_PAREN';
    } else if (/\n/.test(token)) {
      currLine += 1;
      skip = true;
    } else if (/\s/.test(token)) {
      // whitespace is ignored
      skip = true;
    } else if (token === ';') {
      currToken.type = 'COMMENT';
    } else if (/\d/.test(token)) {
      currToken.type = 'NUMBER';
    } else if (/(["']).*\1/.test(token)) {
      currToken.token = token.substring(1, token.length - 1);
      currToken.type = 'STRING';
    } else {
      currToken.type = 'SYMBOL';
    }

    if (!skip) {
      tokens.push(currToken);
    } else {
      skip = false;
    }
    buffer = [];
  }

  function read() {
    while (peek() !== 'EOF') {
      var nextChar = peek();

      if (seperators.indexOf(nextChar) !== -1) {
        clearBuffer();
      }

      buffer.push(nextChar);

      if (seperators.indexOf(nextChar) !== -1) {
        clearBuffer();
      }
      next();
    }
    return tokens;
  }

  return read(input);
};
<commit_msg>Fix issues with primitive values<commit_after>/*!
 * Lisp syntax scanner
 */

exports.tokenize = function tokenize(input) {
  var currentPosition = -1;
  var buffer = [];
  var tokens = [];
  var seperators = [' ', '(', ')', '\n'];
  var currLine = 1;

  function peek() {
    var nextIndex = currentPosition + 1;

    if (nextIndex >= input.length) {
      return 'EOF';
    } else {
      return input.charAt(nextIndex);
    }
  }

  function next() {
    var nextChar = peek();
    currentPosition += 1;
    return nextChar;
  }

  function clearBuffer() {
    var token = buffer.join('');
    var currToken = { token: token, type: null, line: currLine };
    var skip = false;

    // FIXME: refactor me
    if (token === '') {
      return;
    }

    if (token === '(') {
      currToken.type = 'L_PAREN';
    } else if (token === ')') {
      currToken.type = 'R_PAREN';
    } else if (token === '.') {
      currToken.type = 'DOT';
    } else if (/\n/.test(token)) {
      currLine += 1;
      skip = true;
    } else if (/\s/.test(token)) {
      // whitespace is ignored
      skip = true;
    } else if (token === ';') {
      currToken.type = 'COMMENT';
    } else if (/\d/.test(token)) {
      currToken.type = 'NUMBER';
    } else if (/(["']).*\1/.test(token)) {
      currToken.token = token.substring(1, token.length - 1);
      currToken.type = 'STRING';
    } else {
      currToken.type = 'SYMBOL';
    }

    if (!skip) {
      tokens.push(currToken);
    } else {
      skip = false;
    }
    buffer = [];
  }

  function read() {
    while (peek() !== 'EOF') {
      var nextChar = peek();

      if (seperators.indexOf(nextChar) !== -1) {
        clearBuffer();
      }

      buffer.push(nextChar);

      if (seperators.indexOf(nextChar) !== -1) {
        clearBuffer();
      }
      next();
    }
    clearBuffer();
    return tokens;
  }

  return read(input);
};
<|endoftext|>


<commit_before>const omit = require('lodash.omit');
const isEqual = require('lodash.isequal');
const Proto = require('uberproto');
const filter = require('feathers-query-filters');
const { select } = require('feathers-commons');
const errors = require('feathers-errors');
const errorHandler = require('./error-handler');

// Create the service.
class Service {
  constructor (options) {
    if (!options) {
      throw new Error('Mongoose options have to be provided');
    }

    if (!options.Model || !options.Model.modelName) {
      throw new Error('You must provide a Mongoose Model');
    }

    this.Model = options.Model;
    this.discriminatorKey = this.Model.schema.options.discriminatorKey;
    this.discriminators = {};
    (options.discriminators || []).forEach(element => {
      if (element.modelName) {
        this.discriminators[element.modelName] = element;
      }
    });
    this.id = options.id || '_id';
    this.bulkErrorsKey = options.bulkErrorsKey || 'errors';
    this.paginate = options.paginate || {};
    this.lean = options.lean === undefined ? true : options.lean;
    this.overwrite = options.overwrite !== false;
    this.events = options.events || [];
  }

  extend (obj) {
    return Proto.extend(obj, this);
  }

  _find (params, count, getFilter = filter) {
    const { filters, query } = getFilter(params.query || {});
    const discriminator = (params.query || {})[this.discriminatorKey] || this.discriminatorKey;
    const model = this.discriminators[discriminator] || this.Model;
    const q = model.find(query).lean(this.lean);

    // $select uses a specific find syntax, so it has to come first.
    if (Array.isArray(filters.$select)) {
      let fields = {};

      for (let key of filters.$select) {
        fields[key] = 1;
      }

      q.select(fields);
    } else if (typeof filters.$select === 'string' || typeof filters.$select === 'object') {
      q.select(filters.$select);
    }

    // Handle $sort
    if (filters.$sort) {
      q.sort(filters.$sort);
    }

    // Handle $limit
    if (typeof filters.$limit !== 'undefined') {
      q.limit(filters.$limit);
    }

    // Handle $skip
    if (filters.$skip) {
      q.skip(filters.$skip);
    }

    // Handle $populate
    if (filters.$populate) {
      q.populate(filters.$populate);
    }

    let executeQuery = total => {
      return q.exec().then(data => {
        return {
          total,
          limit: filters.$limit,
          skip: filters.$skip || 0,
          data
        };
      });
    };

    if (filters.$limit === 0) {
      executeQuery = total => {
        return Promise.resolve({
          total,
          limit: filters.$limit,
          skip: filters.$skip || 0,
          data: []
        });
      };
    }

    if (count) {
      return model.where(query).count().exec().then(executeQuery);
    }

    return executeQuery();
  }

  find (params) {
    const paginate = (params && typeof params.paginate !== 'undefined') ? params.paginate : this.paginate;
    const result = this._find(params, !!paginate.default,
      query => filter(query, paginate)
    );

    if (!paginate.default) {
      return result.then(page => page.data);
    }

    return result;
  }

  _get (id, params = {}) {
    params.query = params.query || {};

    const discriminator = (params.query || {})[this.discriminatorKey] || this.discriminatorKey;
    const model = this.discriminators[discriminator] || this.Model;
    let modelQuery = model
      .findOne({ [this.id]: id });

    // Handle $populate
    if (params.query.$populate) {
      modelQuery = modelQuery.populate(params.query.$populate);
    }

    // Handle $select
    if (params.query.$select && params.query.$select.length) {
      let fields = { [this.id]: 1 };

      for (let key of params.query.$select) {
        fields[key] = 1;
      }

      modelQuery.select(fields);
    } else if (params.query.$select && typeof params.query.$select === 'object') {
      modelQuery.select(params.query.$select);
    }

    return modelQuery
      .lean(this.lean)
      .exec()
      .then(data => {
        if (!data) {
          throw new errors.NotFound(`No record found for id '${id}'`);
        }

        return data;
      })
      .catch(errorHandler);
  }

  get (id, params) {
    return this._get(id, params);
  }

  _getOrFind (id, params) {
    if (id === null) {
      return this._find(params).then(page => page.data);
    }

    return this._get(id, params);
  }

  create (data, params) {
    if (Array.isArray(data)) {
      return this._createbulk(data, params);
    }

    const discriminator = data[this.discriminatorKey] || this.discriminatorKey;
    const model = this.discriminators[discriminator] || this.Model;
    return model.create(data)
      .then(result => {
        if (this.lean) {
          if (Array.isArray(result)) {
            return result.map(item => (item.toObject ? item.toObject() : item));
          }
          return result.toObject ? result.toObject() : result;
        }
        return result;
      })
      .then(select(params, this.id))
      .catch(errorHandler);
  }

  _createbulk (data, params) {
    return this._insertMany(data)
      .then(({ data, errors }) => {
        if (!Array.isArray(data)) {
          return { data, errors };
        }
        data = data.map(result => (this.lean && result.toObject) ? result.toObject() : result);
        return { data, errors };
      })
      .then(({ data, errors }) => {
        // Check the errors exists
        if (!params[this.bulkErrorsKey]) {
          params[this.bulkErrorsKey] = [];
        }

        if (!Array.isArray(data)) {
          // Add the errors results to params.errors
          params[this.bulkErrorsKey] = params[this.bulkErrorsKey].concat(errors);

          return data;
        }
        data = data.map(result => select(params, this.id)(result));
        // Add the errors results to params.errors
        params[this.bulkErrorsKey] = params[this.bulkErrorsKey].concat(errors);

        return data;
      })
      .catch(errorHandler);
  }

  _insertMany (data) {
    const INSERT_MANY_CONVERT_OPTIONS = {
      depopulate: true,
      transform: false,
      _skipDepopulateTopLevel: true,
      flattenDecimals: false
    };
    const discriminator = data[this.discriminatorKey] || this.discriminatorKey;
    const Model = this.discriminators[discriminator] || this.Model;
    let errorDocs, successDocs;

    return new Promise((resolve, reject) => {
      if (!Array.isArray(data)) {
        data = [data];
      }

      // validate the docs against the mongoose schema
      const validate = function validate (doc) {
        return new Promise(resolve => {
          let model = new Model(doc);
          model.validate({ __noPromise: true }, error => {
            if (error) {
              // Resolve the error, so we can track it
              let errorDoc = new Error(error.message);
              errorDoc.data = doc;
              return resolve(errorDoc);
            }
            // resolve the validated model
            return resolve(model);
          });
        });
      };
      // Map the validate promises to be executed together
      let toExecute = data.map(validate);

      // Execute the validation promises
      Promise.all(toExecute)
      .then(docs => {
        // Filter out any errors
        successDocs = docs.filter(doc => {
          return (!(doc instanceof Error) && doc !== null);
        });
        // Get the errors
        errorDocs = docs.filter(doc => {
          return (doc instanceof Error);
        })
        .reduce((acc, cur) => {
          acc.push({ error: { type: 'ValidationError', message: cur.message }, data: cur.data });
          return acc;
        }, []);

        // Escape while there aren't any valid docs
        if (successDocs.length < 1) {
          resolve({ errors: errorDocs });
          return;
        }

        // parse the documents as per mongoose functionality
        var docObjects = successDocs.map(function (doc) {
          if (doc.schema.options.versionKey) {
            doc[doc.schema.options.versionKey] = 0;
          }
          if (doc.initializeTimestamps) {
            return doc.initializeTimestamps().toObject(INSERT_MANY_CONVERT_OPTIONS);
          }
          return doc.toObject(INSERT_MANY_CONVERT_OPTIONS);
        });

        // Run the native insertMany method
        // { ordered: false } ## ordered operations stop after an error, while unordered operations continue to process any remaining write operations in the queue.
        Model.collection.insertMany(docObjects, { ordered: false }, (error, docs) => {
          let writeErrors;
          // If we have an error, lets find which docs where successful and return those
          if (error) {
            // Get a list of the insertIds, which will be all of the docs
            // as mongoose creates _ids client-side
            docs = docs.toJSON().insertedIds;

            // Check if we have singular error
            if (!error.writeErrors) {
              let _error = error.toJSON();
              writeErrors = [{ error: { type: 'WriteError', message: _error.errmsg }, data: _error.op }];
            } else {
                // Get a list of the errors and the ids
                // so we can filter out the those that failed from the successful ones
              writeErrors = error.writeErrors.reduce((acc, cur) => {
                let error = cur.toJSON();
                acc.push({ error: { type: 'WriteError', message: error.errmsg }, data: error.op });
                return acc;
              }, []);
            }
          }

          // If we have failed documents, filter them out
          if (writeErrors) {
            successDocs = successDocs.reduce((acc, doc) => {
              // remove duplicate key docs
              let errorDoc = writeErrors.find(curError => {
                let data = doc.toJSON();
                return isEqual(curError.data, data) && curError.error.message.includes('duplicate key');
              });
              if (errorDoc) {
                return acc;
              }
              acc.push(doc);
              return acc;
            }, []);
            // Merge the validation errors with the write errors
            errorDocs = [...errorDocs, ...writeErrors];
          }

          // Map the mongoose methods to each document
          successDocs = successDocs.map(doc => {
            doc.isNew = false;
            doc.emit('isNew', false);
            doc.constructor.emit('isNew', false);
            return doc;
          });

          errorDocs = errorDocs.length ? errorDocs : null;
          successDocs = successDocs.length ? successDocs : null;

          // return combination of success and error documents
          resolve({ data: successDocs, errors: errorDocs });
        });
      })
      .catch(err => {
        reject(err);
      });
    });
  }

  update (id, data, params) {
    if (id === null) {
      return Promise.reject(new errors.BadRequest('Not replacing multiple records. Did you mean `patch`?'));
    }

    // Handle case where data might be a mongoose model
    if (typeof data.toObject === 'function') {
      data = data.toObject();
    }

    const options = Object.assign({
      new: true,
      overwrite: this.overwrite,
      runValidators: true,
      context: 'query',
      setDefaultsOnInsert: true
    }, params.mongoose);

    if (this.id === '_id') {
      // We can not update default mongo ids
      data = omit(data, this.id);
    } else {
      // If not using the default Mongo _id field set the id to its
      // previous value. This prevents orphaned documents.
      data = Object.assign({}, data, { [this.id]: id });
    }

    const discriminator = (params.query || {})[this.discriminatorKey] || this.discriminatorKey;
    const model = this.discriminators[discriminator] || this.Model;
    let modelQuery = model.findOneAndUpdate({ [this.id]: id }, data, options);

    if (params && params.query && params.query.$populate) {
      modelQuery = modelQuery.populate(params.query.$populate);
    }

    return modelQuery
      .lean(this.lean)
      .exec()
      .then(select(params, this.id))
      .catch(errorHandler);
  }

  patch (id, data, params) {
    const query = Object.assign({}, filter(params.query || {}).query);
    const mapIds = page => page.data.map(current => current[this.id]);

    // By default we will just query for the one id. For multi patch
    // we create a list of the ids of all items that will be changed
    // to re-query them after the update
    const ids = id === null ? this._find(params)
        .then(mapIds) : Promise.resolve([ id ]);

    // Handle case where data might be a mongoose model
    if (typeof data.toObject === 'function') {
      data = data.toObject();
    }

    // ensure we are working on a copy
    data = Object.assign({}, data);

    // If we are updating multiple records
    let options = Object.assign({
      multi: id === null,
      runValidators: true,
      context: 'query'
    }, params.mongoose);

    if (id !== null) {
      query[this.id] = id;
    }

    if (this.id === '_id') {
      // We can not update default mongo ids
      delete data[this.id];
    } else if (id !== null) {
      // If not using the default Mongo _id field set the id to its
      // previous value. This prevents orphaned documents.
      data[this.id] = id;
    }

    // NOTE (EK): We need this shitty hack because update doesn't
    // return a promise properly when runValidators is true. WTF!
    try {
      return ids
        .then(idList => {
          // Create a new query that re-queries all ids that
          // were originally changed
          const findParams = idList.length ? Object.assign({}, params, {
            query: {
              [this.id]: { $in: idList }
            }
          }) : params;

          if (params.query && params.query.$populate) {
            findParams.query.$populate = params.query.$populate;
          }

          // If params.query.$populate was provided, remove it
          // from the query sent to mongoose.
          const discriminator = (params.query || {})[this.discriminatorKey] || this.discriminatorKey;
          const model = this.discriminators[discriminator] || this.Model;
          return model
            .update(omit(query, '$populate'), data, options)
            .lean(this.lean)
            .exec()
            .then(() => this._getOrFind(id, findParams));
        })
        .then(select(params, this.id))
        .catch(errorHandler);
    } catch (e) {
      return errorHandler(e);
    }
  }

  remove (id, params) {
    const query = Object.assign({}, filter(params.query || {}).query);

    if (id !== null) {
      query[this.id] = id;
    }

    // NOTE (EK): First fetch the record(s) so that we can return
    // it/them when we delete it/them.
    return this._getOrFind(id, params)
      .then(data =>
        this.Model
          .remove(query)
          .lean(this.lean)
          .exec()
          .then(() => data)
          .then(select(params, this.id))
      )
      .catch(errorHandler);
  }
}

const init = function init (options) {
  return new Service(options);
};

init.Service = Service;

module.exports = init;
<commit_msg>Fixed issue with null errors being pushed into params.errors<commit_after>const omit = require('lodash.omit');
const isEqual = require('lodash.isequal');
const Proto = require('uberproto');
const filter = require('feathers-query-filters');
const { select } = require('feathers-commons');
const errors = require('feathers-errors');
const errorHandler = require('./error-handler');

// Create the service.
class Service {
  constructor (options) {
    if (!options) {
      throw new Error('Mongoose options have to be provided');
    }

    if (!options.Model || !options.Model.modelName) {
      throw new Error('You must provide a Mongoose Model');
    }

    this.Model = options.Model;
    this.discriminatorKey = this.Model.schema.options.discriminatorKey;
    this.discriminators = {};
    (options.discriminators || []).forEach(element => {
      if (element.modelName) {
        this.discriminators[element.modelName] = element;
      }
    });
    this.id = options.id || '_id';
    this.bulkErrorsKey = options.bulkErrorsKey || 'errors';
    this.paginate = options.paginate || {};
    this.lean = options.lean === undefined ? true : options.lean;
    this.overwrite = options.overwrite !== false;
    this.events = options.events || [];
  }

  extend (obj) {
    return Proto.extend(obj, this);
  }

  _find (params, count, getFilter = filter) {
    const { filters, query } = getFilter(params.query || {});
    const discriminator = (params.query || {})[this.discriminatorKey] || this.discriminatorKey;
    const model = this.discriminators[discriminator] || this.Model;
    const q = model.find(query).lean(this.lean);

    // $select uses a specific find syntax, so it has to come first.
    if (Array.isArray(filters.$select)) {
      let fields = {};

      for (let key of filters.$select) {
        fields[key] = 1;
      }

      q.select(fields);
    } else if (typeof filters.$select === 'string' || typeof filters.$select === 'object') {
      q.select(filters.$select);
    }

    // Handle $sort
    if (filters.$sort) {
      q.sort(filters.$sort);
    }

    // Handle $limit
    if (typeof filters.$limit !== 'undefined') {
      q.limit(filters.$limit);
    }

    // Handle $skip
    if (filters.$skip) {
      q.skip(filters.$skip);
    }

    // Handle $populate
    if (filters.$populate) {
      q.populate(filters.$populate);
    }

    let executeQuery = total => {
      return q.exec().then(data => {
        return {
          total,
          limit: filters.$limit,
          skip: filters.$skip || 0,
          data
        };
      });
    };

    if (filters.$limit === 0) {
      executeQuery = total => {
        return Promise.resolve({
          total,
          limit: filters.$limit,
          skip: filters.$skip || 0,
          data: []
        });
      };
    }

    if (count) {
      return model.where(query).count().exec().then(executeQuery);
    }

    return executeQuery();
  }

  find (params) {
    const paginate = (params && typeof params.paginate !== 'undefined') ? params.paginate : this.paginate;
    const result = this._find(params, !!paginate.default,
      query => filter(query, paginate)
    );

    if (!paginate.default) {
      return result.then(page => page.data);
    }

    return result;
  }

  _get (id, params = {}) {
    params.query = params.query || {};

    const discriminator = (params.query || {})[this.discriminatorKey] || this.discriminatorKey;
    const model = this.discriminators[discriminator] || this.Model;
    let modelQuery = model
      .findOne({ [this.id]: id });

    // Handle $populate
    if (params.query.$populate) {
      modelQuery = modelQuery.populate(params.query.$populate);
    }

    // Handle $select
    if (params.query.$select && params.query.$select.length) {
      let fields = { [this.id]: 1 };

      for (let key of params.query.$select) {
        fields[key] = 1;
      }

      modelQuery.select(fields);
    } else if (params.query.$select && typeof params.query.$select === 'object') {
      modelQuery.select(params.query.$select);
    }

    return modelQuery
      .lean(this.lean)
      .exec()
      .then(data => {
        if (!data) {
          throw new errors.NotFound(`No record found for id '${id}'`);
        }

        return data;
      })
      .catch(errorHandler);
  }

  get (id, params) {
    return this._get(id, params);
  }

  _getOrFind (id, params) {
    if (id === null) {
      return this._find(params).then(page => page.data);
    }

    return this._get(id, params);
  }

  create (data, params) {
    if (Array.isArray(data)) {
      return this._createbulk(data, params);
    }

    const discriminator = data[this.discriminatorKey] || this.discriminatorKey;
    const model = this.discriminators[discriminator] || this.Model;
    return model.create(data)
      .then(result => {
        if (this.lean) {
          if (Array.isArray(result)) {
            return result.map(item => (item.toObject ? item.toObject() : item));
          }
          return result.toObject ? result.toObject() : result;
        }
        return result;
      })
      .then(select(params, this.id))
      .catch(errorHandler);
  }

  _createbulk (data, params) {
    return this._insertMany(data)
      .then(({ data, errors }) => {
        if (!Array.isArray(data)) {
          return { data, errors };
        }
        data = data.map(result => (this.lean && result.toObject) ? result.toObject() : result);
        return { data, errors };
      })
      .then(({ data, errors }) => {
        // Check the errors exists
        if (!params[this.bulkErrorsKey]) {
          params[this.bulkErrorsKey] = [];
        }

        if (!Array.isArray(data) && errors && errors.length) {
          // Add the errors results to params.errors
          params[this.bulkErrorsKey] = params[this.bulkErrorsKey].concat(errors);

          return data;
        }
        data = data.map(result => select(params, this.id)(result));
        if (errors && errors.length) {
          // Add the errors results to params.errors
          params[this.bulkErrorsKey] = params[this.bulkErrorsKey].concat(errors);
        }

        return data;
      })
      .catch(errorHandler);
  }

  _insertMany (data) {
    const INSERT_MANY_CONVERT_OPTIONS = {
      depopulate: true,
      transform: false,
      _skipDepopulateTopLevel: true,
      flattenDecimals: false
    };
    const discriminator = data[this.discriminatorKey] || this.discriminatorKey;
    const Model = this.discriminators[discriminator] || this.Model;
    let errorDocs, successDocs;

    return new Promise((resolve, reject) => {
      if (!Array.isArray(data)) {
        data = [data];
      }

      // validate the docs against the mongoose schema
      const validate = function validate (doc) {
        return new Promise(resolve => {
          let model = new Model(doc);
          model.validate({ __noPromise: true }, error => {
            if (error) {
              // Resolve the error, so we can track it
              let errorDoc = new Error(error.message);
              errorDoc.data = doc;
              return resolve(errorDoc);
            }
            // resolve the validated model
            return resolve(model);
          });
        });
      };
      // Map the validate promises to be executed together
      let toExecute = data.map(validate);

      // Execute the validation promises
      Promise.all(toExecute)
      .then(docs => {
        // Filter out any errors
        successDocs = docs.filter(doc => {
          return (!(doc instanceof Error) && doc !== null);
        });
        // Get the errors
        errorDocs = docs.filter(doc => {
          return (doc instanceof Error);
        })
        .reduce((acc, cur) => {
          acc.push({ error: { type: 'ValidationError', message: cur.message }, data: cur.data });
          return acc;
        }, []);

        // Escape while there aren't any valid docs
        if (successDocs.length < 1) {
          resolve({ errors: errorDocs });
          return;
        }

        // parse the documents as per mongoose functionality
        var docObjects = successDocs.map(function (doc) {
          if (doc.schema.options.versionKey) {
            doc[doc.schema.options.versionKey] = 0;
          }
          if (doc.initializeTimestamps) {
            return doc.initializeTimestamps().toObject(INSERT_MANY_CONVERT_OPTIONS);
          }
          return doc.toObject(INSERT_MANY_CONVERT_OPTIONS);
        });

        // Run the native insertMany method
        // { ordered: false } ## ordered operations stop after an error, while unordered operations continue to process any remaining write operations in the queue.
        Model.collection.insertMany(docObjects, { ordered: false }, (error, docs) => {
          let writeErrors;
          // If we have an error, lets find which docs where successful and return those
          if (error) {
            // Get a list of the insertIds, which will be all of the docs
            // as mongoose creates _ids client-side
            docs = docs.toJSON().insertedIds;

            // Check if we have singular error
            if (!error.writeErrors) {
              let _error = error.toJSON();
              writeErrors = [{ error: { type: 'WriteError', message: _error.errmsg }, data: _error.op }];
            } else {
                // Get a list of the errors and the ids
                // so we can filter out the those that failed from the successful ones
              writeErrors = error.writeErrors.reduce((acc, cur) => {
                let error = cur.toJSON();
                acc.push({ error: { type: 'WriteError', message: error.errmsg }, data: error.op });
                return acc;
              }, []);
            }
          }

          // If we have failed documents, filter them out
          if (writeErrors) {
            successDocs = successDocs.reduce((acc, doc) => {
              // remove duplicate key docs
              let errorDoc = writeErrors.find(curError => {
                let data = doc.toJSON();
                return isEqual(curError.data, data) && curError.error.message.includes('duplicate key');
              });
              if (errorDoc) {
                return acc;
              }
              acc.push(doc);
              return acc;
            }, []);
            // Merge the validation errors with the write errors
            errorDocs = [...errorDocs, ...writeErrors];
          }

          // Map the mongoose methods to each document
          successDocs = successDocs.map(doc => {
            doc.isNew = false;
            doc.emit('isNew', false);
            doc.constructor.emit('isNew', false);
            return doc;
          });

          errorDocs = errorDocs.length ? errorDocs : null;
          successDocs = successDocs.length ? successDocs : null;

          // return combination of success and error documents
          resolve({ data: successDocs, errors: errorDocs });
        });
      })
      .catch(err => {
        reject(err);
      });
    });
  }

  update (id, data, params) {
    if (id === null) {
      return Promise.reject(new errors.BadRequest('Not replacing multiple records. Did you mean `patch`?'));
    }

    // Handle case where data might be a mongoose model
    if (typeof data.toObject === 'function') {
      data = data.toObject();
    }

    const options = Object.assign({
      new: true,
      overwrite: this.overwrite,
      runValidators: true,
      context: 'query',
      setDefaultsOnInsert: true
    }, params.mongoose);

    if (this.id === '_id') {
      // We can not update default mongo ids
      data = omit(data, this.id);
    } else {
      // If not using the default Mongo _id field set the id to its
      // previous value. This prevents orphaned documents.
      data = Object.assign({}, data, { [this.id]: id });
    }

    const discriminator = (params.query || {})[this.discriminatorKey] || this.discriminatorKey;
    const model = this.discriminators[discriminator] || this.Model;
    let modelQuery = model.findOneAndUpdate({ [this.id]: id }, data, options);

    if (params && params.query && params.query.$populate) {
      modelQuery = modelQuery.populate(params.query.$populate);
    }

    return modelQuery
      .lean(this.lean)
      .exec()
      .then(select(params, this.id))
      .catch(errorHandler);
  }

  patch (id, data, params) {
    const query = Object.assign({}, filter(params.query || {}).query);
    const mapIds = page => page.data.map(current => current[this.id]);

    // By default we will just query for the one id. For multi patch
    // we create a list of the ids of all items that will be changed
    // to re-query them after the update
    const ids = id === null ? this._find(params)
        .then(mapIds) : Promise.resolve([ id ]);

    // Handle case where data might be a mongoose model
    if (typeof data.toObject === 'function') {
      data = data.toObject();
    }

    // ensure we are working on a copy
    data = Object.assign({}, data);

    // If we are updating multiple records
    let options = Object.assign({
      multi: id === null,
      runValidators: true,
      context: 'query'
    }, params.mongoose);

    if (id !== null) {
      query[this.id] = id;
    }

    if (this.id === '_id') {
      // We can not update default mongo ids
      delete data[this.id];
    } else if (id !== null) {
      // If not using the default Mongo _id field set the id to its
      // previous value. This prevents orphaned documents.
      data[this.id] = id;
    }

    // NOTE (EK): We need this shitty hack because update doesn't
    // return a promise properly when runValidators is true. WTF!
    try {
      return ids
        .then(idList => {
          // Create a new query that re-queries all ids that
          // were originally changed
          const findParams = idList.length ? Object.assign({}, params, {
            query: {
              [this.id]: { $in: idList }
            }
          }) : params;

          if (params.query && params.query.$populate) {
            findParams.query.$populate = params.query.$populate;
          }

          // If params.query.$populate was provided, remove it
          // from the query sent to mongoose.
          const discriminator = (params.query || {})[this.discriminatorKey] || this.discriminatorKey;
          const model = this.discriminators[discriminator] || this.Model;
          return model
            .update(omit(query, '$populate'), data, options)
            .lean(this.lean)
            .exec()
            .then(() => this._getOrFind(id, findParams));
        })
        .then(select(params, this.id))
        .catch(errorHandler);
    } catch (e) {
      return errorHandler(e);
    }
  }

  remove (id, params) {
    const query = Object.assign({}, filter(params.query || {}).query);

    if (id !== null) {
      query[this.id] = id;
    }

    // NOTE (EK): First fetch the record(s) so that we can return
    // it/them when we delete it/them.
    return this._getOrFind(id, params)
      .then(data =>
        this.Model
          .remove(query)
          .lean(this.lean)
          .exec()
          .then(() => data)
          .then(select(params, this.id))
      )
      .catch(errorHandler);
  }
}

const init = function init (options) {
  return new Service(options);
};

init.Service = Service;

module.exports = init;
<|endoftext|>


<commit_before>"use strict";

var mdns = require('mdns'), debug = require('debug-levels')('koala-puree:service');
var readYaml = require('read-yaml');
var mDnsBrowser, globalDnsCache = {};

process.env.NODE_ENV = process.env.NODE_ENV || "development";

var Browser=function(){
	// first search for all possible mdns
	// locate the first dnscache
	// if not located, continue caching everything
	// once located, switch off all browser, and start listening to only dnscache
	function serviceUp(service){ // lets first filter out all loopback devices, these tests would fail during loopback cases then
		debug(`serviceup: ${service.type.name}`,service);
		if (service.type.name !== "koala-puree") { debug(`ignoring service, service name not koala-puree`); return; }
		if (!service.txtRecord || !service.txtRecord.version) { debug(`ignoring service, service.txtRecord does not exist`); return; }

		var host;
		for ( var i = 0; i< service.addresses.length; i++ ) {
			if ( !service.addresses[i].includes(':') ) {
				host = service.addresses[i];
			}
		}
		if ( !host ) { debug('ipv4 not found, ignoring service'); return; }
		var type = (globalDnsCache[service.txtRecord.name] = globalDnsCache[service.txtRecord.name] || {});
		var ver = type[service.txtRecord.version] = type[service.txtRecord.version] || {indices: [], services: {}};
		var id = service.host+":"+service.port;
		ver.services[id] = service;
		ver.indices.push(id);
		debug(`adding service to dns cache`);
	}
	function serviceDown(service){
		var type, ver;
		debug(`service ${service} is down`);
		if ((type = (globalDnsCache[service.type.name])) && (ver = type[service.txtRecord.version])) {
			var id = `${service.host}:${service.port}`;
			delete ver.services[id];
			ver[indices].filter(function(e){
				return e === id;
			})
		}
	}
	function browserError(err, service){
		console.log("browser error:", err, err.stack);
		console.log("service:", service);
	}
	if ( !mDnsBrowser ) {
		// mDnsBrowser = mdns.browseThemAll();
		// probably will require one more for loopback interface
		debug('creating service browser');
		// debug('')
		mDnsBrowser = new mdns.Browser(mdns.tcp('koala-puree'), {resolverSequence:[
			mdns.rst.DNSServiceResolve(),
			mdns.rst.DNSServiceGetAddrInfo({families:[4]})
			// mdns.rst.getaddrinfo({families:[4]})
			// ('DNSServiceGetAddrInfo' in mdns.dns_sd ? mdns.rst.DNSServiceGetAddrInfo : mdns.rst.getaddrinfo)({families:[4]})
		]})
		mDnsBrowser.on('serviceUp', serviceUp)
		mDnsBrowser.on('serviceChanged', serviceUp)
		mDnsBrowser.on('serviceDown', serviceDown)
		mDnsBrowser.on('error', browserError)
		debug('starting service browser');
		mDnsBrowser.start();
	}
}

Browser.prototype = {
	get: function(name, ver) {
		var time = new Date();
		// think of a way to refactor this into a generator, so we can just store that as a cache and yield everytime
		// hopeful usage would be:
		// var service = cache[service@version].next();
		// service.request(method, data, blabla);
		debug(`getting service(${name}@${ver})`);
		return new Promise(function(resolve, reject){
			function resolver() {
				debug('looping through browser caches')
				// keep trying for a specific amount of time, now hard coded, 1s
				if ( new Date() - time > 2000 ) { debug('timeout'); return reject(); }
				if ( name in globalDnsCache ) {
					debug("name is found in globaDnsCache");
					var topver = undefined;
					for ( let gver in globalDnsCache[name] ) {
						if ( require('semver').satisfies(gver, ver) ) {
							if ( topver === undefined || require('semver').gt(gver, topver)) {
								topver = gver;
							}
						}
					}
					if ( topver ) {
						debug("round robinning");
						// lets first do round robin
						let pool = globalDnsCache[name][topver];
						var c = ( (pool.lastCounter === null || pool.lastCount === undefined) ? 0 : pool.lastCounter);

						if ( c+1 > pool.indices.length ) {
							c = -1;
						}
						pool.lastCounter++;
						debug("pool is:"+ JSON.stringify(pool.services));
						debug("resolved with:"+ pool.services[pool.indices[c+1]])
						// TODO: handle cases where it is not found
						return resolve(pool.services[pool.indices[c+1]]);
					}
				}
				setTimeout(resolver, 30);
			}
			process.nextTick(resolver);
		});
	},
	cache: {}
}

var Service = function(name, ver, app){
	this._name = name;
	this._ver = ver;
	this._browser = new Browser();
	this._app = app;
}
var serviceConfig;
Service.prototype = {
	_retrieve: function () {
		debug(`retriving a service based on ${this._name}, ${this._ver}:start`);
		return this._browser.get(this._name, this._ver);
	},
	// TODO, implement a stream method
	request: function(method, path, data, headers) {
		debug(`calling general request helper: start`)
		var self = this;
		function _request(sc, path, nsp, method, data, headers, resolve, reject) {
			var realPath = "";
			if ( !path.startsWith("/")) { realPath+= "/"}
			realPath+=path;
			debug("making the request to", realPath);
			sc.emit('s', method, realPath, data, headers,function(status, headers, body){
				debug(`service replied with ${status}, ${headers}, ${body}`);
				resolve({status: status, headers: headers, body:body});
			}).on('error', function(e){
				reject(e);
			});
		}

		return new Promise(function(resolve, reject){
			debug(`attempting to retrieve service`);
			if ( self.sioClient ) {
				debug(`sioClient already exists`);
				_request(self.sioClient, path, self._nsp, method, data, headers, resolve, reject);
			} else {

				if ( serviceConfig === undefined ) {
					try {
						serviceConfig = readYaml.sync(require('path').resolve(self._app._basePath,"./config/services.yml"))[app._app.env];
					} catch(e) {
						serviceConfig = false;
					}
				}

				if ( serviceConfig && serviceConfig[self._name] ) {
                    var uri = require('url').parse(serviceConfig[self._name]);
                    debug(`WARNING: ${self._name} is using ws://${uri.host}/${uri.path}`);
                    let sc = (self.sioClient = self.sioClient || new require('socket.io-client')(`ws://${uri.host}/`));
                    debug("client connection made");
                    self._nsp = uri.path;
                    _request(sc, path, uri.path, method, data, headers, resolve, reject);
				} else {
					self._retrieve().then(function(service){
						var nsp = service.txtRecord.nsp || '/';
						self._nsp = nsp;
						var host;
						for ( var i = 0; i< service.addresses.length; i++ ) {
							if ( !service.addresses[i].includes(':') ) {
								host = service.addresses[i];
							}
						}
						// var host = `[${service.addresses[0]}]`;
						// if ( service.replyDomain === "local.") { // this is a local service
							// host = "127.0.0.1";
						// }
						if ( !nsp.startsWith('/') ) {
							nsp = '/'+nsp
						}
						var url = `ws://${host}:${service.port}${nsp}`;
						debug(`service(${self._name}@${self._ver}) resolved, connecting now@${url}`);
						debug(`joining into namespace: ${nsp}`)
						let sc = (self.sioClient = self.sioClient || new require('socket.io-client')(url));
						self.sioClient.on('error', function(err){
							console.log(err);
							self.sioClient = undefined;
						})
						_request(sc, path, nsp, method, data, headers, resolve, reject);
					}, function(e){
						debug.error(`service(${self._name}@${self._ver}) failed to be resolved: ${e}`);
						reject(e);
					});
				}
			}
		});
	}
}
for(let method of require('methods')) {
	Service.prototype[method] = (function(method){
		return function(path, data, headers) {
			debug(`calling ${method} helper: start`)
			return this.request.call(this, method, path, data, headers );
		}
	})(method);
}

exports = module.exports = {
	Service: Service,
	Browser: Browser,
	middleware: function(){
		return {
			setup: function *(next) {
				var app = this;
				yield (new Promise(function(resolve, reject){
					debug("Setting up services middleware");
					app._services = {};
					app.services = function(name){
						if ( !app._services[name] ) { throw "service missing in package.json[services]";}
						return app._services[name];
					};
					for ( let name in (app._pkginfo.services || {}) ) {
						app._services[name] = new Service(name, app._pkginfo.services[name], app);
					}
					resolve();
				}));
				yield* next;
			}
		}
	}
};<commit_msg>fixing bug<commit_after>"use strict";

var mdns = require('mdns'), debug = require('debug-levels')('koala-puree:service');
var readYaml = require('read-yaml');
var mDnsBrowser, globalDnsCache = {};

process.env.NODE_ENV = process.env.NODE_ENV || "development";

var Browser=function(){
	// first search for all possible mdns
	// locate the first dnscache
	// if not located, continue caching everything
	// once located, switch off all browser, and start listening to only dnscache
	function serviceUp(service){ // lets first filter out all loopback devices, these tests would fail during loopback cases then
		debug(`serviceup: ${service.type.name}`,service);
		if (service.type.name !== "koala-puree") { debug(`ignoring service, service name not koala-puree`); return; }
		if (!service.txtRecord || !service.txtRecord.version) { debug(`ignoring service, service.txtRecord does not exist`); return; }

		var host;
		for ( var i = 0; i< service.addresses.length; i++ ) {
			if ( !service.addresses[i].includes(':') ) {
				host = service.addresses[i];
			}
		}
		if ( !host ) { debug('ipv4 not found, ignoring service'); return; }
		var type = (globalDnsCache[service.txtRecord.name] = globalDnsCache[service.txtRecord.name] || {});
		var ver = type[service.txtRecord.version] = type[service.txtRecord.version] || {indices: [], services: {}};
		var id = service.host+":"+service.port;
		ver.services[id] = service;
		ver.indices.push(id);
		debug(`adding service to dns cache`);
	}
	function serviceDown(service){
		var type, ver;
		debug(`service ${service} is down`);
		if ((type = (globalDnsCache[service.type.name])) && (ver = type[service.txtRecord.version])) {
			var id = `${service.host}:${service.port}`;
			delete ver.services[id];
			ver[indices].filter(function(e){
				return e === id;
			})
		}
	}
	function browserError(err, service){
		console.log("browser error:", err, err.stack);
		console.log("service:", service);
	}
	if ( !mDnsBrowser ) {
		// mDnsBrowser = mdns.browseThemAll();
		// probably will require one more for loopback interface
		debug('creating service browser');
		// debug('')
		mDnsBrowser = new mdns.Browser(mdns.tcp('koala-puree'), {resolverSequence:[
			mdns.rst.DNSServiceResolve(),
			mdns.rst.DNSServiceGetAddrInfo({families:[4]})
			// mdns.rst.getaddrinfo({families:[4]})
			// ('DNSServiceGetAddrInfo' in mdns.dns_sd ? mdns.rst.DNSServiceGetAddrInfo : mdns.rst.getaddrinfo)({families:[4]})
		]})
		mDnsBrowser.on('serviceUp', serviceUp)
		mDnsBrowser.on('serviceChanged', serviceUp)
		mDnsBrowser.on('serviceDown', serviceDown)
		mDnsBrowser.on('error', browserError)
		debug('starting service browser');
		mDnsBrowser.start();
	}
}

Browser.prototype = {
	get: function(name, ver) {
		var time = new Date();
		// think of a way to refactor this into a generator, so we can just store that as a cache and yield everytime
		// hopeful usage would be:
		// var service = cache[service@version].next();
		// service.request(method, data, blabla);
		debug(`getting service(${name}@${ver})`);
		return new Promise(function(resolve, reject){
			function resolver() {
				debug('looping through browser caches')
				// keep trying for a specific amount of time, now hard coded, 1s
				if ( new Date() - time > 2000 ) { debug('timeout'); return reject(); }
				if ( name in globalDnsCache ) {
					debug("name is found in globaDnsCache");
					var topver = undefined;
					for ( let gver in globalDnsCache[name] ) {
						if ( require('semver').satisfies(gver, ver) ) {
							if ( topver === undefined || require('semver').gt(gver, topver)) {
								topver = gver;
							}
						}
					}
					if ( topver ) {
						debug("round robinning");
						// lets first do round robin
						let pool = globalDnsCache[name][topver];
						var c = ( (pool.lastCounter === null || pool.lastCount === undefined) ? 0 : pool.lastCounter);

						if ( c+1 > pool.indices.length ) {
							c = -1;
						}
						pool.lastCounter++;
						debug("pool is:"+ JSON.stringify(pool.services));
						debug("resolved with:"+ pool.services[pool.indices[c+1]])
						// TODO: handle cases where it is not found
						return resolve(pool.services[pool.indices[c+1]]);
					}
				}
				setTimeout(resolver, 30);
			}
			process.nextTick(resolver);
		});
	},
	cache: {}
}

var Service = function(name, ver, app){
	this._name = name;
	this._ver = ver;
	this._browser = new Browser();
	this._app = app;
}
var serviceConfig;
Service.prototype = {
	_retrieve: function () {
		debug(`retriving a service based on ${this._name}, ${this._ver}:start`);
		return this._browser.get(this._name, this._ver);
	},
	// TODO, implement a stream method
	request: function(method, path, data, headers) {
		debug(`calling general request helper: start`)
		var self = this;
		function _request(sc, path, nsp, method, data, headers, resolve, reject) {
			var realPath = "";
			if ( !path.startsWith("/")) { realPath+= "/"}
			realPath+=path;
			debug("making the request to", realPath);
			sc.emit('s', method, realPath, data, headers,function(status, headers, body){
				debug(`service replied with ${status}, ${headers}, ${body}`);
				resolve({status: status, headers: headers, body:body});
			}).on('error', function(e){
				reject(e);
			});
		}

		return new Promise(function(resolve, reject){
			debug(`attempting to retrieve service`);
			if ( self.sioClient ) {
				debug(`sioClient already exists`);
				_request(self.sioClient, path, self._nsp, method, data, headers, resolve, reject);
			} else {

				if ( serviceConfig === undefined ) {
					try {
						serviceConfig = readYaml.sync(require('path').resolve(self._app._basePath,"./config/services.yml"))[self._app._app.env];
					} catch(e) {
						serviceConfig = false;
					}
				}

				if ( serviceConfig && serviceConfig[self._name] ) {
                    var uri = require('url').parse(serviceConfig[self._name]);
                    debug(`WARNING: ${self._name} is using ws://${uri.host}/${uri.path}`);
                    let sc = (self.sioClient = self.sioClient || new require('socket.io-client')(`ws://${uri.host}/`));
                    debug("client connection made");
                    self._nsp = uri.path;
                    _request(sc, path, uri.path, method, data, headers, resolve, reject);
				} else {
					self._retrieve().then(function(service){
						var nsp = service.txtRecord.nsp || '/';
						self._nsp = nsp;
						var host;
						for ( var i = 0; i< service.addresses.length; i++ ) {
							if ( !service.addresses[i].includes(':') ) {
								host = service.addresses[i];
							}
						}
						// var host = `[${service.addresses[0]}]`;
						// if ( service.replyDomain === "local.") { // this is a local service
							// host = "127.0.0.1";
						// }
						if ( !nsp.startsWith('/') ) {
							nsp = '/'+nsp
						}
						var url = `ws://${host}:${service.port}${nsp}`;
						debug(`service(${self._name}@${self._ver}) resolved, connecting now@${url}`);
						debug(`joining into namespace: ${nsp}`)
						let sc = (self.sioClient = self.sioClient || new require('socket.io-client')(url));
						self.sioClient.on('error', function(err){
							console.log(err);
							self.sioClient = undefined;
						})
						_request(sc, path, nsp, method, data, headers, resolve, reject);
					}, function(e){
						debug.error(`service(${self._name}@${self._ver}) failed to be resolved: ${e}`);
						reject(e);
					});
				}
			}
		});
	}
}
for(let method of require('methods')) {
	Service.prototype[method] = (function(method){
		return function(path, data, headers) {
			debug(`calling ${method} helper: start`)
			return this.request.call(this, method, path, data, headers );
		}
	})(method);
}

exports = module.exports = {
	Service: Service,
	Browser: Browser,
	middleware: function(){
		return {
			setup: function *(next) {
				var app = this;
				yield (new Promise(function(resolve, reject){
					debug("Setting up services middleware");
					app._services = {};
					app.services = function(name){
						if ( !app._services[name] ) { throw "service missing in package.json[services]";}
						return app._services[name];
					};
					for ( let name in (app._pkginfo.services || {}) ) {
						app._services[name] = new Service(name, app._pkginfo.services[name], app);
					}
					resolve();
				}));
				yield* next;
			}
		}
	}
};<|endoftext|>


<commit_before><commit_msg>Constant declarations and express setup<commit_after>// TODO:

// Initiate Express.
// Setup all OSC api Pathes.
//  -Pathes:
//      - Play Next.
//      - Previous Playhead
//      - Pause
//      - Start
//
const serverIp = "localhost";
const port = "53000";

const express = require("express");
const osc = require("node-osc");
const app = express.app();
const client = new osc.client(serverIp, port);

app.listen(3000, () => {
    console.log("App is running");
});
<|endoftext|>


<commit_before>const mysql = require('mysql2/promise')

class MySQLSession {
  constructor (options, connection) {
    this.options = Object.assign({
      property: 'session',
      userProperty: '',
      chatProperty: '',
      table: 'sessions',
      interval: 300000,
      lifetime: 300,
      getSessionKey: (ctx) => {
        if (ctx.updateType === 'callback_query') {
          ctx = ctx.update.callback_query.message
        }
        if (!ctx.from || !ctx.chat) {
          return
        }
        return [ctx.from.id, ctx.chat.id]
      }
    }, options)

    this.sessions = {}
    this.sessionsDts = {}

    this.client = mysql.createPool(connection)

    this.client.execute(
`CREATE TABLE IF NOT EXISTS ?
      (
        user_id BIGINT(20) NOT NULL,
        chat_id BIGINT(20) NOT NULL,
        session JSON NOT NULL,
        UNIQUE KEY user_id (user_id, chat_id)
      )
      engine=innodb
      DEFAULT charset=utf8;`,
      [this.options.table]
    )
      .catch((err) => {
        throw new Error("Can't create table for sessions", err)
      })

    this.interval = setInterval(() => {
      const currentTime = Math.floor(new Date() / 1000)

      Object.keys(this.sessionsDts).forEach((key) => {
        if (currentTime - this.sessionsDts[key] >= this.options.lifetime) {
          delete this.sessions[key]
          delete this.sessionsDts[key]
        }
      })
    }, this.options.interval)
  }

  async getSession (userID, chatID) {
    const key = userID + ':' + chatID

    if (!this.sessions[key]) {
      try {
        const [rows] = await this.client.query(`
        SELECT session
        FROM ?
        WHERE user_id = ?
        AND chat_id = ?`, [this.options.table, userID, chatID])

        if (rows && rows.length) {
          this.sessions[key] = rows[0].session
        } else {
          this.sessions[key] = {}
        }
      } catch (err) {
        console.error("Can't get user session:", err)
        this.sessions[key] = {}
      }

      this.sessionsDts[key] = Math.floor(new Date() / 1000)
    }

    return this.sessions[key]
  }

  async saveSession (userID, chatID, session) {
    if (!session || Object.keys(session).length === 0) {
      try {
        await this.client.query(`
          DELETE FROM ?
          WHERE  user_id = ?
          AND chat_id = ?
        `, [this.options.table, userID, chatID])
      } catch (err) {
        console.error("Can't delete (save) user session:", err)
        return false
      }
    }

    const sessionString = JSON.stringify(session)

    try {
      return await this.client.query(`
        INSERT INTO ? (user_id, chat_id, session) value (?, ?, ?) ON DUPLICATE KEY UPDATE session = ?;
    `, [this.options.table, userID, chatID, sessionString])
    } catch (err) {
      console.error("Can't save user session:", err)
      return false
    }
  }

  destroy () {
    this.client.end()
    this.client = null

    delete this.options
    this.options = null

    clearInterval(this.interval)
    this.interval = null

    delete this.sessions
    this.sessions = null

    delete this.sessionsDts
    this.sessionsDts = null
  }

  middleware () {
    return async (ctx, next) => {
      const [userID, chatID] = this.options.getSessionKey(ctx)

      if (!userID || !chatID) {
        return next()
      }

      let session = null
      if (this.options.property) {
        session = await this.getSession(userID, chatID)

        Object.defineProperty(ctx, this.options.property, {
          get: function () {
            return session
          },
          set: function (newValue) {
            session = Object.assign({}, newValue)
          }
        })
      }

      let userSession = null

      if (this.options.userProperty) {
        userSession = await this.getSession(userID, 0)

        Object.defineProperty(ctx, this.options.userProperty, {
          get: function () {
            return userSession
          },
          set: function (newValue) {
            userSession = Object.assign({}, newValue)
          }
        })
      }

      let chatSession = null

      if (this.options.chatProperty) {
        chatSession = await this.getSession(0, chatID)

        Object.defineProperty(ctx, this.options.chatProperty, {
          get: function () {
            return chatSession
          },
          set: function (newValue) {
            chatSession = Object.assign({}, newValue)
          }
        })
      }
      return next().then(async() => {
        if (session) {
          await this.saveSession(userID, chatID, session)
        }
        if (userSession) {
          await this.saveSession(userID, 0, userSession)
        }
        if (chatSession) {
          await this.saveSession(0, chatID, chatSession)
        }
      })
    }
  }
}

module.exports = MySQLSession
<commit_msg>Added debug<commit_after>const mysql = require('mysql2/promise')
const debug = require('debug')('telegraf:session-redis')

class MySQLSession {
  constructor (options, connection) {
    this.options = Object.assign({
      property: 'session',
      userProperty: '',
      chatProperty: '',
      table: 'sessions',
      interval: 300000,
      lifetime: 300,
      getSessionKey: (ctx) => {
        if (ctx.updateType === 'callback_query') {
          ctx = ctx.update.callback_query.message
        }
        if (!ctx.from || !ctx.chat) {
          return
        }
        return [ctx.from.id, ctx.chat.id]
      }
    }, options)

    this.sessions = {}
    this.sessionsDts = {}

    this.client = mysql.createPool(connection)

    this.client.execute(
`CREATE TABLE IF NOT EXISTS ?
      (
        user_id BIGINT(20) NOT NULL,
        chat_id BIGINT(20) NOT NULL,
        session JSON NOT NULL,
        UNIQUE KEY user_id (user_id, chat_id)
      )
      engine=innodb
      DEFAULT charset=utf8;`,
      [this.options.table]
    )
      .catch((err) => {
        throw new Error("Can't create table for sessions", err)
      })

    this.interval = setInterval(() => {
      const currentTime = Math.floor(new Date() / 1000)

      Object.keys(this.sessionsDts).forEach((key) => {
        if (currentTime - this.sessionsDts[key] >= this.options.lifetime) {
          delete this.sessions[key]
          delete this.sessionsDts[key]
        }
      })
    }, this.options.interval)
  }

  async getSession (userID, chatID) {
    const key = userID + ':' + chatID

    if (!this.sessions[key]) {
      try {
        const [rows] = await this.client.query(`
        SELECT session
        FROM ?
        WHERE user_id = ?
        AND chat_id = ?`, [this.options.table, userID, chatID])

        if (rows && rows.length) {
          this.sessions[key] = rows[0].session
        } else {
          this.sessions[key] = {}
        }
      } catch (err) {
        console.error("Can't get user session:", err)
        this.sessions[key] = {}
      }

      this.sessionsDts[key] = Math.floor(new Date() / 1000)
    }
    debug('session state', key, this.sessions[key])
    return this.sessions[key]
  }

  async saveSession (userID, chatID, session) {
    debug('save session', userID + ':' + chatID, session)
    if (!session || Object.keys(session).length === 0) {
      try {
        await this.client.query(`
          DELETE FROM ?
          WHERE  user_id = ?
          AND chat_id = ?
        `, [this.options.table, userID, chatID])
      } catch (err) {
        console.error("Can't delete (save) user session:", err)
        return false
      }
    }

    const sessionString = JSON.stringify(session)

    try {
      return await this.client.query(`
        INSERT INTO ? (user_id, chat_id, session) value (?, ?, ?) ON DUPLICATE KEY UPDATE session = ?;
    `, [this.options.table, userID, chatID, sessionString])
    } catch (err) {
      console.error("Can't save user session:", err)
      return false
    }
  }

  destroy () {
    this.client.end()
    this.client = null

    delete this.options
    this.options = null

    clearInterval(this.interval)
    this.interval = null

    delete this.sessions
    this.sessions = null

    delete this.sessionsDts
    this.sessionsDts = null

    debug('destroy sessions')
  }

  middleware () {
    return async (ctx, next) => {
      const [userID, chatID] = this.options.getSessionKey(ctx)

      if (!userID || !chatID) {
        return next()
      }

      let session = null
      if (this.options.property) {
        session = await this.getSession(userID, chatID)

        Object.defineProperty(ctx, this.options.property, {
          get: function () {
            return session
          },
          set: function (newValue) {
            session = Object.assign({}, newValue)
          }
        })
      }

      let userSession = null

      if (this.options.userProperty) {
        userSession = await this.getSession(userID, 0)

        Object.defineProperty(ctx, this.options.userProperty, {
          get: function () {
            return userSession
          },
          set: function (newValue) {
            userSession = Object.assign({}, newValue)
          }
        })
      }

      let chatSession = null

      if (this.options.chatProperty) {
        chatSession = await this.getSession(0, chatID)

        Object.defineProperty(ctx, this.options.chatProperty, {
          get: function () {
            return chatSession
          },
          set: function (newValue) {
            chatSession = Object.assign({}, newValue)
          }
        })
      }

      debug(`session snapshot for Chat: ${chatID || 'None'} User: ${userID || 'None'}`, session)
      return next().then(async() => {
        if (session) {
          await this.saveSession(userID, chatID, session)
        }
        if (userSession) {
          await this.saveSession(userID, 0, userSession)
        }
        if (chatSession) {
          await this.saveSession(0, chatID, chatSession)
        }
      })
    }
  }
}

module.exports = MySQLSession
<|endoftext|>


<commit_before>'use strict';

var
    bole    = require('bole'),
    restify = require('restify')
    ;

var slackClient;
var logger = bole('slack');

exports.createClient = function createClient(opts)
{
    slackClient = restify.createJSONClient({ url: opts.slack });
    logger.info('Slack client created')
};

exports.report = function report(message, options)
{
    if (!slackClient) return;

    var body = options || {};
    body.text = message;

    slackClient.post('', body, function(err, req, res, obj)
    {
        if (err)
            logger.error({error: err, message: message}, 'error posting to webhook');
        else if (res.statusCode === 200)
        {
            logger.info('report posted to Slack');
        }
    });
};
<commit_msg>Work around slack message limits<commit_after>'use strict';

var
    bole    = require('bole'),
    restify = require('restify')
    ;

var slackClient;
var logger = bole('slack');

exports.createClient = function createClient(opts)
{
    slackClient = restify.createJSONClient({ url: opts.slack });
    logger.info('Slack client created')
};

exports.report = function report(message, options)
{
    if (!slackClient) return;

    var body = options || {};
    var text = message;

    // because slack has message limits
    if (message.length > 499){
        text = message.substr(0, 499)
        exports.report(message.substr(499))
    }
    body.text = text;

    slackClient.post('', body, function(err, req, res, obj)
    {
        if (err)
            logger.error({error: err, message: message}, 'error posting to webhook');
        else if (res.statusCode === 200)
        {
            logger.info('report posted to Slack');
        }
    });
};
<|endoftext|>