UNPKG

mongoose-restapi-ui

Version:

Rest api library for your mongoose apps, with react ui: mongoose-restapi-ui-component react library

703 lines (702 loc) 33.3 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) t[p[i]] = s[p[i]]; return t; }; Object.defineProperty(exports, "__esModule", { value: true }); var mongoose_1 = require("mongoose"); var events_1 = require("events"); var model_1 = require("../definitions/model"); var utils = require("../utils"); var query_1 = require("./query"); var PermissionClass_1 = require("./PermissionClass"); var defaultOptions = { MAX_RESULTS: 100, name: 'name', getFilterByPermissions: function (req, callback) { callback(null, null); } }; var routerWeakMap = new WeakMap(); function getOptions(options) { if (options) { var hasEditPermission = options.hasEditPermission, hasAddPermission = options.hasAddPermission, hasUpdatePermission = options.hasUpdatePermission, hasDeletePermission = options.hasDeletePermission; if (hasEditPermission) { var add = hasAddPermission; var updatePermission = hasUpdatePermission ? hasUpdatePermission : hasEditPermission; var deletePermission = hasDeletePermission ? hasDeletePermission : hasEditPermission; return __assign({}, defaultOptions, options, { hasAddPermission: add, hasUpdatePermission: updatePermission, hasDeletePermission: deletePermission }); } return __assign({}, defaultOptions, options); } return defaultOptions; } var RestApiPath = /** @class */ (function () { function RestApiPath(router, route, model, options, mongo4) { if (mongo4 === void 0) { mongo4 = true; } this._getType = function (type) { return type.replace('Schema', ''); }; routerWeakMap.set(this, router); this._options = getOptions(options); this._route = route; this._model = model; this._emitter = new events_1.EventEmitter(); this._paths = this._getModelProperties(model.schema); this.isMongo4 = mongo4; this._generateFullPathTypes(); } Object.defineProperty(RestApiPath.prototype, "MAX_RESULTS", { get: function () { return this.options.MAX_RESULTS; }, enumerable: true, configurable: true }); Object.defineProperty(RestApiPath.prototype, "emitter", { get: function () { return this._emitter; }, enumerable: true, configurable: true }); Object.defineProperty(RestApiPath.prototype, "model", { get: function () { return this._model; }, enumerable: true, configurable: true }); Object.defineProperty(RestApiPath.prototype, "route", { get: function () { return this._route; }, enumerable: true, configurable: true }); Object.defineProperty(RestApiPath.prototype, "router", { get: function () { return routerWeakMap.get(this); }, enumerable: true, configurable: true }); Object.defineProperty(RestApiPath.prototype, "options", { get: function () { return this._options; }, enumerable: true, configurable: true }); Object.defineProperty(RestApiPath.prototype, "paths", { get: function () { return this._paths; }, enumerable: true, configurable: true }); Object.defineProperty(RestApiPath.prototype, "infoModel", { get: function () { return { name: this.model.modelName, route: this.route, label: this.options.name, paths: this.paths, model: this.model }; }, enumerable: true, configurable: true }); Object.defineProperty(RestApiPath.prototype, "arrayFullPaths", { get: function () { return this._arrayFullPaths; }, enumerable: true, configurable: true }); Object.defineProperty(RestApiPath.prototype, "refFullPaths", { get: function () { return this._refFullPaths; }, enumerable: true, configurable: true }); Object.defineProperty(RestApiPath.prototype, "fullPathTypes", { get: function () { return this._fullPathTypes; }, enumerable: true, configurable: true }); Object.defineProperty(RestApiPath.prototype, "numberFullPaths", { get: function () { return this._numberFullPaths; }, enumerable: true, configurable: true }); Object.defineProperty(RestApiPath.prototype, "stringFullPaths", { get: function () { return this._stringFullPaths; }, enumerable: true, configurable: true }); Object.defineProperty(RestApiPath.prototype, "booleanFullPaths", { get: function () { return this._booleanFullPaths; }, enumerable: true, configurable: true }); Object.defineProperty(RestApiPath.prototype, "dateFullPaths", { get: function () { return this._dateFullPaths; }, enumerable: true, configurable: true }); Object.defineProperty(RestApiPath.prototype, "objectIdFullPaths", { get: function () { return this._objectIdFullPaths; }, enumerable: true, configurable: true }); RestApiPath.prototype._generateFullPathTypes = function () { var fullPathTypes = this._getFullPathTypes(); this._fullPathTypes = fullPathTypes; this._stringFullPaths = Object.keys(fullPathTypes).filter(function (fullPath) { return fullPathTypes[fullPath].type === 'String' || fullPathTypes[fullPath].type === 'ArrayString'; }); this._booleanFullPaths = Object.keys(fullPathTypes).filter(function (fullPath) { return fullPathTypes[fullPath].type === 'Boolean' || fullPathTypes[fullPath].type === 'ArrayBoolean'; }); this._dateFullPaths = Object.keys(fullPathTypes).filter(function (fullPath) { return fullPathTypes[fullPath].type === 'Date' || fullPathTypes[fullPath].type === 'ArrayDate'; }); this._objectIdFullPaths = Object.keys(fullPathTypes).filter(function (fullPath) { return fullPathTypes[fullPath].type === 'ObjectId' || fullPathTypes[fullPath].type === 'ArrayObjectId'; }); this._arrayFullPaths = Object.keys(fullPathTypes).filter(function (fullPath) { return fullPathTypes[fullPath].type.startsWith('Array'); }); this._numberFullPaths = Object.keys(fullPathTypes).filter(function (fullPath) { return fullPathTypes[fullPath].type === 'Number' || fullPathTypes[fullPath].type === 'ArrayNumber'; }); this._refFullPaths = Object.keys(fullPathTypes).filter(function (fullPath) { return fullPathTypes[fullPath].type === 'Ref' || fullPathTypes[fullPath].type === 'ArrayRef'; }); }; RestApiPath.prototype._transformPaths = function (paths) { var _this = this; var pathsWithObject = paths.filter(function (el) { return el.name.indexOf('.') >= 0; }); var pathsWithoutObject = paths.filter(function (el) { return el.name.indexOf('.') < 0; }); var newObjects = []; var visited = []; pathsWithObject.forEach(function (el, key) { if (visited.indexOf(el) < 0) { var base_1 = el.name.split('.').shift(); var newObject = { name: base_1, complex: true, type: 'Object', children: [__assign({}, el, { name: el.name.split('.').slice(1).join('.') })].concat(_this._transformPaths(pathsWithObject.slice(key + 1).filter(function (el) { return el.name.startsWith(base_1 + '.'); }).map(function (el) { return (__assign({}, el, { name: el.name.slice(base_1.length + 1) })); }))) }; pathsWithObject.slice(1).filter(function (el) { return el.name.startsWith(base_1 + '.'); }).forEach(function (el) { visited.push(el); }); var labeledChild = newObject.children.find(function (child) { return child.label; }); if (!labeledChild) _this._warnLabel(newObject.name, { name: newObject.children[0].name, type: newObject.children[0].type }); newObjects.push(__assign({}, newObject, { label: labeledChild ? labeledChild.name : undefined, children: newObject.children.map(function (child) { var label = child.label, others = __rest(child, ["label"]); return others; }) })); } }); return pathsWithoutObject.concat(newObjects); }; RestApiPath.prototype._warnLabel = function (pathName, child) { console.warn("WARNING: " + this.infoModel.name + ": One children of path \"" + pathName + "\" must have a flag with \"label: true\" for UI purpose.\n Suggestion: " + child.name + ": {type: " + child.type + ", label: true}\n\n"); }; RestApiPath.prototype._getModelProperties = function (schema) { var _this = this; return this._transformPaths(Object.keys(schema.paths).map(function (key) { var type = schema.paths[key].constructor.name; if (type === 'SchemaType') { var children_1 = schema.paths[key].schema; var labels = Object.keys(children_1.paths).filter(function (el) { return children_1.paths[el].options.label; }); var path = { name: key, type: 'Object', label: labels.length > 0 ? labels.shift() : undefined, required: schema.requiredPaths(true).indexOf(key) >= 0, children: _this._getModelProperties(schema.paths[key].schema) }; if (labels.length === 0) _this._warnLabel(key, { name: path.children[0].name, type: path.children[0].type }); return path; } if (type === 'DocumentArray') { var children_2 = schema.paths[key].schema; var labels = Object.keys(children_2.paths).filter(function (el) { return children_2.paths[el].options.label; }); var path = { name: key, type: 'Array', complex: true, label: labels.length > 0 ? labels.shift() : undefined, required: schema.requiredPaths(true).indexOf(key) >= 0, children: _this._getModelProperties(schema.paths[key].schema) }; if (labels.length === 0) _this._warnLabel(key, { name: path.children[0].name, type: path.children[0].type }); return path; } if (type === 'ObjectId' && schema.paths[key].options.ref !== undefined) { return { name: key, type: 'Ref', label: schema.paths[key].options.label, to: schema.paths[key].options.ref }; } if (schema.paths[key].caster) { if (schema.paths[key].caster.options.ref) { return { name: key, type: 'ArrayRef', label: schema.paths[key].options.label, required: schema.requiredPaths(true).indexOf(key) >= 0, to: schema.paths[key].caster.options.ref }; } return { name: key, label: schema.paths[key].options.label, auto: schema.paths[key].options.auto, type: 'Array' + schema.paths[key].caster.instance, required: schema.requiredPaths(true).indexOf(key) >= 0 }; } return { name: key, label: schema.paths[key].options.label, auto: schema.paths[key].options.auto, type: _this._getType(schema.paths[key].constructor.name), required: schema.requiredPaths(true).indexOf(key) >= 0 }; })); }; RestApiPath.prototype._getFullPathTypes = function () { function getFullPath(paths) { var fullPaths = {}; paths.forEach(function (path) { if (path.type === 'Array' || path.complex) { var children_3 = getFullPath(path.children); Object.keys(children_3).forEach(function (el) { fullPaths[path.name + "." + el] = children_3[el]; }); } else { fullPaths[path.name] = path; } }); return fullPaths; } return getFullPath(this.paths); }; RestApiPath.prototype.getItem = function (id, callback) { var _this = this; this.model.findById(id, function (err, res) { var _a; if (err || !res) { return _this.model.findOne((_a = {}, _a[_this.options.name] = id, _a), callback); } return callback(err, res); }); }; /* private getEditObjects(Permission: Model<IPermission>, req: UserRequest, callback: (err: Error, res?: Types.ObjectId[]) => void) { const table = this.model.modelName Permission.find({ table, user: req.user._id }, { _id: 1 }, (err, res) => { if (err) return callback(err) callback(null, res.map(el => el.object)) }) } private getAdminObjects(Permission: Model<IPermission>, req: UserRequest, callback: (err: Error, res?: Types.ObjectId[]) => void) { const table = this.model.modelName Permission.find({ table, user: req.user._id }, { _id: 1 }, (err, res) => { if (err) return callback(err) callback(null, res.map(el => el.object)) }) } */ RestApiPath.prototype._addPermission = function (req, Permission, object, callback) { var table = this.model.modelName; if (!Permission) { return callback(null); } var p = new Permission({ table: table, object: object, user: req.user._id, permission: model_1.PermissionEnum.DELETE }); p.save(callback); }; RestApiPath.prototype._deletePermission = function (Permission, object, callback) { var table = this.model.modelName; if (Permission) return Permission.deleteMany({ table: table, object: object }, callback); callback(null); }; RestApiPath.setRoleEndpoints = function (router, route, Role) { router.use("" + (route === '' ? '/' : route), function (req, res, next) { if (req.method === 'GET' || req.method === 'DELETE') return next(); if (req.body) return next(); var data = ''; req.on('data', function (chunk) { data += chunk.toString(); }); req.on('end', function () { try { req.body = JSON.parse(data); next(); } catch (err) { res.status(400).send('Body is not a valid json.'); } }); }); router.use("" + (route === '' ? '/' : route), function (req, res, next) { if (req.user.super_admin) return next(); res.status(403).send(); }); router.get("" + (route === '' ? '/' : route), function (req, res) { Role.find({}, function (err, docs) { if (err) return res.status(500).send(err.message); res.send(docs); }); }); router.post("" + (route === '' ? '/' : route), function (req, res) { var permission = new Role(req.body); permission.save(function (err) { if (err) return res.status(500).send(err.message); res.status(201).send(permission); }); }); router.use((route.endsWith('/') ? route.slice(0, -1) : route) + "/:role", function (req, res, next) { Role.findById(req.params.role, function (err, doc) { if (err) return Role.findOne({ name: req.params.role }, function (err, doc) { if (err) return res.status(500).send(err.message); if (!doc) return res.status(404).send('Not found'); req.role = doc; next(); }); req.role = doc; next(); }); }); router.get((route.endsWith('/') ? route.slice(0, -1) : route) + "/:role", function (req, res) { res.send(req.role); }); router.put((route.endsWith('/') ? route.slice(0, -1) : route) + "/:role", function (req, res) { req.role.name = req.body.name; req.role.schemas = req.body.schemas; req.role.save(function (err) { if (err) return res.status(500).send(err.message); res.status(200).send(req.body); }); }); router.patch((route.endsWith('/') ? route.slice(0, -1) : route) + "/:role", function (req, res) { if (req.body.name) req.role.name = req.body.name; if (req.body.schemas) req.role.schemas = req.body.schemas; req.role.save(function (err) { if (err) return res.status(500).send(err.message); res.status(200).send(req.role); }); }); router.delete((route.endsWith('/') ? route.slice(0, -1) : route) + "/:role", function (req, res) { req.role.remove(function (err) { if (err) return res.status(500).send(err.message); res.send(req.role); }); }); return router; }; RestApiPath.prototype.setEndPoints = function (models, Permission, Role) { var _this = this; var permission = new PermissionClass_1.default(this.model, Permission, Role, this.options); var table = this.model.modelName; this.router.use(this.route, function (req, res, next) { if (req.method === 'GET' || req.method === 'DELETE') return next(); if (req.body) return next(); var data = ''; req.on('data', function (chunk) { data += chunk.toString(); }); req.on('end', function () { try { req.body = JSON.parse(data); next(); } catch (err) { res.status(400).send('Body is not a valid json.'); } }); }); this.router.use(this.route, function (req, res, next) { if (!permission.checkUser(req.user)) return res.status(500).send("<h1>Request needs mongodb user</h1>\n <p>Fix it adding a middleware, useful for your project.</p>\n <p>Example:</p>\n <p><code>app.use(\"/\", (req, res, next)=>{<br/>\n &emsp;User.findOne({username: req.user}, (err, user)=>{<br/>\n &emsp;&emsp;if(err) return next(err)<br/>\n &emsp;&emsp;req.user = user<br/>\n &emsp;&emsp;next()<br/>\n &emsp;})<br/>\n }</code>)</p>"); next(); }); this.router.use(this.route + "/:id", function (req, res, next) { _this.getItem(req.params.id, function (err, item) { if (err) return res.status(500).send(err.message); if (!item) return res.sendStatus(404); req.doc = item; next(); }); }); this.router.get("" + this.route, function (req, res) { permission.getReadQuery(req, function (err, query) { if (err) return res.status(500).send(err.message); var _a = req.query, $page = _a.$page, $rowsPerPage = _a.$rowsPerPage, $sort = _a.$sort, $sortBy = _a.$sortBy, others = __rest(_a, ["$page", "$rowsPerPage", "$sort", "$sortBy"]); if (Object.keys(others).filter(function (key) { return !_this.fullPathTypes.hasOwnProperty(key) && key !== '$any'; }).length > 0) { return res.status(400).send('Path ' + Object.keys(others).find(function (key) { return !_this.fullPathTypes.hasOwnProperty(key); }) + ' not in schema'); } query_1.default(_this.isMongo4, models, _this.model, _this, others, query, function (err, cursor) { if (err) return res.status(500).send(err.message); cursor.count(function (err, count) { var _a; if (err) return res.status(500).send(err.message); var page = $page ? $page : 1; var rowsPerPage = $rowsPerPage ? parseInt($rowsPerPage) : _this.MAX_RESULTS; var sort = null; if ($sortBy) { if (Array.isArray($sortBy)) { sort = $sortBy.map(function (field, key) { return ({ field: field, direction: $sort && Array.isArray($sort) ? $sort[key] ? $sort[key] : 1 : $sort ? $sort : 1 }); }).reduce(function (el, next) { el[next.field] = next.direction; return el; }, {}); } else { sort = (_a = {}, _a[$sortBy] = $sort ? Array.isArray($sort) ? $sort.pop() : $sort : 1, _a); } cursor = cursor.sort(sort); } cursor .skip((parseInt(page) - 1) * rowsPerPage) .limit(rowsPerPage) .find(function (err, results) { if (err) return res.status(500).send(err.message); return res.send({ total_pages: Math.ceil(count / rowsPerPage), page: page, count: count, sort: sort, results: results, }); }); }); }); }); }); this.router.get(this.route + "/:id", function (req, res) { permission.hasReadPermission(req, req.doc, function (err, hasPermission, reason) { if (err) res.status(500).send(err.message); if (!hasPermission) return res.status(403).send(); res.send(req.doc); }); }); this.router.post("" + this.route, function (req, res) { var item = new _this.model(req.body); utils.replaceObjectIds(_this.paths, item); permission.hasAddPermission(req, function (err, hasPermission, message) { if (err) return res.status(500).send(err.message); if (hasPermission) { item.save(function (err, result) { if (err) return res.status(500).send(err.message); _this._addPermission(req, Permission, result._id, function (err) { if (err) return res.status(500).send('Error setting owner permission: ' + err.message); res.status(201).send(result); _this.emitter.emit('add', result); }); }); } else { res.status(403).send(message ? message : 'Unauthorized'); } }); }); this.router.put(this.route + "/:id", function (req, res) { var oldItem = req.doc.toObject(); permission.hasUpdatePermission(req, req.doc, function (err, hasPermission, message) { if (err) return res.status(500).send(err.message); if (hasPermission) { _this.model.schema.eachPath(function (path) { if (['_id', '__v'].indexOf(path) < 0) { req.doc[path.split('.').shift()] = undefined; } }); Object.keys(req.body).forEach(function (key) { return req.doc[key] = req.body[key]; }); utils.replaceObjectIds(_this.paths, req.doc); req.doc.save(function (err, result) { if (err) return res.status(500).send(err.message); res.send(result); _this.emitter.emit('update', { old: oldItem, new: result }); }); } else { res.status(403).send(message ? message : 'Unauthorized'); } }); }); this.router.patch(this.route + "/:id", function (req, res) { var oldItem = req.doc.toObject(); permission.hasUpdatePermission(req, req.doc, function (err, hasPermission, message) { if (err) return res.status(500).send(err.message); if (hasPermission) { Object.keys(req.body).forEach(function (key) { return req.doc[key] = req.body[key]; }); utils.replaceObjectIds(_this.paths, req.doc); req.doc.save(function (err, result) { if (err) return res.status(500).send(err.message); res.send(result); _this.emitter.emit('update', { old: oldItem, new: result }); }); } else { res.status(403).send(message ? message : 'Unauthorized'); } }); }); this.router.delete(this.route + "/:id", function (req, res) { permission.hasDeletePermission(req, req.doc, function (err, hasPermission, message) { if (err) return res.status(500).send(err.message); if (hasPermission) { req.doc.remove(function (err, item) { if (err) return res.status(400).send(err.message); _this._deletePermission(Permission, req.doc._id, function (err) { if (err) return res.status(500).send('Error deleting permissions: ' + err.message); res.send(item); _this.emitter.emit('delete', item); }); }); } else { res.status(403).send(message ? message : 'Unauthorized'); } }); }); this.router.use(this.route + "/:id/permission", function (req, res, next) { permission.hasAdminPermission(req, req.doc, function (err, hasPermission) { if (err) return res.status(500).send(err.message); if (!hasPermission) return res.status(403).send(); next(); }); }); this.router.get(this.route + "/:id/permission", function (req, res) { Permission.find({ table: table, object: req.doc._id, user: { $exists: true } }, function (err, docs) { if (err) return res.status(500).send(err.message); res.send(docs); }); }); this.router.use(this.route + "/:id/permission/user/:user", function (req, res, next) { Permission.findOne({ table: table, object: req.doc._id, user: mongoose_1.Types.ObjectId(req.params.user) }, function (err, doc) { if (err) return res.status(500).send(err.message); req.doc_perm = doc; next(); }); }); this.router.get(this.route + "/:id/permission/user/:user", function (req, res) { res.send(req.doc_perm); }); this.router.post(this.route + "/:id/permission/user/:user", function (req, res) { if (req.doc_perm) return res.status(402).send('This user has a permission on this object.'); var permission = new Permission(__assign({}, req.body, { table: table })); permission.object = req.doc._id; permission.save(function (err) { if (err) return res.status(500).send(err.message); res.status(201).send(permission); }); }); this.router.put(this.route + "/:id/permission/user/:user", function (req, res) { if (!req.doc_perm) return res.status(404).send("Object " + req.params.id + " not found on " + table + " permissions table."); if (req.body.read) req.doc_perm.permission = model_1.PermissionEnum.READ; if (req.body.write) req.doc_perm.permission = model_1.PermissionEnum.DELETE; if (req.body.admin) req.doc_perm.permission = model_1.PermissionEnum.ADMIN; req.doc_perm.save(function (err) { if (err) return res.status(500).send(err.message); res.status(200).send(req.doc_perm); }); }); this.router.patch(this.route + "/:id/permission/user/:user", function (req, res) { if (!req.doc_perm) return res.status(404).send("Object " + req.doc._id + " not found on " + table + " permissions table for you."); if (req.body.read) req.doc_perm.permission = model_1.PermissionEnum.READ; if (req.body.write) req.doc_perm.permission = model_1.PermissionEnum.DELETE; if (req.body.admin) req.doc_perm.permission = model_1.PermissionEnum.ADMIN; req.doc_perm.save(function (err) { if (err) return res.status(500).send(err.message); res.status(200).send(req.doc_perm); }); }); this.router.delete(this.route + "/:id/permission/user/:user", function (req, res) { if (!req.doc_perm) return res.status(404).send("Object " + req.doc._id + " not found on " + table + " permissions table for you."); req.doc_perm.remove(function (err) { if (err) return res.status(500).send(err.message); res.send(req.doc_perm); }); }); return permission; }; return RestApiPath; }()); exports.default = RestApiPath; //# sourceMappingURL=RestApiPath.js.map