<commit_before>import moment from 'moment'
import _ from 'lodash'
import { ObjectID } from 'mongodb'
import makeDebug from 'debug'

const debug = makeDebug('kalisio:kCore:query:hooks')

function marshallComparisonFieldsInQuery (queryObject) {
  _.forOwn(queryObject, (value, key) => {
    // Process current attributes or  recurse
    if (typeof value === 'object') {
      marshallComparisonFieldsInQuery(value)
    } else if ((key === '$lt') || (key === '$lte') || (key === '$gt') || (key === '$gte')) {
      let number = _.toNumber(value)
      // Update from query string to number if required
      if (!Number.isNaN(number)) {
        queryObject[key] = number
      } else {
        // try for dates as well
        let date = moment.utc(value)
        if (date.isValid()) {
          queryObject[key] = date.toDate()
        }
      }
    }
  })
}

export function marshallComparisonQuery (hook) {
  let query = hook.params.query
  if (query) {
    // Complex queries might have nested objects so we call a recursive function to handle this
    marshallComparisonFieldsInQuery(query)
  }
}

export function marshallGeometryQuery (hook) {
  let query = hook.params.query
  if (!query) return

  if (typeof query.geometry === 'object') {
    // Geospatial operators begin with $
    let geoOperator = _.keys(query.geometry).find(key => key.startsWith('$'))
    geoOperator = query.geometry[geoOperator]
    _.forOwn(geoOperator, (value, key) => {
      // Geospatial parameters begin with $
      if (key.startsWith('$')) {
        // Some target coordinates
        if (!_.isNil(value.coordinates)) {
          value.coordinates = value.coordinates.map(coordinate => _.toNumber(coordinate))
        } else {
          // Other simple values
          geoOperator[key] = _.toNumber(value)
        }
      }
    })
  }
}

export function populateObject (options) {
  return function (hook) {
    let app = hook.app
    let data = hook.data
    let params = hook.params
    let query = params.query
    const context = hook.service.context
    const idProperty = options.nameIdAs || options.idField
    const serviceProperty = options.nameServiceAs || options.serviceField

    // Check if not already done
    if (typeof _.get(params, idProperty) === 'object') {
      debug(`Skipping populating ${idProperty} as already done`)
      return Promise.resolve(hook)
    }
    if (typeof _.get(params, serviceProperty) === 'object') {
      debug(`Skipping populating ${serviceProperty} as already done`)
      return Promise.resolve(hook)
    }

    // Get service where we can find the object to populate
    // Make hook usable with query params as well and service name or real object
    let service = _.get(data, options.serviceField) || _.get(query, options.serviceField)
    if (typeof service === 'string') {
      const message = `Cannot find the service for ${options.serviceField} = ${service} to dynamically populate.`
      service = app.getService(service, context)
      if (!service) {
        if (options.throwOnNotFound) throw new Error(message)
        else return Promise.resolve(hook)
      }
    } else if (!service) {
      if (options.throwOnNotFound) throw new Error(`No ${options.serviceField} given to dynamically populate.`)
      else return Promise.resolve(hook)
    }
    // Then the object ID
    let id = _.get(data, options.idField) || _.get(query, options.idField) || _.get(hook, 'id')
    if (!id) {
      if (options.throwOnNotFound) throw new Error(`Cannot find the ${options.idField} to dynamically populate.`)
      else return Promise.resolve(hook)
    }

    debug(`Populating ${idProperty} with ID ${id}`)
    // Set the retrieved service on the same field or given one in hook params
    _.set(params, serviceProperty, service)
    // Let it work with id string or real object
    if (typeof id === 'string' || ObjectID.isValid(id)) {
      return service.get(id.toString(), { user: hook.params.user }).then(object => {
        if (!object) {
          if (options.throwOnNotFound) throw new Error(`Cannot find object with id ${id} to dynamically populate.`)
          else return hook
        }
        // Set the retrieved object on the same field or given one in hook params
        _.set(params, idProperty, object)
        return hook
      })
    } else {
      // Set the object on the same field or given one in hook params
      _.set(params, idProperty, id)
      return Promise.resolve(hook)
    }
  }
}

