server/util/helpers.js

const Sequelize = require('sequelize');
const _ = require('lodash');
const fs = require('fs');
const translation = require('./translation');

const { Op } = Sequelize;

const getLines = (filename, lineCount, callback) => {
  const stream = fs.createReadStream(filename, {
    flags: 'r',
    encoding: 'utf-8',
    fd: null,
    mode: 438, // 0666 in Octal
    bufferSize: 64 * 1024,
  });

  let data = '';
  let lines = [];
  stream.on('data', (moreData) => {
    data += moreData;
    lines = data.split('\n');
    if (lines.length > lineCount + 1) {
      stream.destroy();
      lines = lines.slice(0, lineCount); // junk as above
      callback(false, lines.join('\n'));
    }
  });

  stream.on('error', () => {
    callback('Error');
  });

  stream.on('end', () => {
    callback(false, lines.join('\n'));
  });
};

const nullableLines = (filename, callback) => {
  const stream = fs;
  stream.createWriteStream(filename, {
    flags: 'w',
    encoding: 'utf-8',
    fd: null,
    mode: 438, // 0666 in Octal
    bufferSize: 64 * 1024,
  });
  stream.write(0, 'asdasd', (err) => {
    if (err) {
      callback('Error');
    }
    callback(false, '');
  });
};

const getRequestLanguage = cookies => (cookies && cookies.language) || 'en-us';

const getTranslation = params => (translation[getRequestLanguage(params.cookies || {})]);

const getTranslationIdByCode = (languages, code) => {
  const lang = (languages || []).find(language => language.code === code);
  if (!lang) {
    console.log('Not able to find lang'); // eslint-disable-line
    return 1;
  }
  return lang.id;
};
/**
 * parseQueryParam
 * adds basic params to query
 * @param query
 * @param search
 * @param field
 * @param likes
 * @param equals
 * @returns {*}
 */
const parseQueryParam = (query, search, field, likes, equals) => {
  if (likes.indexOf(field) !== -1) {
    Object.assign(query, { [field]: { [Op.iLike]: `%${search[field]}%` } });
  } else if (equals.indexOf(field) !== -1) {
    Object.assign(query, { [field]: search[field] });
  }
  return query;
};
/**
 * addOrCondition
 * adds OR conditions to the query
 * @param query
 * @param search
 * @param field
 * @param likes
 * @param equals
 */
const addOrCondition = (query, search, field, likes, equals) => {
  let result = query;
  if (field.indexOf('_OR_') !== -1) {
    const parts = field.split('_OR_');
    if (likes.indexOf(parts[0]) !== -1 && likes.indexOf(parts[1]) !== -1) {
      return Object.assign(query, {
        [Op.or]: {
          [parts[0]]: { [Op.iLike]: `%${search[field]}%` }, [parts[1]]: { [Op.iLike]: `%${search[field]}%` },
        },
      });
    } else if (equals.indexOf(parts[0]) !== -1 && equals.indexOf(parts[1]) !== -1) { // eslint-disable-line
      return Object.assign(query, {
        [Op.or]: {
          [parts[0]]: search[field], [parts[1]]: search[field],
        },
      });
    }
    result = parseQueryParam(query, search, parts[0], likes, equals);
    result = parseQueryParam(query, search, parts[1], likes, equals);
  }
  return result;
};
const addOrAndOrCondition = (query, search, field, likes, equals) => {
  let result = query;
  if (field.indexOf('_ORANDOR_') !== -1) {
    const parts = field.split('_ORANDOR_');
    if (likes.indexOf(parts[0]) !== -1 && likes.indexOf(parts[1]) !== -1) {
      const qPart = search[field].split(' ');
      return Object.assign(query, {
        [Op.or]: {
          [parts[0]]: { [Op.iLike]: `%${qPart[0]}%` },
          [parts[0]]: { [Op.iLike]: `%${qPart[1]}%` },
          [parts[1]]: { [Op.iLike]: `%${qPart[0]}%` },
          [parts[1]]: { [Op.iLike]: `%${qPart[1]}%` },
        },
      });
    } else if (equals.indexOf(parts[0]) !== -1 && equals.indexOf(parts[1]) !== -1) { // eslint-disable-line
      return Object.assign(query, {
        [Op.or]: {
          [parts[0]]: search[field], [parts[1]]: search[field],
        },
      });
    }
    result = parseQueryParam(query, search, parts[0], likes, equals);
    result = parseQueryParam(query, search, parts[1], likes, equals);
  }
  return result;
};
/**
 * addDateCondition
 * add dates conditions to the query
 * @param query
 * @param search
 * @param field
 * @param likes
 * @param equals
 * @returns {*}
 */
const addDateCondition = (query, search, field, likes, equals) => {
  if (equals.indexOf(field) !== -1 && (field === 'createdAt' || field === 'updatedAt')) {
    const value = search[field];
    if (value.indexOf('_to_') !== -1) {
      const splited = value.split('_to_');
      if (splited[0] && splited[1]) {
        return Object.assign(query, {
          [field]: {
            [Op.between]: [new Date(parseFloat(splited[0])), new Date(parseFloat(splited[1]))],
          },
        });
      } else if (splited[0] || splited[1]) { // eslint-disable-line
        return Object.assign(query, {
          [field]: new Date(parseFloat(splited[0] || splited[1])),
        });
      }
    } else if (search[field]) {
      return Object.assign(query, {
        [field]: new Date(parseFloat(search[field])),
      });
    }
  }
  return query;
};

