type-arango
Version:
ArangoDB Foxx decorators and utilities for TypeScript
814 lines • 39.8 kB
JavaScript
;
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 `Values: " + resolvable.join(', ') + "`\n `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 `Values: " + sortValues.join(', ') + "`\n `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