export function populateObjects (options) {
  return function (hook) {
    let app = hook.app
    let data = hook.data
    let params = hook.params
    let query = params.query
    const context = hook.service.context
    const idProperty = options.nameIdAs || options.idField
    const serviceProperty = options.nameServiceAs || options.serviceField

    // Check if not already done
    if (Array.isArray(_.get(params, idProperty))) {
      debug(`Skipping populating ${idProperty} as already done`)<commit_msg>Added perspective support in populateObject hook<commit_after>import moment from 'moment'
import _ from 'lodash'
import { ObjectID } from 'mongodb'
import makeDebug from 'debug'

const debug = makeDebug('kalisio:kCore:query:hooks')

function marshallComparisonFieldsInQuery (queryObject) {
  _.forOwn(queryObject, (value, key) => {
    // Process current attributes or  recurse
    if (typeof value === 'object') {
      marshallComparisonFieldsInQuery(value)
    } else if ((key === '$lt') || (key === '$lte') || (key === '$gt') || (key === '$gte')) {
      let number = _.toNumber(value)
      // Update from query string to number if required
      if (!Number.isNaN(number)) {
        queryObject[key] = number
      } else {
        // try for dates as well
        let date = moment.utc(value)
        if (date.isValid()) {
          queryObject[key] = date.toDate()
        }
      }
    }
  })
}

export function marshallComparisonQuery (hook) {
  let query = hook.params.query
  if (query) {
    // Complex queries might have nested objects so we call a recursive function to handle this
    marshallComparisonFieldsInQuery(query)
  }
}

export function marshallGeometryQuery (hook) {
  let query = hook.params.query
  if (!query) return

  if (typeof query.geometry === 'object') {
    // Geospatial operators begin with $
    let geoOperator = _.keys(query.geometry).find(key => key.startsWith('$'))
    geoOperator = query.geometry[geoOperator]
    _.forOwn(geoOperator, (value, key) => {
      // Geospatial parameters begin with $
      if (key.startsWith('$')) {
        // Some target coordinates
        if (!_.isNil(value.coordinates)) {
          value.coordinates = value.coordinates.map(coordinate => _.toNumber(coordinate))
        } else {
          // Other simple values
          geoOperator[key] = _.toNumber(value)
        }
      }
    })
  }
}

export function populateObject (options) {
  return function (hook) {
    let app = hook.app
    let data = hook.data
    let params = hook.params
    let query = params.query
    const context = hook.service.context
    const idProperty = options.nameIdAs || options.idField
    const serviceProperty = options.nameServiceAs || options.serviceField

    // Check if not already done
    if (typeof _.get(params, idProperty) === 'object') {
      debug(`Skipping populating ${idProperty} as already done`)
      return Promise.resolve(hook)
    }
    if (typeof _.get(params, serviceProperty) === 'object') {
      debug(`Skipping populating ${serviceProperty} as already done`)
      return Promise.resolve(hook)
    }

    // Get service where we can find the object to populate
    // Make hook usable with query params as well and service name or real object
    let service = _.get(data, options.serviceField) || _.get(query, options.serviceField)
    if (typeof service === 'string') {
      const message = `Cannot find the service for ${options.serviceField} = ${service} to dynamically populate.`
      service = app.getService(service, context)
      if (!service) {
        if (options.throwOnNotFound) throw new Error(message)
        else return Promise.resolve(hook)
      }
    } else if (!service) {
      if (options.throwOnNotFound) throw new Error(`No ${options.serviceField} given to dynamically populate.`)
      else return Promise.resolve(hook)
    }
    // Then the object ID
    let id = _.get(data, options.idField) || _.get(query, options.idField) || _.get(hook, 'id')
    if (!id) {
      if (options.throwOnNotFound) throw new Error(`Cannot find the ${options.idField} to dynamically populate.`)
      else return Promise.resolve(hook)
    }
    // Then the perspective if any
    let perspective = _.get(data, options.perspectiveField) || _.get(query, options.perspectiveField)

    debug(`Populating ${idProperty} with ID ${id}`)
    // Set the retrieved service on the same field or given one in hook params
    _.set(params, serviceProperty, service)
    // Let it work with id string or real object
    if (typeof id === 'string' || ObjectID.isValid(id)) {
      let args = { user: hook.params.user }
      if (perspective) args = Object.assign(args, { query: { $select: [perspective] } })
      return service.get(id.toString(), args).then(object => {
        if (!object) {
          if (options.throwOnNotFound) throw new Error(`Cannot find object with id ${id} to dynamically populate.`)
          else return hook
        }
        // Set the retrieved object on the same field or given one in hook params
        _.set(params, idProperty, object)
        return hook
      })
    } else {
      // Set the object on the same field or given one in hook params
      _.set(params, idProperty, id)
      return Promise.resolve(hook)
    }
  }
}

export function populateObjects (options) {
  return function (hook) {
    let app = hook.app
    let data = hook.data
    let params = hook.params
    let query = params.query
    const context = hook.service.context
    const idProperty = options.nameIdAs || options.idField
    const serviceProperty = options.nameServiceAs || options.serviceField

    // Check if not already done
    if (Array.isArray(_.get(params, idProperty))) {
      debug(`Skipping populating ${idProperty} as already done`)
{'fraction_non_alphanumeric': 0.11489985344406449, 'fraction_numerical': 0.0, 'mean_word_length': 2.887580706418534, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 13, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1172439', 'n_tokens_mistral': 2867, 'n_tokens_neox': 2829, 'n_words': 924}