/**
 * withLikes
 * convert query to like params
 * @param hook
 * @param likes {Array} params which match with likes
 * @param equals {Array} params which match with equals
 * @returns {Object}
 */
const withLikes = (hook, likes = [], equals = []) => {
  const $search = _.get(hook, 'params.query.$search');
  if (!$search || !Object.keys($search).length) {
    return {};
  }
  return ({
    where: Object.keys($search).reduce((acc, field) => {
      let query = acc;
      query = addOrAndOrCondition(query, $search, field, likes, equals);
      query = addOrCondition(query, $search, field, likes, equals);
      query = parseQueryParam(query, $search, field, likes, equals);
      query = addDateCondition(query, $search, field, likes, equals);
      return query;
    }, {}),
  });
};

/**
 * prepareHook
 * removes support params from hook
 * @param hook
 */
const prepareHook = (hook) => {
  if (_.has(hook, 'params.query.list')) {
    delete hook.params.query.list; // eslint-disable-line
  }
  if (_.has(hook, 'params.query.$search')) {
    delete hook.params.query.$search; // eslint-disable-line
  }
  if (_.has(hook, 'params.query.$limit') && parseFloat(hook.params.query.$limit) === -1) {
    Object.assign(hook.params, { paginate: false });
    delete hook.params.query.$limit; // eslint-disable-line
  }
  return hook;
};

/**
 * targetTable
 * clarifies the table if it is transmitted
 * @param tableName {String} table name of database
 * @param currentElement {String} column name
 * @returns String
 */
const targetTable = (tableName, currentElement) => (tableName.length > 0 ? `"${tableName}"."${currentElement}"` : `"${currentElement}"`);

/**
 * allowedFields
 * clarifies the table if it is transmitted
 * @param search {Object} of search
 * @param allowed {Array} of allowed fields
 * @returns Object
 */
const allowedFields = (search, allowed) => {
  const newObject = search || {};
  if (newObject && 'group_by' in newObject) {
    delete newObject.group_by;
  }
  const keys = Object.keys(newObject);
  keys.forEach((key) => {
    if (allowed.indexOf(key) === -1) throw new Error(`Parameter ${key} does not exist`);
  });
  return newObject;
};

/**
 * searchToWhereString
 * convert query to part of SQL String
 * @param search
 * @param allowed {Array} of allowed fields
 * @param table table of database
 * @param like {Array} params which match with likes
 * @param where {Boolean}
 * @returns String
 */
const searchToWhereString = (search, allowed, table = '', like = [], where = true) => {
  let query = '';
  const searching = allowedFields(search, allowed);
  if (!searching) {
    return query;
  }
  query += where ? 'WHERE ' : ' AND ';
  const searched = Object.keys(searching).reduce((acc, key) => {
    if ((key === 'createdAt' || key === 'updatedAt' || key === 'date_end' || key === 'date_start') && searching[key].indexOf('_to_') !== -1) {
      acc.push(`${targetTable(table, key)} BETWEEN :${key}First AND :${key}Last `);
    } else if (like.length > 0 && like.indexOf(key) !== -1) {
      acc.push(`${targetTable(table, key)} ILIKE :${key} `);
    } else {
      acc.push(`${targetTable(table, key)} = :${key} `);
    }
    return acc;
  }, []);
  if (searched.length === 0) return '';
  return query + searched.join(' AND ');
};

/**
 * objectQueries
 * remove unusable in query group_by year, and separate date if they period
 * @param search {Object}
 * @param allowed {Array} of allowed fields
 * @param like {Array} of like fields
 * @returns Object
 */
const objectQueries = (search, allowed, like = []) => {
  const newObj = allowedFields(search, allowed);
  const date = ['createdAt', 'updatedAt', 'date_end', 'date_start'];
  return Object.keys(newObj).reduce((acc, key) => {
    const value = newObj[key] || '';
    if (date.indexOf(key) !== -1 && value.indexOf('_to_') !== -1) {
      const splitter = value.split('_to_');
      return Object.assign(acc, { [`${key}First`]: splitter[0], [`${key}Last`]: splitter[1] });
    }
    if (like.length > 0 && like.indexOf(key)) {
      return Object.assign(acc, { [key]: `%${value}%` });
    }
    return Object.assign(acc, { [key]: value });
  }, {});
};


/**
 * createImageRegexp, create a regexp for image files
 * @param formats
 * @returns {RegExp}
 */
const createImageRegexp = formats => (new RegExp(`${_.reduce(formats.split('\r\n'), (acc, format) => (acc ? `${acc}|.${format}` : `${acc}.${format}`), '')}$`, 'gi'));


module.exports = {
  nullableLines,
  getLines,
  createImageRegexp,
  getRequestLanguage,
  getTranslation,
  getTranslationIdByCode,
  addOrCondition,
  parseQueryParam,
  addDateCondition,
  withLikes,
  prepareHook,
  searchToWhereString,
  objectQueries,
};