UNPKG

type-arango

Version:

ArangoDB Foxx decorators and utilities for TypeScript

814 lines 39.8 kB
"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 __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; var __spreadArrays = (this && this.__spreadArrays) || function () { for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length; for (var r = Array(s), k = 0, i = 0; i < il; i++) for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++) r[k] = a[j]; return r; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Route = exports.getRouteForCollection = void 0; var _1 = require("."); var index_1 = require("../index"); var joi_1 = require("../joi"); var utils_1 = require("../utils"); var Scalar_model_1 = require("./Scalar.model"); var errors_1 = require("../errors"); var REGEX_PATH_PARAM = /:+([^=/?&]+)[=]?([^/?&]+)?/gi; var mime = ['application/json']; function getRouteForCollection(method, opt, collection) { if (opt === void 0) { opt = {}; } var route = index_1.routes.find(function (route) { return route.col === collection && route.method === method && (route.path === opt.path || (!opt.path && !route.isCustom)); }); if (route) { route.opt = Object.assign(opt, route.opt); } else { route = new Route(method, collection, opt); index_1.routes.push(route); } return route; } exports.getRouteForCollection = getRouteForCollection; var Route = /** @class */ (function () { function Route(method, col, opt) { this.method = method; this.col = col; this.opt = opt; this.isCustom = !!opt.handler; //!!opt.path this.path = typeof opt.path === 'string' ? opt.path : Route.defaultPath(col, method); // is ClassDecorator, save roles to collection if (opt.roles && opt.roles.length) { if (method === 'post') col.addRoles('creators', opt.roles); else if (method === 'get') col.addRoles('readers', opt.roles); else if (method === 'delete') col.addRoles('deleters', opt.roles); else col.addRoles('updaters', opt.roles); } // try to read from collection roles if (!opt.roles) { if (method === 'get') opt.roles = col.roles.readers; else if (method === 'post') opt.roles = col.roles.creators; else if (method === 'delete') opt.roles = col.roles.deleters; else opt.roles = col.roles.updaters; if (!opt.roles.length) opt.roles = opt.roles.concat(index_1.config.requiredRolesFallback || []); } // inherit cache from collection if (!opt.cache && col.opt.cache) { opt.cache = col.opt.cache; } } Route.defaultPath = function (col, method) { var path = col.route + '/:_key'; // remove `_key` from path when `allowUserKeys` is set to true and `type` isn't set to `autoincrement` or no options are given if (method === 'post' && !col.allowUserKeys) { path = col.route; } return path; }; Route.parsePath = function (opt, collectionName) { var path = opt.path; if (!path) { if (!path && opt.action === 'list') path = collectionName; if (path === undefined) return opt; } index_1.logger.debug('Parsing route path: %s', path); path = path.startsWith('/') ? path.substr(1) : path; if (collectionName) { // paths starting with ! aren't bound to the current scope if (path.startsWith('!')) { path = path.substr(1); } else if (!path.startsWith(collectionName)) { path = collectionName + '/' + opt.path; } } var query = path.includes('?') ? path.substr(path.indexOf('?') + 1) : ''; path = opt.path = query ? path.substr(0, path.length - query.length - 1) : path; // pathParams var str = path; var match; while ((match = REGEX_PATH_PARAM.exec(str)) !== null) { var scalar = new Scalar_model_1.Scalar(match[2], match[1]); scalar.isRequired = true; opt.path = opt.path.replace('=' + match[2], ''); opt.pathParams = utils_1.toArray(opt.pathParams).concat([[ scalar.name, scalar.joi, scalar.requiredIcon + ' ' + (scalar.isRequired ? '**Required' : '**Optional') + (" path parameter** \u2002`[ " + scalar.name + ": " + scalar + " ]`\n \u3000\u2002`Example: " + scalar.path + "`") ]]); } if (opt.pathParams) index_1.logger.debug('Added `pathParams` %o', opt.pathParams); // queryParams query.split('&').filter(function (f) { return f; }).forEach(function (part) { var scalar = new Scalar_model_1.Scalar(part); opt.queryParams = utils_1.toArray(opt.queryParams).concat([[ scalar.name, scalar.joi, scalar.requiredIcon + ' ' + (scalar.isRequired ? '**Required' : '**Optional') + (" query parameter** \u2002`[ " + scalar.name + ": " + scalar + " ]`\n \u3000\u2002`Example: ?" + scalar.query + "`") ]]); }); if (opt.queryParams) index_1.logger.debug('Added `queryParams` %o', opt.queryParams); return opt; }; Route.prototype.setup = function (router) { var _a; var _b = this, isCustom = _b.isCustom, method = _b.method, path = _b.path, col = _b.col, opt = _b.opt; var response = __assign({ status: 'ok', schema: col.doc.joi, mime: mime }, (opt.response || {})); var _c = opt, body = _c.body, deprecated = _c.deprecated, _d = _c.description, description = _d === void 0 ? '' : _d, _e = _c.errors, errors = _e === void 0 ? [] : _e, handler = _c.handler, handlerName = _c.handlerName, _f = _c.pathParams, pathParams = _f === void 0 ? [] : _f, _g = _c.queryParams, queryParams = _g === void 0 ? [] : _g, relations = _c.relations, roles = _c.roles, cache = _c.cache, _h = _c.summary, summary = _h === void 0 ? '' : _h, tags = _c.tags; // use collection option "relations" by default if (!relations && col.opt && col.opt.relations) { relations = col.opt.relations; } // let body: RouteBody = opt.body var name = col.doc.name; var validParams = []; if (this.path.includes('/:_key') && (method !== 'post' || col.allowUserKeys)) { // if(this.path.includes('/:_key')){ pathParams = pathParams.concat([['_key', joi_1.Joi.string(), '🆔 **Document identifier**']]); } var sortValues = Object.keys(col.doc.schema); // `relation` query param var resolvable = relations === true ? Object.keys(col.doc.relation) : relations; if (resolvable && resolvable.length && (method === 'get' || opt.action === 'list')) { queryParams.push([ 'relations', joi_1.Joi.string(), "\uD83D\uDCCC **List of related entities to fetch and return**\n &nbsp; &nbsp; &nbsp; `Values: " + resolvable.join(', ') + "`\n &nbsp; &nbsp; &nbsp; `Example: ?relations=" + resolvable.slice(0, 2).join(',') + "`" ]); } // `attributes` query param if (['get', 'post', 'patch', 'put'].includes(method) || opt.action === 'list') { queryParams.push([ 'attributes', joi_1.Joi.string(), "\u2702\uFE0F **List of attributes to return**\n &nbsp; &nbsp; &nbsp; `Values: " + sortValues.join(', ') + "`\n &nbsp; &nbsp; &nbsp; `Example: ?attributes=" + sortValues.slice(0, 2).join(',') + "`" ]); } tags = tags || [col.doc.name]; // 404 if (['get', 'patch', 'delete'].includes(method)) errors = __spreadArrays(errors, [['not found', name + " document not found in " + col.name + " collection."]]); /** * Joi.required() is documented but ignored by Foxx: * https://docs.arangodb.com/devel/Manual/Foxx/Reference/Routers/Endpoints.html#body */ if (isCustom) { queryParams = __spreadArrays((opt.queryParams || []), (queryParams || [])); } else { if (opt.action === 'list') { summary = summary || "Returns " + name + "[]"; description = description || "Prints a list of " + name + " documents of the collection **" + col.name + "**."; response.description = "Array of " + name + " documents of the " + col.name + " collection."; handler = handler || Route.list; var qp = Object.values(queryParams).map(function (qp) { return qp[0]; }); if (!qp.includes('limit')) queryParams.push(['limit', joi_1.Joi.number().min(1).max(index_1.config.defaultListLimitMax).default(index_1.config.defaultListLimit), '**#️⃣ Limit results**\n     `Example: ?limit=100`']); if (!qp.includes('offset')) queryParams.push(['offset', joi_1.Joi.number().default(0), '**⏭️ Skip results**\n     `Example: ?offset=25`']); if (!qp.includes('sort')) queryParams.push(['sort', (_a = joi_1.Joi.any()).valid.apply(_a, sortValues), '**🔀 Sort results by attribute**\n     `Values: ' + sortValues.join(', ') + '`\n     `Example: ?sort=' + sortValues[0] + '`']); if (!qp.includes('order')) queryParams.push(['order', joi_1.Joi.any().valid('ASC', 'DESC').default('ASC'), '**🔃 Order results**\n     `Values: ASC, DESC`\n     `Example: ?order=DESC`']); } else switch (method) { default: case 'get': summary = summary || "Returns " + name; description = description || "Prints a " + name + " document of the collection **" + col.name + "**."; response.description = name + " document of the " + col.name + " collection."; break; case 'post': summary = summary || "Inserts " + name; description = description || "Inserts and prints the **" + name + "** document of the collection **" + col.name + "**."; body = body || [col.doc.joi, "\uD83D\uDCD1 **" + name + " document to create**"]; response.status = 'created'; response.description = "The newly created " + name + " document of the " + col.name + " collection."; break; case 'patch': summary = summary || "Updates " + name; description = description || "Updates and prints the **" + name + "** document of the collection **" + col.name + "**."; body = body || [col.doc.joi, "\uD83D\uDCD1 **Partial " + name + " document to update**"]; response.description = "The updated " + name + " document of the " + col.name + " collection."; break; case 'put': summary = summary || "Replaces " + name; description = description || "Replaces and prints the **" + name + "** document of the collection **" + col.name + "**."; body = body || [col.doc.joi, "\uD83D\uDCD1 **" + name + " document to replace**"]; response.description = "The replaced " + name + " document of the " + col.name + " collection."; break; case 'delete': summary = summary || "Deletes " + name; description = description || "Deletes a **" + name + "** document of the collection **" + col.name + "**. Prints an empty body on success."; response.status = 'no content'; response.schema = null; // ArangoDB Bug: null is not accepted response.description = 'No content.'; queryParams = []; break; } } if (cache) { if (index_1.config.disableCache) { cache = 0; } else { description = '**✴️ Cache:** `' + (typeof cache === 'string' ? cache : cache < Infinity ? cache + 'min' : 'endless') + '`<br/><br/>' + description; cache = typeof cache === 'number' ? "max-age=" + cache * 60 + ", private" : cache; } } if (opt.action !== 'list' && handler) { var handlerId = col.name + '.' + handlerName + '()'; summary = opt.summary || 'Calls 👁️️️️️ ' + handlerId; description = '**👁️️️️️ Handler:** `' + handlerId + '`<br/><br/>' + (index_1.config.exposeRouteFunctionsToSwagger ? '<pre>' + col.name + '.' + (handler.toString()) + '</pre><br/>' : '') + description; } var routeAuths = col.routeAuths, routeRoles = col.routeRoles; if (roles || routeAuths.length || routeRoles.length) { var rolesText = roles ? '[' + (roles || []).join(', ') + ']' : null; description = '**🔐 Authorization:** `getAuthorizedRoles(' + (routeRoles.length ? '[...getUserRoles(), ...Route.roles' + (routeRoles.length > 1 ? '[' + routeRoles.length + ']' : '') + ']' : 'getUserRoles()') + (rolesText ? ', ' + rolesText : '') + ') ' + (routeAuths.length ? '&& Route.auth' + (routeAuths.length > 1 ? '[' + routeAuths.length + ']' : '') : '') + '`<br/><br/>' + description; } if (body && !Array.isArray(body)) body = [body, '📑 **Body schema**']; var action = opt.action || method === 'get' ? 'read' : method === 'post' ? 'create' : method === 'delete' ? 'delete' : 'update'; // add query & pathParams to `param` queryParams.forEach(function (qp) { return validParams.push(qp[0]); }); pathParams.forEach(function (qp) { return validParams.push(qp[0]); }); // add body schema keys to `validParams` if (body && body[0]) body[0]._inner.children.forEach(function (o) { return validParams.push(o.key); }); return Route.setup(col, router, method, action, path, roles, validParams, pathParams, queryParams, response, errors, cache, summary, description, body, deprecated, tags, resolvable, handler); }; /** * Setup route */ Route.setup = function (col, router, method, action, path, roles, validParams, pathParams, queryParams, response, errors, cache, summary, description, body, deprecated, tags, resolvable, handler) { if (roles === void 0) { roles = []; } var aql = utils_1.arango.aql, query = utils_1.arango.query; var info = index_1.logger.info, debug = index_1.logger.debug, warn = index_1.logger.warn; var name = col.name, routeAuths = col.routeAuths, routeRoles = col.routeRoles; var doc = col.doc; info('- Setup %s %s', method.toUpperCase(), path); var _ = Route.fetch.bind(null, query); var collection = utils_1.db._collection(col.name); var document = Route.document.bind(null, collection, doc); var insert = Route.insert.bind(null, collection, doc); var update = Route.modify.bind(null, collection, doc, 'update'); var replace = Route.modify.bind(null, collection, doc, 'replace'); var remove = Route.remove.bind(null, collection, doc); var relations = Route.relations.bind(null, doc, resolvable); var route = router[method](path, function (req, res) { var getUserRoles = index_1.config.getUserRoles, getAuthorizedRoles = index_1.config.getAuthorizedRoles, throwForbidden = index_1.config.throwForbidden; info('[client %s] %s %s', req.remoteAddress, req.method.toUpperCase(), req.path); debug('Required roles %o', roles); // authorize by route/collection roles var userRoles = getUserRoles(req); var tmp = { doc: null }; // change array[] to array req.queryParams = __assign(__assign({}, req.queryParams), Object.keys(req.queryParams) .filter(function (k) { return k.endsWith('[]'); }) .reduce(function (p, n) { var _a; return (__assign(__assign({}, p), (_a = {}, _a[n.replace('[]', '')] = utils_1.toArray(req.queryParams[n]), _a))); }, {})); var param = validParams.reduce(function (c, n) { c[n] = req.pathParams[n] !== undefined ? req.pathParams[n] : req.queryParams[n] !== undefined ? req.queryParams[n] : req.body ? req.body[n] : undefined; if (c[n] === undefined) delete c[n]; return c; }, {}); var requestedAttributes = req.queryParams.attributes ? typeof req.queryParams.attributes === 'string' ? req.queryParams.attributes.split(',') : req.queryParams.attributes : null; var _key = param._key; var args = { // @deprecated $: query, _: _, _key: _key, action: action, aql: aql, auth: Route.auth.bind(null, req, res, roles, routeAuths), collection: collection, db: utils_1.db, document: document.bind(null, tmp, action !== 'create', _key), error: Route.error.bind(null, res), exists: collection.exists.bind(collection), fetch: _, hasAuth: !!col.routeAuths.length, insert: insert, method: method, name: name, param: param, path: path, query: Route.query.bind(null), relations: relations.bind(null, req, res, requestedAttributes, userRoles), remove: remove.bind(null, _key), replace: replace.bind(null, _key), req: req, requestedAttributes: requestedAttributes, res: res, roles: roles, session: Route.session.bind(null, req, res), update: update.bind(null, _key), validParams: validParams }; if (routeRoles.length) { userRoles = routeRoles.map(function (f) { return f(args) || []; }).reduce(function (c, n) { return c.concat(n); }, userRoles); } debug('Provided roles %o', userRoles); var authorizedRoles = getAuthorizedRoles(userRoles, roles || []); info('Authorized roles %o', authorizedRoles); if (!authorizedRoles.length) { warn('Forbidden [client %s] %s %s', req.remoteAddress, req.method.toUpperCase(), req.path); return res.throw(throwForbidden || 'forbidden'); } var stripAttributesRead = doc.stripAttributeList(userRoles, 'read'); var stripAttributesWrite = doc.stripAttributeList(userRoles, 'write'); // build route argument var data = Object.assign(args, { req: req, res: res, userRoles: userRoles, send: Route.send.bind(null, req, res, doc, stripAttributesRead, args.requestedAttributes), json: Route.json.bind(null, req, res, doc, body ? body[0] : null, stripAttributesWrite), deprecated: deprecated, tags: tags, summary: summary, description: description }); if (cache) { res.setHeader('cache-control', cache); } debug('Call route handler for %o %o', method.toUpperCase(), path); if (handler) { var result = handler(data); return result ? data.send(result) : result; } return data.send(Route[method](data)); }) .summary(summary).description(description); // add path params pathParams.forEach(function (a) { return route.pathParam.apply(route, a); }); // add query params queryParams.forEach(function (a) { return route.queryParam.apply(route, a); }); // add response information if (response) { var status_1 = response.status, schema = response.schema, mime_1 = response.mime, description_1 = response.description; route.response(status_1, schema, mime_1, description_1); } // add error information errors.forEach(function (a) { return route.response.apply(route, a); }); // add tags if (tags) route.tag.apply(route, tags); // add body if (body) { route.body(body[0], response.mime, body[1]); } // else route.body(null) // seems like a bug? // deprecate if (deprecated) route.deprecated(true); return route; }; /** * Request based document cache in order to avoid duplicate calls to collection.document * (only active when document() is called without an argument) */ Route.document = function (collection, doc, tmp, canThrow, key, selector) { if (tmp === void 0) { tmp = {}; } if (canThrow === void 0) { canThrow = true; } var k = selector || key; if (!k) throw new errors_1.MissingKeyError(collection.name()); // temp cache to avoid duplicate reads if (!selector || selector === key) { if (tmp.doc) return tmp.doc; return tmp.doc = Route.documentRead(collection, doc, canThrow, k); } return Route.documentRead(collection, doc, canThrow, k); }; /** * Used by Route.document */ Route.documentRead = function (collection, doc, canThrow, selector) { if (canThrow === void 0) { canThrow = true; } selector = doc.emitBefore('document', selector); try { return doc.emitAfter('document', collection.document(selector), selector); } catch (e) { if (canThrow) throw e; return {}; } }; /** * Executes collection.insert, triggers listeners */ Route.insert = function (collection, doc, data, options) { data = doc.emitBefore('write', doc.emitBefore('insert', data)); return doc.emitAfter('write', doc.emitAfter('insert', collection.insert(data, options))); }; /** * Executes collection.update / collection.replace, triggers listeners */ Route.modify = function (collection, doc, method, key, selectorOrData, dataOrOptions, options) { var k = key; var d; var o; // selector is selector if (typeof selectorOrData === 'string' || (selectorOrData && (selectorOrData._key || selectorOrData._id))) { k = selectorOrData; d = dataOrOptions; o = options; } // selector is document else { d = selectorOrData; o = dataOrOptions; } if (!k) throw new errors_1.MissingKeyError(collection.name()); d = doc.emitBefore('write', doc.emitBefore('modify', doc.emitBefore(method, d, k), k), k); // execute the modification var action = function () { switch (method) { case 'replace': // create non-existing documents for "replace" if (!collection.exists(k)) { collection.insert(__assign({ _key: key }, d), o); } break; } return collection[method](k, d, o); }; return doc.emitAfter('write', doc.emitAfter('modify', doc.emitAfter(method, action(), k), k), k); }; /** * Executes collection.remove, triggers listeners */ Route.remove = function (collection, doc, key, selector, options) { var k = key; var o = {}; // selector is selector if (typeof selector === 'string' || (selector && (selector.hasOwnProperty('_key') || selector.hasOwnProperty('_id')))) { k = selector; o = options; } // selector is option else if (selector) { o = selector; } if (!k) throw new errors_1.MissingKeyError(collection.name()); k = doc.emitBefore('remove', k); return doc.emitAfter('remove', collection.remove(k, o)); }; /** * Get or set the session */ Route.session = function (req, res, dataOrEnforce) { var enforce = dataOrEnforce === true; var data = dataOrEnforce && !enforce ? dataOrEnforce : null; // read if (!data || enforce) { if (enforce && !req.session.uid) { res.throw(index_1.config.throwUnauthorized, { cause: new Error('Session invalid') }); } return req.session; } // write Object.keys(data).forEach(function (k) { return req.session[k] = data[k]; }); return req.session; }; /** * Authorizes a document by calling the Route.auth handlers */ Route.auth = function (req, res, roles, authorizes, document, method, action, canThrow) { if (canThrow === void 0) { canThrow = true; } if (!authorizes.length) return document; var args = { session: req.session, roles: roles, doc: document, document: document, method: method, action: action, req: req, res: res }; var success = !(authorizes || []).find(function (f) { return !f(args); }); if (!success) { if (canThrow) res.throw(index_1.config.throwForbidden || 'forbidden'); return false; } return document; }; /** * Execute a query */ Route.query = function (query, _options) { index_1.logger.debug('Query %o', query); return utils_1.db._query.apply(utils_1.db, arguments); }; /** * Fetch data from a query * _`FOR Item IN Items RETURN Item` => Item[] */ Route.fetch = function (query, strings) { var args = []; for (var _i = 2; _i < arguments.length; _i++) { args[_i - 2] = arguments[_i]; } index_1.logger.debug('Query %o', query); return query.apply(void 0, __spreadArrays([strings], args)).toArray(); }; /** * Returns a picked version containing only writable attributes from `req.json()` */ Route.json = function (req, res, document, body, stripAttributes, omitUnwritableAttributes) { if (omitUnwritableAttributes === void 0) { omitUnwritableAttributes = true; } var json = req.json(); if (body && body._inner) { // add joi defaults to result, this should've been done by Foxx instead of me json = utils_1.removeValues(utils_1.joiDefaults(body, json), undefined); } // remove un-writable attributes if (json && omitUnwritableAttributes) json = utils_1.omit(json, stripAttributes); // pass to config.fromClient or Document.fromClient if (index_1.config.fromClient || document.fromClient) { var args = { req: req, res: res, _key: req.param('_key') || '', requestedAttributes: req.param('attributes') || null, session: Route.session.bind(null, req, res), error: Route.error.bind(null, res) }; if (index_1.config.fromClient) json = index_1.config.fromClient(json, args); if (document.fromClient) json = document.fromClient(json, args); } index_1.logger.debug('Read input json() %o', json); return json; }; /** * Throws an error with an optional reason */ Route.error = function (res, status, reason) { return reason ? res.throw(status, reason) : res.throw(status); }; /** * Map data for client */ Route.forClient = function (req, res, document, stripAttributes, requestedAttributes, omitUnreadableAttributes, doc) { if (omitUnreadableAttributes === void 0) { omitUnreadableAttributes = true; } if (index_1.config.stripDocumentKey && doc._key) delete doc._key; if (index_1.config.stripDocumentId && doc._id) delete doc._id; if (!['PATCH', 'PUT'].includes(req.method) && index_1.config.stripDocumentRev && doc._key) delete doc._rev; var resp = requestedAttributes ? utils_1.pick(doc, requestedAttributes.map(function (a) { return a.split('.')[0]; })) : doc; resp = omitUnreadableAttributes ? utils_1.omit(resp, stripAttributes) : resp; if (index_1.config.forClient || document.forClient) { var args_1 = { req: req, res: res, _key: req.param('_key') || '', requestedAttributes: requestedAttributes, //req.param('attributes') || null, session: Route.session.bind(null, req, res), error: Route.error.bind(null, res) }; // use forClient on related attributes Object.keys(doc).forEach(function (k) { return doc[k] instanceof _1.Entity ? doc[k] = doc[k]._doc.forClient(doc[k].toObject(), args_1) : Array.isArray(doc[k]) && doc[k][0] instanceof _1.Entity ? doc[k] = doc[k].map(function (e) { return e._doc.forClient(e.toObject(), args_1); }) : null; }); if (index_1.config.forClient) resp = index_1.config.forClient(resp, args_1); if (document.forClient) resp = document.forClient(resp, args_1); } return resp; }; /** * Send / map response */ Route.send = function (req, res, document, stripAttributes, requestedAttributes, doc, omitUnreadableAttributes) { if (omitUnreadableAttributes === void 0) { omitUnreadableAttributes = true; } var call = Route.forClient.bind(null, req, res, document, stripAttributes, requestedAttributes, omitUnreadableAttributes); var resp; if (Array.isArray(doc)) { resp = doc.map(function (d) { return typeof d === 'object' && d ? call(__assign({}, d)) : d; }); } else if (doc && typeof doc === 'object') { resp = call(__assign({}, doc)); } else { resp = doc; } if (index_1.config.header) { var headers = typeof index_1.config.header === 'function' ? index_1.config.header(req, res) : index_1.config.header; index_1.logger.debug('Set response headers %o', headers); res.set(headers); } index_1.logger.debug('Send response %o', resp); return res.send(resp); }; /** * Fetch related documents */ Route.relations = function (doc, resolvable, req, res, attributes, userRoles, data) { if (resolvable === void 0) { resolvable = []; } if (attributes === void 0) { attributes = []; } if (!resolvable.length) return data; var relations = req.queryParams.relations; if (!relations) return data; attributes = Array.isArray(attributes) ? attributes.reduce(function (attr, next) { next .split('.') .forEach(function (_part, index, parts) { return index && !attr.includes(parts.slice(0, index).join('.')) && attr.push(parts.slice(0, index).join('.')); }); if (!attr.includes(next)) attr.push(next); return attr; }, []) : []; relations = relations .split(',') .filter(function (r) { return resolvable.includes(r); }); var r = Object.assign({}, data); var forClient = Route.forClient.bind(null, req, res); var _loop_1 = function (relation) { var id = ''; var document_1 = doc; relation.split('.').reduce(function (data, part) { var parent = document_1; id += part + '.'; document_1 = document_1.relation[part].document; var keep = attributes.filter(function (a) { return a.startsWith(id); }).map(function (a) { return a.substr(id.length); }); if (!keep.length) keep = Object.keys(document_1.attribute); var fc = forClient.bind(null, document_1, document_1.stripAttributeList(userRoles, 'read'), keep, true); // value can be an array for (var _i = 0, _a = utils_1.toArray(data); _i < _a.length; _i++) { var o = _a[_i]; var r_1 = parent.resolveRelation(o, part, keep); if (!r_1) { o[part] = null; continue; } if (Array.isArray(r_1)) { o[part] = r_1.map(function (r) { return fc(__assign({}, r)); }); } else if (doc && typeof doc === 'object') { o[part] = fc(__assign({}, r_1)); } } return data[part]; }, r); }; // append related documents for (var _i = 0, relations_1 = relations; _i < relations_1.length; _i++) { var relation = relations_1[_i]; _loop_1(relation); } return r; }; /** * Read document */ Route.get = function (_a) { var _key = _a._key, auth = _a.auth, name = _a.name, document = _a.document, relations = _a.relations; index_1.logger.info('GET %s/%s', name, _key); var data = auth(document(), 'get', 'read'); if (!data) return false; return relations(data); }; /** * List documents */ Route.list = function (_a) { var name = _a.name, param = _a.param, hasAuth = _a.hasAuth, auth = _a.auth, relations = _a.relations; index_1.logger.info('LIST %s/%s', name); var attributes = param.attributes, offset = param.offset, _b = param.limit, limit = _b === void 0 ? index_1.config.defaultListLimit : _b, sort = param.sort, order = param.order, filter = __rest(param, ["attributes", "offset", "limit", "sort", "order"]); delete filter.relations; var q = { filter: filter, sort: sort ? [sort + ' ' + (order || 'ASC')] : undefined, limit: offset ? [offset, limit] : limit }; return utils_1.db ._query(utils_1.queryBuilder(name, q)) .toArray() .filter(function (doc) { return !hasAuth || auth(doc, 'get', 'list'); }) .map(function (doc) { return relations(doc); }); }; /** * Create document */ Route.post = function (_a) { var json = _a.json, res = _a.res, auth = _a.auth, _key = _a._key, name = _a.name, exists = _a.exists, insert = _a.insert; var body = json(); _key = _key || body._key; body._key = _key; index_1.logger.info('POST %s/%s', name, _key || 'n/a', body); if (_key && exists(_key)) return res.throw(409, 'Document already exists'); var doc = auth(_key ? Object.assign(body, { _key: _key }) : body, 'post', 'create'); if (!doc) return; return Object.assign(doc, insert(doc)); }; /** * Update document */ Route.patch = function (_a) { var json = _a.json, res = _a.res, _key = _a._key, document = _a.document, exists = _a.exists, update = _a.update, name = _a.name, hasAuth = _a.hasAuth, auth = _a.auth; index_1.logger.info('PATCH %s/%s', name, _key); if (!exists(_key)) return res.throw(409, 'Document does not exist'); var doc = json(); if (hasAuth && !auth(Object.assign(document(), doc), 'patch', 'update')) return; return update(_key, doc, { returnNew: true }).new; }; /** * Replace document */ Route.put = function (_a) { var json = _a.json, _key = _a._key, document = _a.document, replace = _a.replace, name = _a.name, hasAuth = _a.hasAuth, auth = _a.auth; index_1.logger.info('PUT %s/%s', name, _key); var doc = json(); if (hasAuth && auth(Object.assign(document() || {}, doc), 'put', 'update')) return; return replace(_key, doc, { returnNew: true }).new; }; /** * Delete document */ Route.delete = function (_a) { var res = _a.res, _key = _a._key, document = _a.document, exists = _a.exists, remove = _a.remove, name = _a.name, hasAuth = _a.hasAuth, auth = _a.auth; index_1.logger.info('DELETE %s/%s', name, _key); if (!exists(_key)) return res.throw(409, 'Document does not exist'); if (hasAuth && !auth(document(), 'delete', 'delete')) return; remove(_key); return null; }; return Route; }()); exports.Route = Route; //# sourceMappingURL=Route.model.js.map