"use strict";
var __assign = (this && this.__assign) || function () {
    __assign = Object.assign || function(t) {
        for (var s, i = 1, n = arguments.length; i < n; i++) {
            s = arguments[i];
            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
                t[p] = s[p];
        }
        return t;
    };
    return __assign.apply(this, arguments);
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __generator = (this && this.__generator) || function (thisArg, body) {
    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
    function verb(n) { return function (v) { return step([n, v]); }; }
    function step(op) {
        if (f) throw new TypeError("Generator is already executing.");
        while (_) try {
            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
            if (y = 0, t) op = [op[0] & 2, t.value];
            switch (op[0]) {
                case 0: case 1: t = op; break;
                case 4: _.label++; return { value: op[1], done: false };
                case 5: _.label++; y = op[1]; op = [0]; continue;
                case 7: op = _.ops.pop(); _.trys.pop(); continue;
                default:
                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
                    if (t[2]) _.ops.pop();
                    _.trys.pop(); continue;
            }
            op = body.call(thisArg, _);
        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
    }
};
exports.__esModule = true;
exports.serviceView = void 0;
var url_1 = require("url");
var HTTP_STATUS = require("http-status");
var cnst = require("../const");
var database_1 = require("../database");
var utils_1 = require("../utils");
var j = JSON.stringify; // convience
var uniqueResource = function (tail, url) {
    return url_1.parse(url, true).pathname.endsWith(tail);
};
// can maybe add prefix to fn signature and use to parse out subquery payload
var seperateQueryAndContext = function (input) {
    return Object.entries(input).reduce(function (query, _a) {
        var key = _a[0], value = _a[1];
        var info = key.startsWith(cnst.PIPE) ? query.context : query.payload;
        info[key.replace(cnst.PIPE, "")] = value;
        return query;
    }, { payload: {}, context: {}, apiType: "REST" } // apiType -- needed for correct queryContext parsing
    );
};
exports.serviceView = function (_a) {
    var operations = _a.operations, permissions = _a.permissions, resourcesMap = _a.resourcesMap, hardDelete = _a.hardDelete, logger = _a.logger, db = _a.db, supportsReturn = _a.supportsReturn;
    return function (ctx) { return __awaiter(void 0, void 0, void 0, function () {
        var reqId, records, _a, category, resource, method, url, record, operation, permit, stripKeys, input, payload, asyncServiceResponse, serviceResponse, sqlString, dbCallSuccessful, _records, err_1, args, _asyncServiceResponseCount, searchCountResult, sqlSearchCount, count, output;
        return __generator(this, function (_b) {
            switch (_b.label) {
                case 0:
                    reqId = ctx.state.reqId;
                    records = null;
                    _a = ctx.params, category = _a.category, resource = _a.resource;
                    method = ctx.method;
                    url = ctx.request.url;
                    record = uniqueResource("/record", url);
                    operation = operations.get(j({ method: method, record: record }));
                    permit = utils_1.permitted(permissions)(resource, operation);
                    // only process for /service & /debug, resource && CRUD operation exists, and 404 trailing slashes
                    if ((category !== "service" && category !== "debug") ||
                        !operations.has(j({ method: method, record: record })) ||
                        !permit ||
                        !resourcesMap.hasOwnProperty(resource) ||
                        uniqueResource("/record/", url) // no trailing slash
                    ) {
                        ctx.response.status = HTTP_STATUS.NOT_FOUND;
                        ctx.response.body = cnst.SERVICE_RESOURCE_NOT_FOUND_BODY;
                        return [2 /*return*/];
                    }
                    stripKeys = function (keys, obj) {
                        return Object.fromEntries(Object.entries(obj).filter(function (_a) {
                            var key = _a[0];
                            return !keys.includes(key);
                        }));
                    };
                    input = seperateQueryAndContext(ctx.request.query);
                    if (method !== "GET") {
                        input.payload =
                            method === "POST"
                                ? ctx.request.body
                                : __assign(__assign({}, stripKeys(resourcesMap[resource].meta.uniqueKeyComponents, ctx.request.body)), input.payload);
                    }
                    payload = __assign(__assign({}, input), { requestId: reqId });
                    if (operation === cnst.DELETE.toLowerCase()) {
                        // fighting typescript a little here. a bit tired to work out the details
                        // tslint:disable-next-line: no-string-literal
                        payload["hardDelete"] = !!hardDelete;
                    }
                    asyncServiceResponse = resourcesMap[resource].hasSubquery
                        ? utils_1.callComplexResource(resourcesMap, resource, operation, payload)
                        : resourcesMap[resource][operation](__assign({}, payload));
                    return [4 /*yield*/, asyncServiceResponse];
                case 1:
                    serviceResponse = _b.sent();
                    if (!serviceResponse.result) return [3 /*break*/, 9];
                    sqlString = serviceResponse.result.sql.toString();
                    serviceResponse.result.sqlString = sqlString;
                    if (ctx.get(cnst.HEADER_GET_SQL)) {
                        ctx.set(cnst.HEADER_SQL, sqlString);
                    }
                    if (!(category === "service")) return [3 /*break*/, 8];
                    dbCallSuccessful = true;
                    _b.label = 2;
                case 2:
                    _b.trys.push([2, 4, , 5]);
                    return [4 /*yield*/, serviceResponse.result.sql];
                case 3:
                    _records = _b.sent();
                    records = resourcesMap[resource].transformRecords(_records);
                    return [3 /*break*/, 5];
                case 4:
                    err_1 = _b.sent();
                    records = [
                        {
                            error: err_1,
                            request_detail: {
                                category: category,
                                resource: resource,
                                method: method,
                                record: record,
                                reqId: reqId,
                                operation: operation,
                                sql: serviceResponse.result.sql.toString()
                            }
                        },
                    ]; // put in array so `output` defined correcly with `record` ternary
                    logger.error(records[0], cnst.DB_CALL_FAILED);
                    ctx.response.status = HTTP_STATUS.INTERNAL_SERVER_ERROR;
                    dbCallSuccessful = false;
                    return [3 /*break*/, 5];
                case 5:
                    if (!(dbCallSuccessful &&
                        operation === "search" &&
                        ctx.get(cnst.HEADER_GET_COUNT))) return [3 /*break*/, 8];
                    args = {
                        payload: input.payload,
                        reqId: reqId
                    };
                    _asyncServiceResponseCount = resourcesMap[resource].hasSubquery
                        ? utils_1.callComplexResource(resourcesMap, resource, operation, args)
                        : resourcesMap[resource][operation](args);
                    return [4 /*yield*/, _asyncServiceResponseCount];
                case 6:
                    searchCountResult = (_b.sent()).result;
                    sqlSearchCount = database_1.genCountQuery(db, searchCountResult.sql);
                    return [4 /*yield*/, sqlSearchCount];
                case 7:
                    count = (_b.sent())[0].count;
                    ctx.set(cnst.HEADER_COUNT, count);
                    _b.label = 8;
                case 8:
                    delete serviceResponse.result.sql;
                    return [3 /*break*/, 10];
                case 9:
                    ctx.response.status = HTTP_STATUS.BAD_REQUEST;
                    ctx.response.body = serviceResponse;
                    return [2 /*return*/];
                case 10:
                    if (category === "service") {
                        output = record
                            ? Array.isArray(records)
                                ? records[0]
                                : { count: records } // unique resources that are not arrays are only delete
                            : method !== "POST"
                                ? records
                                : Array.isArray(payload.payload)
                                    ? records
                                    : records[0];
                    }
                    else {
                        // 'debug' only
                        output = {
                            now: Date.now(),
                            reqId: reqId,
                            url: url,
                            record: record,
                            method: method,
                            category: category,
                            resource: resource,
                            operation: operation,
                            input: input,
                            serviceResponse: serviceResponse
                        };
                    }
                    if ([null, undefined].includes(output)) {
                        ctx.status = HTTP_STATUS.NOT_FOUND;
                        return [2 /*return*/];
                    }
                    // not all dialects return data. for those that dont -- send 204 & NOBODY
                    if (!supportsReturn &&
                        [cnst.CREATE.toLowerCase(), cnst.UPDATE.toLowerCase()].includes(operation)) {
                        ctx.status = HTTP_STATUS.NO_CONTENT;
                        return [2 /*return*/];
                    }
                    ctx.response.body = output;
                    return [2 /*return*/];
            }
        });
    }); };
};
//# sourceMappingURL=views.js.map