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