"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.error = exports.successHtml = exports.success = exports.filterClause = exports.whereCondition = exports.getPageEntities = exports.sort = exports.filter = exports.getValidRestParams = exports.getData = void 0;
const lodash_1 = __importDefault(require("lodash"));
const constraints_1 = __importDefault(require("../validation/constraints"));
const errorTypes_1 = __importDefault(require("../utils/errorTypes"));
const logger_1 = __importDefault(require("../logger"));
const validator_1 = require("../validation/validator");
function respond(res, httpCode, data, type) {
    switch (type) {
        case 'html':
            res.writeHead(httpCode, { 'Content-Type': 'text/html' });
            res.end(data);
            break;
        default:
            res.writeHead(httpCode, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify(data));
            break;
    }
}
function orderByClause(restParams, defaultSortField, allowedSortFields) {
    let sortField = lodash_1.default.includes(allowedSortFields, restParams._sortField) ? restParams._sortField : undefined;
    if (!sortField) {
        sortField = defaultSortField;
    }
    return {
        query: 'ORDER BY ' + sortField + ' ' + (restParams._sortDir === 'ASC' ? 'ASC' : 'DESC'),
        params: []
    };
}
function limitOffsetClause(restParams) {
    const page = restParams._page;
    const perPage = restParams._perPage;
    return {
        query: 'LIMIT ? OFFSET ?',
        params: [perPage, ((page - 1) * perPage)]
    };
}
function escapeForLikePattern(str) {
    return str
        .replace(/\\/g, '\\\\')
        .replace(/%/g, '\\%')
        .replace(/_/g, '\\_');
}
function filterCondition(restParams, filterFields) {
    if (lodash_1.default.isEmpty(filterFields)) {
        return {
            query: '1 = 1',
            params: []
        };
    }
    let query = lodash_1.default.join(lodash_1.default.map(filterFields, function (field) {
        return 'LOWER(' + field + ') LIKE ?';
    }), ' OR ');
    query += ' ESCAPE \'\\\'';
    const search = '%' + (lodash_1.default.isString(restParams.q) ? escapeForLikePattern(lodash_1.default.toLower(restParams.q.trim())) : '') + '%';
    const params = lodash_1.default.times(filterFields.length, lodash_1.default.constant(search));
    return {
        query: query,
        params: params
    };
}
exports.whereCondition = filterCondition;
function getConstrainedValues(data, constraints) {
    const values = {};
    lodash_1.default.each(lodash_1.default.keys(constraints), (key) => {
        const value = data[key];
        values[key] =
            lodash_1.default.isUndefined(value) && key in constraints && !lodash_1.default.isUndefined(constraints[key].default)
                ? constraints[key].default
                : value;
    });
    return values;
}
function getData(req) {
    return lodash_1.default.extend({}, req.body, req.params, req.query);
}
exports.getData = getData;
async function getValidRestParams(type, subtype, req) {
    const restConstraints = constraints_1.default.rest;
    let constraints;
    if (!(type in restConstraints) || !validator_1.isConstraints(restConstraints[type])) {
        logger_1.default.tag('validation', 'rest').error('Unknown REST resource type: {}', type);
        throw { data: 'Internal error.', type: errorTypes_1.default.internalError };
    }
    constraints = restConstraints[type];
    let filterConstraints = {};
    if (subtype) {
        const subtypeFilters = subtype + 'Filters';
        const constraintsObj = constraints_1.default;
        if (!(subtypeFilters in constraintsObj) || !validator_1.isConstraints(constraintsObj[subtypeFilters])) {
            logger_1.default.tag('validation', 'rest').error('Unknown REST resource subtype: {}', subtype);
            throw { data: 'Internal error.', type: errorTypes_1.default.internalError };
        }
        filterConstraints = constraintsObj[subtypeFilters];
    }
    const data = getData(req);
    const restParams = getConstrainedValues(data, constraints);
    const filterParams = getConstrainedValues(data, filterConstraints);
    const areValidParams = validator_1.forConstraints(constraints, false);
    const areValidFilters = validator_1.forConstraints(filterConstraints, false);
    if (!areValidParams(restParams) || !areValidFilters(filterParams)) {
        throw { data: 'Invalid REST parameters.', type: errorTypes_1.default.badRequest };
    }
    restParams.filters = filterParams;
    return restParams;
}
exports.getValidRestParams = getValidRestParams;
function filter(entities, allowedFilterFields, restParams) {
    let query = restParams.q;
    if (query) {
        query = lodash_1.default.toLower(query.trim());
    }
    function queryMatches(entity) {
        if (!query) {
            return true;
        }
        return lodash_1.default.some(allowedFilterFields, (field) => {
            if (!query) {
                return true;
            }
            let value = entity[field];
            if (lodash_1.default.isNumber(value)) {
                value = value.toString();
            }
            if (!lodash_1.default.isString(value) || lodash_1.default.isEmpty(value)) {
                return false;
            }
            value = lodash_1.default.toLower(value);
            if (field === 'mac') {
                return lodash_1.default.includes(value.replace(/:/g, ''), query.replace(/:/g, ''));
            }
            return lodash_1.default.includes(value, query);
        });
    }
    const filters = restParams.filters;
    function filtersMatch(entity) {
        if (lodash_1.default.isEmpty(filters)) {
            return true;
        }
        return lodash_1.default.every(filters, (value, key) => {
            if (lodash_1.default.isUndefined(value)) {
                return true;
            }
            if (lodash_1.default.startsWith(key, 'has')) {
                const entityKey = key.substr(3, 1).toLowerCase() + key.substr(4);
                return lodash_1.default.isEmpty(entity[entityKey]).toString() !== value;
            }
            return entity[key] === value;
        });
    }
    return lodash_1.default.filter(entities, function (entity) {
        return queryMatches(entity) && filtersMatch(entity);
    });
}
exports.filter = filter;
function sort(entities, allowedSortFields, restParams) {
    const sortField = lodash_1.default.includes(allowedSortFields, restParams._sortField) ? restParams._sortField : undefined;
    if (!sortField) {
        return entities;
    }
    const sorted = lodash_1.default.sortBy(entities, [sortField]);
    if (restParams._sortDir === 'ASC') {
        return sorted;
    }
    else {
        return lodash_1.default.reverse(sorted);
    }
}
exports.sort = sort;
function getPageEntities(entities, restParams) {
    const page = restParams._page;
    const perPage = restParams._perPage;
    return lodash_1.default.slice(entities, (page - 1) * perPage, page * perPage);
}
exports.getPageEntities = getPageEntities;
function filterClause(restParams, defaultSortField, allowedSortFields, filterFields) {
    const orderBy = orderByClause(restParams, defaultSortField, allowedSortFields);
    const limitOffset = limitOffsetClause(restParams);
    const filter = filterCondition(restParams, filterFields);
    return {
        query: filter.query + ' ' + orderBy.query + ' ' + limitOffset.query,
        params: lodash_1.default.concat(filter.params, orderBy.params, limitOffset.params)
    };
}
exports.filterClause = filterClause;
function success(res, data) {
    respond(res, 200, data, 'json');
}
exports.success = success;
function successHtml(res, html) {
    respond(res, 200, html, 'html');
}
exports.successHtml = successHtml;
function error(res, err) {
    respond(res, err.type.code, err.data, 'json');
}
exports.error = error;
//# sourceMappingURL=resources.js.map