@modular-rest/server 1.11.13 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.nvmrc +1 -0
- package/.prettierrc.json +9 -0
- package/.releaserc.json +24 -0
- package/README.md +79 -94
- package/dist/application.d.ts +29 -0
- package/dist/application.js +217 -0
- package/dist/class/cms_trigger.d.ts +61 -0
- package/dist/class/cms_trigger.js +47 -0
- package/dist/class/collection_definition.d.ts +112 -0
- package/dist/class/collection_definition.js +87 -0
- package/dist/class/combinator.d.ts +43 -0
- package/dist/class/combinator.js +174 -0
- package/dist/class/database_trigger.d.ts +84 -0
- package/dist/class/database_trigger.js +64 -0
- package/dist/class/db_schemas.d.ts +25 -0
- package/dist/class/db_schemas.js +28 -0
- package/dist/class/directory.d.ts +20 -0
- package/dist/class/directory.js +87 -0
- package/dist/class/paginator.d.ts +31 -0
- package/dist/class/paginator.js +43 -0
- package/dist/class/reply.d.ts +29 -0
- package/dist/class/reply.js +44 -0
- package/dist/class/security.d.ts +186 -0
- package/dist/class/security.js +178 -0
- package/dist/class/trigger_operator.d.ts +92 -0
- package/dist/class/trigger_operator.js +99 -0
- package/dist/class/user.d.ts +81 -0
- package/dist/class/user.js +151 -0
- package/dist/class/validator.d.ts +19 -0
- package/dist/class/validator.js +101 -0
- package/dist/config.d.ts +112 -0
- package/dist/config.js +26 -0
- package/dist/defult-permissions.d.ts +2 -0
- package/dist/defult-permissions.js +31 -0
- package/dist/events.d.ts +23 -0
- package/dist/events.js +47 -0
- package/dist/helper/data_insertion.d.ts +38 -0
- package/dist/helper/data_insertion.js +110 -0
- package/dist/helper/presetup_services.d.ts +60 -0
- package/dist/helper/presetup_services.js +108 -0
- package/dist/index.d.ts +118 -0
- package/dist/index.js +79 -0
- package/dist/middlewares.d.ts +53 -0
- package/dist/middlewares.js +106 -0
- package/dist/play-test.d.ts +1 -0
- package/dist/play-test.js +9 -0
- package/dist/services/data_provider/router.d.ts +4 -0
- package/dist/services/data_provider/router.js +187 -0
- package/dist/services/data_provider/service.d.ts +131 -0
- package/dist/services/data_provider/service.js +252 -0
- package/dist/services/data_provider/typeCasters.d.ts +9 -0
- package/dist/services/data_provider/typeCasters.js +18 -0
- package/dist/services/file/db.d.ts +1 -0
- package/dist/services/file/db.js +31 -0
- package/dist/services/file/router.d.ts +4 -0
- package/dist/services/file/router.js +115 -0
- package/dist/services/file/service.d.ts +204 -0
- package/dist/services/file/service.js +341 -0
- package/dist/services/functions/router.d.ts +4 -0
- package/dist/services/functions/router.js +67 -0
- package/dist/services/functions/service.d.ts +132 -0
- package/dist/services/functions/service.js +159 -0
- package/dist/services/jwt/router.d.ts +4 -0
- package/dist/services/jwt/router.js +99 -0
- package/dist/services/jwt/service.d.ts +97 -0
- package/dist/services/jwt/service.js +135 -0
- package/dist/services/user_manager/db.d.ts +1 -0
- package/dist/services/user_manager/db.js +75 -0
- package/dist/services/user_manager/permissionManager.d.ts +19 -0
- package/dist/services/user_manager/permissionManager.js +42 -0
- package/dist/services/user_manager/router.d.ts +4 -0
- package/dist/services/user_manager/router.js +195 -0
- package/dist/services/user_manager/service.d.ts +317 -0
- package/dist/services/user_manager/service.js +628 -0
- package/docs/.keep +0 -0
- package/docs/system-access-type.md +26 -0
- package/package.json +58 -45
- package/src/application.ts +206 -0
- package/src/class/cms_trigger.ts +68 -0
- package/src/class/collection_definition.ts +134 -0
- package/src/class/combinator.ts +176 -0
- package/src/class/database_trigger.ts +99 -0
- package/src/class/db_schemas.ts +44 -0
- package/src/class/{directory.js → directory.ts} +40 -18
- package/src/class/paginator.ts +51 -0
- package/src/class/reply.ts +59 -0
- package/src/class/security.ts +250 -0
- package/src/class/trigger_operator.ts +142 -0
- package/src/class/user.ts +199 -0
- package/src/class/validator.ts +123 -0
- package/src/config.ts +121 -0
- package/src/defult-permissions.ts +31 -0
- package/src/events.ts +59 -0
- package/src/helper/data_insertion.ts +94 -0
- package/src/helper/presetup_services.ts +96 -0
- package/src/index.ts +146 -0
- package/src/middlewares.ts +75 -0
- package/src/play-test.ts +8 -0
- package/src/services/data_provider/router.ts +191 -0
- package/src/services/data_provider/service.ts +305 -0
- package/src/services/data_provider/typeCasters.ts +15 -0
- package/src/services/file/db.ts +29 -0
- package/src/services/file/router.ts +88 -0
- package/src/services/file/service.ts +387 -0
- package/src/services/functions/router.ts +34 -0
- package/src/services/functions/service.ts +203 -0
- package/src/services/jwt/router.ts +73 -0
- package/src/services/jwt/service.ts +139 -0
- package/src/services/user_manager/db.ts +87 -0
- package/src/services/user_manager/permissionManager.ts +49 -0
- package/src/services/user_manager/router.ts +193 -0
- package/src/services/user_manager/service.ts +698 -0
- package/tsconfig.json +16 -9
- package/typedoc.mjs +41 -0
- package/LICENSE +0 -21
- package/package-lock.json +0 -1373
- package/src/application.js +0 -239
- package/src/class/cms_trigger.js +0 -20
- package/src/class/collection_definition.js +0 -33
- package/src/class/combinator.js +0 -133
- package/src/class/database_trigger.js +0 -20
- package/src/class/db_schemas.js +0 -18
- package/src/class/paginator.js +0 -31
- package/src/class/reply.js +0 -37
- package/src/class/security.js +0 -141
- package/src/class/trigger_operator.js +0 -39
- package/src/class/user.js +0 -112
- package/src/class/validator.js +0 -91
- package/src/config.js +0 -67
- package/src/events.js +0 -15
- package/src/helper/data_insertion.js +0 -64
- package/src/helper/presetup_services.js +0 -31
- package/src/index.js +0 -66
- package/src/middlewares.js +0 -44
- package/src/services/data_provider/router.js +0 -552
- package/src/services/data_provider/service.js +0 -262
- package/src/services/data_provider/typeCasters.js +0 -10
- package/src/services/file/db.js +0 -29
- package/src/services/file/router.js +0 -92
- package/src/services/file/service.js +0 -231
- package/src/services/functions/router.js +0 -37
- package/src/services/functions/service.js +0 -74
- package/src/services/jwt/router.js +0 -82
- package/src/services/jwt/service.js +0 -37
- package/src/services/user_manager/db.js +0 -83
- package/src/services/user_manager/permissionManager.js +0 -43
- package/src/services/user_manager/router.js +0 -176
- package/src/services/user_manager/service.js +0 -377
- package/types/application.d.ts +0 -97
- package/types/class/cms_trigger.d.ts +0 -24
- package/types/class/collection_definition.d.ts +0 -36
- package/types/class/combinator.d.ts +0 -30
- package/types/class/database_trigger.d.ts +0 -28
- package/types/class/db_schemas.d.ts +0 -2
- package/types/class/directory.d.ts +0 -2
- package/types/class/paginator.d.ts +0 -8
- package/types/class/reply.d.ts +0 -8
- package/types/class/security.d.ts +0 -109
- package/types/class/trigger_operator.d.ts +0 -19
- package/types/class/user.d.ts +0 -24
- package/types/class/validator.d.ts +0 -9
- package/types/config.d.ts +0 -101
- package/types/events.d.ts +0 -7
- package/types/helper/data_insertion.d.ts +0 -4
- package/types/helper/presetup_services.d.ts +0 -5
- package/types/index.d.ts +0 -72
- package/types/middlewares.d.ts +0 -10
- package/types/services/data_provider/router.d.ts +0 -3
- package/types/services/data_provider/service.d.ts +0 -40
- package/types/services/data_provider/typeCasters.d.ts +0 -3
- package/types/services/file/db.d.ts +0 -3
- package/types/services/file/router.d.ts +0 -3
- package/types/services/file/service.d.ts +0 -81
- package/types/services/functions/router.d.ts +0 -3
- package/types/services/functions/service.d.ts +0 -23
- package/types/services/jwt/router.d.ts +0 -3
- package/types/services/jwt/service.d.ts +0 -10
- package/types/services/user_manager/db.d.ts +0 -3
- package/types/services/user_manager/permissionManager.d.ts +0 -3
- package/types/services/user_manager/router.d.ts +0 -3
- package/types/services/user_manager/service.d.ts +0 -131
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.auth = auth;
|
|
37
|
+
const validator_1 = require("./class/validator");
|
|
38
|
+
const userManager = __importStar(require("./services/user_manager/service"));
|
|
39
|
+
/**
|
|
40
|
+
* Authentication middleware that secures routes by validating user tokens and managing access control.
|
|
41
|
+
*
|
|
42
|
+
* This middleware performs several key functions:
|
|
43
|
+
* 1. Validates that the incoming request contains an authorization token in the header
|
|
44
|
+
* 2. Verifies the token is valid by checking against the user management service
|
|
45
|
+
* 3. Retrieves the associated user object if the token is valid
|
|
46
|
+
* 4. Attaches the authenticated {@link User} object on ctx.state.user for use in subsequent middleware/routes
|
|
47
|
+
* 5. Throws appropriate HTTP errors (401, 412) if authentication fails
|
|
48
|
+
*
|
|
49
|
+
* The middleware integrates with the permission system to enable role-based access control.
|
|
50
|
+
* The attached user object provides methods like hasPermission() to check specific permissions.
|
|
51
|
+
*
|
|
52
|
+
* Common usage patterns:
|
|
53
|
+
* - Protecting sensitive API endpoints
|
|
54
|
+
* - Implementing role-based access control
|
|
55
|
+
* - Getting the current authenticated user
|
|
56
|
+
* - Validating user permissions before allowing actions
|
|
57
|
+
*
|
|
58
|
+
* @throws {Error} 401 - If no authorization header is present
|
|
59
|
+
* @throws {Error} 412 - If token validation fails
|
|
60
|
+
* @param ctx - Koa Context object containing request/response data
|
|
61
|
+
* @param next - Function to invoke next middleware
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```typescript
|
|
65
|
+
* // Inside the router.ts file
|
|
66
|
+
* import { auth } from '@modular-rest/server';
|
|
67
|
+
* import { Router } from 'koa-router';
|
|
68
|
+
*
|
|
69
|
+
* const name = 'flowers';
|
|
70
|
+
*
|
|
71
|
+
* const flowerRouter = new Router();
|
|
72
|
+
*
|
|
73
|
+
* flowerRouter.get('/list', auth, (ctx) => {
|
|
74
|
+
* // Get the authenticated user
|
|
75
|
+
* const user = ctx.state.user;
|
|
76
|
+
*
|
|
77
|
+
* // Then you can check the user's role and permission
|
|
78
|
+
* if(user.hasPermission('get_flower')) {
|
|
79
|
+
* ctx.body = 'This is a list of flowers: Rose, Lily, Tulip';
|
|
80
|
+
* } else {
|
|
81
|
+
* ctx.status = 403;
|
|
82
|
+
* ctx.body = 'You are not authorized to access this resource';
|
|
83
|
+
* }
|
|
84
|
+
* });
|
|
85
|
+
*
|
|
86
|
+
* module.exports.name = name;
|
|
87
|
+
* module.exports.main = flowerRouter;
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
|
90
|
+
async function auth(ctx, next) {
|
|
91
|
+
const headers = ctx.header;
|
|
92
|
+
const headersValidated = (0, validator_1.validator)(headers, 'authorization');
|
|
93
|
+
if (!headersValidated.isValid)
|
|
94
|
+
ctx.throw(401, 'authentication is required');
|
|
95
|
+
const token = headers.authorization;
|
|
96
|
+
await userManager.main
|
|
97
|
+
.getUserByToken(token)
|
|
98
|
+
.then(async (user) => {
|
|
99
|
+
ctx.state.user = user;
|
|
100
|
+
await next();
|
|
101
|
+
})
|
|
102
|
+
.catch(err => {
|
|
103
|
+
console.log(err);
|
|
104
|
+
ctx.throw(err.status || 412, err.message);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.main = exports.name = void 0;
|
|
40
|
+
const security_1 = require("../../class/security");
|
|
41
|
+
const koa_router_1 = __importDefault(require("koa-router"));
|
|
42
|
+
const validator_1 = require("../../class/validator");
|
|
43
|
+
const reply_1 = require("../../class/reply");
|
|
44
|
+
const nested_property_1 = __importDefault(require("nested-property"));
|
|
45
|
+
const service = __importStar(require("./service"));
|
|
46
|
+
const middleware = __importStar(require("../../middlewares"));
|
|
47
|
+
const name = 'data-provider';
|
|
48
|
+
exports.name = name;
|
|
49
|
+
const dataProvider = new koa_router_1.default();
|
|
50
|
+
exports.main = dataProvider;
|
|
51
|
+
dataProvider.use('/', middleware.auth, async (ctx, next) => {
|
|
52
|
+
const body = ctx.request.body;
|
|
53
|
+
const bodyValidated = (0, validator_1.validateObject)(body, 'database collection');
|
|
54
|
+
// fields validation
|
|
55
|
+
if (!bodyValidated.isValid) {
|
|
56
|
+
ctx.throw(412, JSON.stringify((0, reply_1.create)('e', { error: bodyValidated.requires })));
|
|
57
|
+
}
|
|
58
|
+
// type caster
|
|
59
|
+
if (body.types && body.hasOwnProperty(body.bodyKey || '.')) {
|
|
60
|
+
const bodyKey = body.bodyKey;
|
|
61
|
+
for (const key in body.types) {
|
|
62
|
+
if (body.types.hasOwnProperty(key) && typeof body.types[key] == 'object') {
|
|
63
|
+
const typeDetail = body.types[key];
|
|
64
|
+
try {
|
|
65
|
+
const value = nested_property_1.default.get(body[bodyKey], typeDetail.path);
|
|
66
|
+
const newProperty = service.TypeCasters[typeDetail.type](value);
|
|
67
|
+
nested_property_1.default.set(body[bodyKey], typeDetail.path, newProperty);
|
|
68
|
+
console.log('newProperty', newProperty, JSON.stringify(body[bodyKey]));
|
|
69
|
+
}
|
|
70
|
+
catch (e) {
|
|
71
|
+
console.log('type caster error', e);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
await next();
|
|
77
|
+
});
|
|
78
|
+
dataProvider.post('/find', async (ctx) => {
|
|
79
|
+
const body = ctx.request.body;
|
|
80
|
+
const bodyValidate = (0, validator_1.validateObject)(body, 'database collection query');
|
|
81
|
+
// fields validation
|
|
82
|
+
if (!bodyValidate.isValid) {
|
|
83
|
+
ctx.throw(412, JSON.stringify((0, reply_1.create)('e', { error: bodyValidate.requires })));
|
|
84
|
+
}
|
|
85
|
+
// access validation
|
|
86
|
+
const hasAccess = service.checkAccess(body.database, body.collection, security_1.AccessTypes.read, body.query, ctx.state.user);
|
|
87
|
+
if (!hasAccess) {
|
|
88
|
+
console.log(body);
|
|
89
|
+
console.log(ctx.state.user.permission);
|
|
90
|
+
ctx.throw(403, 'access denied');
|
|
91
|
+
}
|
|
92
|
+
// collection validation
|
|
93
|
+
const collection = service.getCollection(body.database, body.collection);
|
|
94
|
+
if (collection == null) {
|
|
95
|
+
ctx.throw(412, JSON.stringify((0, reply_1.create)('e', { error: 'wrong database or collection' })));
|
|
96
|
+
}
|
|
97
|
+
// operate on db
|
|
98
|
+
let queryRequest = collection.find(body.query, body.projection);
|
|
99
|
+
if (body.options) {
|
|
100
|
+
queryRequest = service.performAdditionalOptionsToQueryObject(queryRequest, body.options);
|
|
101
|
+
}
|
|
102
|
+
if (body.populates) {
|
|
103
|
+
try {
|
|
104
|
+
queryRequest = service.performPopulateToQueryObject(queryRequest, body.populates);
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
ctx.status = 412;
|
|
108
|
+
ctx.body = err;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
await queryRequest
|
|
112
|
+
.exec()
|
|
113
|
+
.then(async (docs) => {
|
|
114
|
+
ctx.body = { data: docs };
|
|
115
|
+
})
|
|
116
|
+
.catch(err => {
|
|
117
|
+
ctx.status = err.status || 500;
|
|
118
|
+
ctx.body = err.message;
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
dataProvider.post('/find-one', async (ctx) => {
|
|
122
|
+
const body = ctx.request.body;
|
|
123
|
+
const bodyValidate = (0, validator_1.validateObject)(body, 'database collection query');
|
|
124
|
+
// fields validation
|
|
125
|
+
if (!bodyValidate.isValid) {
|
|
126
|
+
ctx.throw(412, JSON.stringify((0, reply_1.create)('e', { error: bodyValidate.requires })));
|
|
127
|
+
}
|
|
128
|
+
// access validation
|
|
129
|
+
const hasAccess = service.checkAccess(body.database, body.collection, security_1.AccessTypes.read, body.query, ctx.state.user);
|
|
130
|
+
if (!hasAccess)
|
|
131
|
+
ctx.throw(403, 'access denied');
|
|
132
|
+
// collection validation
|
|
133
|
+
const collection = service.getCollection(body.database, body.collection);
|
|
134
|
+
if (collection == null) {
|
|
135
|
+
ctx.throw(412, JSON.stringify((0, reply_1.create)('e', { error: 'wrong database or collection' })));
|
|
136
|
+
}
|
|
137
|
+
// operate on db
|
|
138
|
+
let queryRequest = collection.findOne(body.query, body.projection, body.options);
|
|
139
|
+
if (body.options) {
|
|
140
|
+
queryRequest = service.performAdditionalOptionsToQueryObject(queryRequest, body.options);
|
|
141
|
+
}
|
|
142
|
+
if (body.populates) {
|
|
143
|
+
try {
|
|
144
|
+
queryRequest = service.performPopulateToQueryObject(queryRequest, body.populates);
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
ctx.status = 412;
|
|
148
|
+
ctx.body = err;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// operate on db
|
|
152
|
+
await queryRequest
|
|
153
|
+
.exec()
|
|
154
|
+
.then(async (doc) => {
|
|
155
|
+
ctx.body = { data: doc };
|
|
156
|
+
})
|
|
157
|
+
.catch(err => {
|
|
158
|
+
ctx.status = err.status || 500;
|
|
159
|
+
ctx.body = err.message;
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
dataProvider.post('/count', async (ctx) => {
|
|
163
|
+
const body = ctx.request.body;
|
|
164
|
+
const bodyValidate = (0, validator_1.validateObject)(body, 'database collection query');
|
|
165
|
+
// fields validation
|
|
166
|
+
if (!bodyValidate.isValid) {
|
|
167
|
+
ctx.throw(412, JSON.stringify((0, reply_1.create)('e', { error: bodyValidate.requires })));
|
|
168
|
+
}
|
|
169
|
+
// access validation
|
|
170
|
+
const hasAccess = service.checkAccess(body.database, body.collection, security_1.AccessTypes.read, body.query, ctx.state.user);
|
|
171
|
+
if (!hasAccess)
|
|
172
|
+
ctx.throw(403, 'access denied');
|
|
173
|
+
// collection validation
|
|
174
|
+
const collection = service.getCollection(body.database, body.collection);
|
|
175
|
+
if (collection == null) {
|
|
176
|
+
ctx.throw(412, JSON.stringify((0, reply_1.create)('e', { error: 'wrong database or collection' })));
|
|
177
|
+
}
|
|
178
|
+
await collection
|
|
179
|
+
.countDocuments(body.query)
|
|
180
|
+
.then(count => {
|
|
181
|
+
ctx.body = { data: count };
|
|
182
|
+
})
|
|
183
|
+
.catch(err => {
|
|
184
|
+
ctx.status = err.status || 500;
|
|
185
|
+
ctx.body = err.message;
|
|
186
|
+
});
|
|
187
|
+
});
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import mongoose, { Model, PopulateOptions, Query } from 'mongoose';
|
|
2
|
+
import TypeCasters from './typeCasters';
|
|
3
|
+
import { CollectionDefinition } from '../../class/collection_definition';
|
|
4
|
+
import { User } from '../../class/user';
|
|
5
|
+
/**
|
|
6
|
+
* Service name constant
|
|
7
|
+
* @constant {string}
|
|
8
|
+
*/
|
|
9
|
+
export declare const name = "dataProvider";
|
|
10
|
+
/**
|
|
11
|
+
* MongoDB connection options
|
|
12
|
+
* @interface MongoOption
|
|
13
|
+
* @property {string} [dbPrefix] - Prefix for database names
|
|
14
|
+
* @property {string} mongoBaseAddress - MongoDB connection URL
|
|
15
|
+
*/
|
|
16
|
+
export interface MongoOption {
|
|
17
|
+
dbPrefix?: string;
|
|
18
|
+
mongoBaseAddress: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Collection definition options
|
|
22
|
+
* @interface CollectionDefinitionOption
|
|
23
|
+
* @property {CollectionDefinition[]} list - List of collection definitions
|
|
24
|
+
* @property {MongoOption} mongoOption - MongoDB connection options
|
|
25
|
+
*/
|
|
26
|
+
interface CollectionDefinitionListOption {
|
|
27
|
+
list: CollectionDefinition[];
|
|
28
|
+
mongoOption: MongoOption;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Adds collection definitions and connects to their respective databases
|
|
32
|
+
* @function addCollectionDefinitionByList
|
|
33
|
+
* @param {CollectionDefinitionListOption} options - Collection definition options
|
|
34
|
+
* @returns {Promise<void>} A promise that resolves when all collections are set up
|
|
35
|
+
* @example
|
|
36
|
+
* ```typescript
|
|
37
|
+
* await addCollectionDefinitionByList({
|
|
38
|
+
* list: [
|
|
39
|
+
* new CollectionDefinition({
|
|
40
|
+
* database: 'myapp',
|
|
41
|
+
* collection: 'users',
|
|
42
|
+
* schema: userSchema,
|
|
43
|
+
* permissions: [new Permission({ type: 'user_access', read: true })]
|
|
44
|
+
* })
|
|
45
|
+
* ],
|
|
46
|
+
* mongoOption: {
|
|
47
|
+
* mongoBaseAddress: 'mongodb://localhost:27017',
|
|
48
|
+
* dbPrefix: 'myapp_'
|
|
49
|
+
* }
|
|
50
|
+
* });
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export declare function addCollectionDefinitionByList({ list, mongoOption, }: CollectionDefinitionListOption): Promise<void>;
|
|
54
|
+
/**
|
|
55
|
+
* Gets a Mongoose model for a specific collection
|
|
56
|
+
* @function getCollection
|
|
57
|
+
* @param {string} db - Database name
|
|
58
|
+
* @param {string} collection - Collection name
|
|
59
|
+
* @returns {Model<T>} Mongoose model for the collection
|
|
60
|
+
* @throws {Error} If the collection doesn't exist
|
|
61
|
+
* @example
|
|
62
|
+
* ```typescript
|
|
63
|
+
* const userModel = getCollection('myapp', 'users');
|
|
64
|
+
* const users = await userModel.find();
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
export declare function getCollection<T>(db: string, collection: string): Model<T>;
|
|
68
|
+
/**
|
|
69
|
+
* Checks if a user has access to perform an operation on a collection
|
|
70
|
+
* @function checkAccess
|
|
71
|
+
* @param {string} db - Database name
|
|
72
|
+
* @param {string} collection - Collection name
|
|
73
|
+
* @param {string} operationType - Type of operation (read/write)
|
|
74
|
+
* @param {Record<string, any>} queryOrDoc - Query or document being accessed
|
|
75
|
+
* @param {User} user - User performing the operation
|
|
76
|
+
* @returns {boolean} Whether the user has access
|
|
77
|
+
* @example
|
|
78
|
+
* ```typescript
|
|
79
|
+
* const hasAccess = checkAccess('myapp', 'users', 'read', {}, currentUser);
|
|
80
|
+
* if (hasAccess) {
|
|
81
|
+
* const users = await getCollection('myapp', 'users').find();
|
|
82
|
+
* }
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
export declare function checkAccess(db: string, collection: string, operationType: string, queryOrDoc: Record<string, any>, user: User): boolean;
|
|
86
|
+
/**
|
|
87
|
+
* Converts a string ID to a MongoDB ObjectId
|
|
88
|
+
* @function getAsID
|
|
89
|
+
* @param {string} strId - String ID to convert
|
|
90
|
+
* @returns {mongoose.Types.ObjectId | undefined} MongoDB ObjectId or undefined if invalid
|
|
91
|
+
* @example
|
|
92
|
+
* ```typescript
|
|
93
|
+
* const id = getAsID('507f1f77bcf86cd799439011');
|
|
94
|
+
* if (id) {
|
|
95
|
+
* const doc = await collection.findById(id);
|
|
96
|
+
* }
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
export declare function getAsID(strId: string): mongoose.Types.ObjectId | undefined;
|
|
100
|
+
/**
|
|
101
|
+
* Applies populate options to a Mongoose query
|
|
102
|
+
* @function performPopulateToQueryObject
|
|
103
|
+
* @param {Query<T, any>} queryObj - Mongoose query object
|
|
104
|
+
* @param {PopulateOptions[]} [popArr=[]] - Array of populate options
|
|
105
|
+
* @returns {Query<T, any>} Query with populate options applied
|
|
106
|
+
* @example
|
|
107
|
+
* ```typescript
|
|
108
|
+
* const query = collection.find();
|
|
109
|
+
* const populatedQuery = performPopulateToQueryObject(query, [
|
|
110
|
+
* { path: 'author', select: 'name email' }
|
|
111
|
+
* ]);
|
|
112
|
+
* ```
|
|
113
|
+
*/
|
|
114
|
+
export declare function performPopulateToQueryObject<T = any>(queryObj: Query<T, any>, popArr?: PopulateOptions[]): Query<T, any>;
|
|
115
|
+
/**
|
|
116
|
+
* Applies additional options to a Mongoose query
|
|
117
|
+
* @function performAdditionalOptionsToQueryObject
|
|
118
|
+
* @param {Query<T, any>} queryObj - Mongoose query object
|
|
119
|
+
* @param {Record<string, any>} options - Additional query options
|
|
120
|
+
* @returns {Query<T, any>} Query with additional options applied
|
|
121
|
+
* @example
|
|
122
|
+
* ```typescript
|
|
123
|
+
* const query = collection.find();
|
|
124
|
+
* const queryWithOptions = performAdditionalOptionsToQueryObject(query, {
|
|
125
|
+
* sort: { createdAt: -1 },
|
|
126
|
+
* limit: 10
|
|
127
|
+
* });
|
|
128
|
+
* ```
|
|
129
|
+
*/
|
|
130
|
+
export declare function performAdditionalOptionsToQueryObject<T = any>(queryObj: Query<T, any>, options: Record<string, any>): Query<T, any>;
|
|
131
|
+
export { TypeCasters };
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.TypeCasters = exports.name = void 0;
|
|
7
|
+
exports.addCollectionDefinitionByList = addCollectionDefinitionByList;
|
|
8
|
+
exports.getCollection = getCollection;
|
|
9
|
+
exports.checkAccess = checkAccess;
|
|
10
|
+
exports.getAsID = getAsID;
|
|
11
|
+
exports.performPopulateToQueryObject = performPopulateToQueryObject;
|
|
12
|
+
exports.performAdditionalOptionsToQueryObject = performAdditionalOptionsToQueryObject;
|
|
13
|
+
const mongoose_1 = __importDefault(require("mongoose"));
|
|
14
|
+
const security_1 = require("../../class/security");
|
|
15
|
+
const trigger_operator_1 = __importDefault(require("../../class/trigger_operator"));
|
|
16
|
+
const typeCasters_1 = __importDefault(require("./typeCasters"));
|
|
17
|
+
exports.TypeCasters = typeCasters_1.default;
|
|
18
|
+
/**
|
|
19
|
+
* Service name constant
|
|
20
|
+
* @constant {string}
|
|
21
|
+
*/
|
|
22
|
+
exports.name = 'dataProvider';
|
|
23
|
+
// Set mongoose options
|
|
24
|
+
mongoose_1.default.set('useCreateIndex', true);
|
|
25
|
+
// Database connections and collections storage
|
|
26
|
+
const connections = {};
|
|
27
|
+
const collections = {};
|
|
28
|
+
const permissionDefinitions = {};
|
|
29
|
+
/**
|
|
30
|
+
* Connects to a database and sets up collections based on collection definitions
|
|
31
|
+
* @function connectToDatabaseByCollectionDefinitionList
|
|
32
|
+
* @param {string} dbName - Name of the database to connect to
|
|
33
|
+
* @param {CollectionDefinition[]} [collectionDefinitionList=[]] - List of collection definitions
|
|
34
|
+
* @param {MongoOption} mongoOption - MongoDB connection options
|
|
35
|
+
* @returns {Promise<void>} A promise that resolves when the connection is established
|
|
36
|
+
* @throws {Error} If triggers are not properly configured
|
|
37
|
+
* @private
|
|
38
|
+
*/
|
|
39
|
+
function connectToDatabaseByCollectionDefinitionList(dbName, collectionDefinitionList = [], mongoOption) {
|
|
40
|
+
return new Promise((done, reject) => {
|
|
41
|
+
// Create db connection
|
|
42
|
+
const fullDbName = (mongoOption.dbPrefix || '') + dbName;
|
|
43
|
+
const connectionString = mongoOption.mongoBaseAddress;
|
|
44
|
+
console.info(`- Connecting to database: ${fullDbName}`);
|
|
45
|
+
const connection = mongoose_1.default.createConnection(connectionString, {
|
|
46
|
+
useUnifiedTopology: true,
|
|
47
|
+
useNewUrlParser: true,
|
|
48
|
+
dbName: fullDbName,
|
|
49
|
+
});
|
|
50
|
+
// Store connection
|
|
51
|
+
connections[dbName] = connection;
|
|
52
|
+
// add db models from schemas
|
|
53
|
+
collectionDefinitionList.forEach(collectionDefinition => {
|
|
54
|
+
const collection = collectionDefinition.collection;
|
|
55
|
+
const schema = collectionDefinition.schema;
|
|
56
|
+
if (collections[dbName] == undefined)
|
|
57
|
+
collections[dbName] = {};
|
|
58
|
+
if (permissionDefinitions[dbName] == undefined)
|
|
59
|
+
permissionDefinitions[dbName] = {};
|
|
60
|
+
// create model from schema
|
|
61
|
+
// and store in on global collection object
|
|
62
|
+
const model = connection.model(collection, schema);
|
|
63
|
+
collections[dbName][collection] = model;
|
|
64
|
+
// define Access Definition from component permissions
|
|
65
|
+
// and store it on global access definition object
|
|
66
|
+
permissionDefinitions[dbName][collection] = new security_1.AccessDefinition({
|
|
67
|
+
database: dbName,
|
|
68
|
+
collection: collection,
|
|
69
|
+
permissionList: collectionDefinition.permissions,
|
|
70
|
+
});
|
|
71
|
+
// add trigger
|
|
72
|
+
if (collectionDefinition.triggers != undefined) {
|
|
73
|
+
if (!Array.isArray(collectionDefinition.triggers)) {
|
|
74
|
+
throw new Error('Triggers must be an array');
|
|
75
|
+
}
|
|
76
|
+
collectionDefinition.triggers.forEach(trigger => {
|
|
77
|
+
trigger_operator_1.default.addTrigger({
|
|
78
|
+
...trigger,
|
|
79
|
+
database: collectionDefinition.database,
|
|
80
|
+
collection: collectionDefinition.collection,
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
connection.on('connected', () => {
|
|
86
|
+
console.info(`- ${fullDbName} database has been connected`);
|
|
87
|
+
done();
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Adds collection definitions and connects to their respective databases
|
|
93
|
+
* @function addCollectionDefinitionByList
|
|
94
|
+
* @param {CollectionDefinitionListOption} options - Collection definition options
|
|
95
|
+
* @returns {Promise<void>} A promise that resolves when all collections are set up
|
|
96
|
+
* @example
|
|
97
|
+
* ```typescript
|
|
98
|
+
* await addCollectionDefinitionByList({
|
|
99
|
+
* list: [
|
|
100
|
+
* new CollectionDefinition({
|
|
101
|
+
* database: 'myapp',
|
|
102
|
+
* collection: 'users',
|
|
103
|
+
* schema: userSchema,
|
|
104
|
+
* permissions: [new Permission({ type: 'user_access', read: true })]
|
|
105
|
+
* })
|
|
106
|
+
* ],
|
|
107
|
+
* mongoOption: {
|
|
108
|
+
* mongoBaseAddress: 'mongodb://localhost:27017',
|
|
109
|
+
* dbPrefix: 'myapp_'
|
|
110
|
+
* }
|
|
111
|
+
* });
|
|
112
|
+
* ```
|
|
113
|
+
*/
|
|
114
|
+
async function addCollectionDefinitionByList({ list, mongoOption, }) {
|
|
115
|
+
// Group collection definitions by database
|
|
116
|
+
const dbGroups = {};
|
|
117
|
+
list.forEach(collectionDefinition => {
|
|
118
|
+
if (!dbGroups[collectionDefinition.database]) {
|
|
119
|
+
dbGroups[collectionDefinition.database] = [];
|
|
120
|
+
}
|
|
121
|
+
dbGroups[collectionDefinition.database].push(collectionDefinition);
|
|
122
|
+
});
|
|
123
|
+
// Connect to each database
|
|
124
|
+
const connectionPromises = Object.entries(dbGroups).map(([dbName, collectionDefinitionList]) => connectToDatabaseByCollectionDefinitionList(dbName, collectionDefinitionList, mongoOption));
|
|
125
|
+
await Promise.all(connectionPromises);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Gets a Mongoose model for a specific collection
|
|
129
|
+
* @function getCollection
|
|
130
|
+
* @param {string} db - Database name
|
|
131
|
+
* @param {string} collection - Collection name
|
|
132
|
+
* @returns {Model<T>} Mongoose model for the collection
|
|
133
|
+
* @throws {Error} If the collection doesn't exist
|
|
134
|
+
* @example
|
|
135
|
+
* ```typescript
|
|
136
|
+
* const userModel = getCollection('myapp', 'users');
|
|
137
|
+
* const users = await userModel.find();
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
140
|
+
function getCollection(db, collection) {
|
|
141
|
+
if (!collections[db] || !collections[db][collection]) {
|
|
142
|
+
throw new Error(`Collection ${collection} not found in database ${db}`);
|
|
143
|
+
}
|
|
144
|
+
return collections[db][collection];
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Gets the permission list for a specific operation on a collection
|
|
148
|
+
* @function _getPermissionList
|
|
149
|
+
* @param {string} db - Database name
|
|
150
|
+
* @param {string} collection - Collection name
|
|
151
|
+
* @param {string} operationType - Type of operation (read/write)
|
|
152
|
+
* @returns {any[]} List of permissions
|
|
153
|
+
* @private
|
|
154
|
+
*/
|
|
155
|
+
function _getPermissionList(db, collection, operationType) {
|
|
156
|
+
if (!permissionDefinitions[db] || !permissionDefinitions[db][collection]) {
|
|
157
|
+
return [];
|
|
158
|
+
}
|
|
159
|
+
return permissionDefinitions[db][collection].permissionList;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Checks if a user has access to perform an operation on a collection
|
|
163
|
+
* @function checkAccess
|
|
164
|
+
* @param {string} db - Database name
|
|
165
|
+
* @param {string} collection - Collection name
|
|
166
|
+
* @param {string} operationType - Type of operation (read/write)
|
|
167
|
+
* @param {Record<string, any>} queryOrDoc - Query or document being accessed
|
|
168
|
+
* @param {User} user - User performing the operation
|
|
169
|
+
* @returns {boolean} Whether the user has access
|
|
170
|
+
* @example
|
|
171
|
+
* ```typescript
|
|
172
|
+
* const hasAccess = checkAccess('myapp', 'users', 'read', {}, currentUser);
|
|
173
|
+
* if (hasAccess) {
|
|
174
|
+
* const users = await getCollection('myapp', 'users').find();
|
|
175
|
+
* }
|
|
176
|
+
* ```
|
|
177
|
+
*/
|
|
178
|
+
function checkAccess(db, collection, operationType, queryOrDoc, user) {
|
|
179
|
+
const permissionList = _getPermissionList(db, collection, operationType);
|
|
180
|
+
return permissionList.some(permission => {
|
|
181
|
+
if (permission.type === 'god_access')
|
|
182
|
+
return true;
|
|
183
|
+
if (permission.type === 'anonymous_access' && user.type === 'anonymous')
|
|
184
|
+
return true;
|
|
185
|
+
if (permission.type === 'user_access' && user.type === 'user')
|
|
186
|
+
return true;
|
|
187
|
+
return false;
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Converts a string ID to a MongoDB ObjectId
|
|
192
|
+
* @function getAsID
|
|
193
|
+
* @param {string} strId - String ID to convert
|
|
194
|
+
* @returns {mongoose.Types.ObjectId | undefined} MongoDB ObjectId or undefined if invalid
|
|
195
|
+
* @example
|
|
196
|
+
* ```typescript
|
|
197
|
+
* const id = getAsID('507f1f77bcf86cd799439011');
|
|
198
|
+
* if (id) {
|
|
199
|
+
* const doc = await collection.findById(id);
|
|
200
|
+
* }
|
|
201
|
+
* ```
|
|
202
|
+
*/
|
|
203
|
+
function getAsID(strId) {
|
|
204
|
+
try {
|
|
205
|
+
return mongoose_1.default.Types.ObjectId(strId);
|
|
206
|
+
}
|
|
207
|
+
catch (e) {
|
|
208
|
+
return undefined;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Applies populate options to a Mongoose query
|
|
213
|
+
* @function performPopulateToQueryObject
|
|
214
|
+
* @param {Query<T, any>} queryObj - Mongoose query object
|
|
215
|
+
* @param {PopulateOptions[]} [popArr=[]] - Array of populate options
|
|
216
|
+
* @returns {Query<T, any>} Query with populate options applied
|
|
217
|
+
* @example
|
|
218
|
+
* ```typescript
|
|
219
|
+
* const query = collection.find();
|
|
220
|
+
* const populatedQuery = performPopulateToQueryObject(query, [
|
|
221
|
+
* { path: 'author', select: 'name email' }
|
|
222
|
+
* ]);
|
|
223
|
+
* ```
|
|
224
|
+
*/
|
|
225
|
+
function performPopulateToQueryObject(queryObj, popArr = []) {
|
|
226
|
+
popArr.forEach(pop => {
|
|
227
|
+
queryObj.populate(pop);
|
|
228
|
+
});
|
|
229
|
+
return queryObj;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Applies additional options to a Mongoose query
|
|
233
|
+
* @function performAdditionalOptionsToQueryObject
|
|
234
|
+
* @param {Query<T, any>} queryObj - Mongoose query object
|
|
235
|
+
* @param {Record<string, any>} options - Additional query options
|
|
236
|
+
* @returns {Query<T, any>} Query with additional options applied
|
|
237
|
+
* @example
|
|
238
|
+
* ```typescript
|
|
239
|
+
* const query = collection.find();
|
|
240
|
+
* const queryWithOptions = performAdditionalOptionsToQueryObject(query, {
|
|
241
|
+
* sort: { createdAt: -1 },
|
|
242
|
+
* limit: 10
|
|
243
|
+
* });
|
|
244
|
+
* ```
|
|
245
|
+
*/
|
|
246
|
+
function performAdditionalOptionsToQueryObject(queryObj, options) {
|
|
247
|
+
Object.entries(options).forEach(([key, value]) => {
|
|
248
|
+
// @ts-ignore
|
|
249
|
+
queryObj[key](value);
|
|
250
|
+
});
|
|
251
|
+
return queryObj;
|
|
252
|
+
}
|