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
JavaScript
"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  User.findOne({username: req.user}, (err, user)=>{<br/>\n   if(err) return next(err)<br/>\n   req.user = user<br/>\n   next()<br/>\n  })<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