@modular-rest/server 1.20.0 → 1.21.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/dist/class/cms_trigger.d.ts +2 -4
- package/dist/class/cms_trigger.js +6 -3
- package/dist/class/collection_definition.js +6 -0
- package/dist/class/database_trigger.d.ts +35 -25
- package/dist/class/database_trigger.js +147 -20
- package/dist/services/data_provider/router.js +0 -36
- package/dist/services/data_provider/service.d.ts +1 -2
- package/dist/services/data_provider/service.js +1 -16
- package/dist/services/file/service.js +10 -0
- package/package.json +1 -1
- package/src/class/cms_trigger.ts +5 -7
- package/src/class/collection_definition.ts +9 -2
- package/src/class/database_trigger.ts +177 -26
- package/src/services/data_provider/router.ts +7 -36
- package/src/services/data_provider/service.ts +3 -16
- package/src/services/file/service.ts +10 -0
- package/tests/helpers/test-app.ts +2 -0
- package/tests/router/data-provider.router.int.test.ts +158 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DatabaseTriggerContext } from './database_trigger';
|
|
1
|
+
import DatabaseTrigger, { DatabaseTriggerContext } from './database_trigger';
|
|
2
2
|
/**
|
|
3
3
|
* Type for CMS operations that can trigger a callback
|
|
4
4
|
* @typedef {('update-one' | 'insert-one' | 'remove-one')} CmsOperation
|
|
@@ -27,9 +27,7 @@ export type CmsOperation = 'update-one' | 'insert-one' | 'remove-one';
|
|
|
27
27
|
* });
|
|
28
28
|
* ```
|
|
29
29
|
*/
|
|
30
|
-
export declare class CmsTrigger {
|
|
31
|
-
operation: CmsOperation;
|
|
32
|
-
callback: (context: DatabaseTriggerContext) => void;
|
|
30
|
+
export declare class CmsTrigger extends DatabaseTrigger {
|
|
33
31
|
/**
|
|
34
32
|
* Creates a new CmsTrigger instance
|
|
35
33
|
* @param {CmsOperation} operation - The CMS operation to trigger on
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.CmsTrigger = void 0;
|
|
7
|
+
const database_trigger_1 = __importDefault(require("./database_trigger"));
|
|
4
8
|
/**
|
|
5
9
|
* Defines a callback to be executed on specific CMS operations
|
|
6
10
|
* @class CmsTrigger
|
|
@@ -20,7 +24,7 @@ exports.CmsTrigger = void 0;
|
|
|
20
24
|
* });
|
|
21
25
|
* ```
|
|
22
26
|
*/
|
|
23
|
-
class CmsTrigger {
|
|
27
|
+
class CmsTrigger extends database_trigger_1.default {
|
|
24
28
|
/**
|
|
25
29
|
* Creates a new CmsTrigger instance
|
|
26
30
|
* @param {CmsOperation} operation - The CMS operation to trigger on
|
|
@@ -39,8 +43,7 @@ class CmsTrigger {
|
|
|
39
43
|
* ```
|
|
40
44
|
*/
|
|
41
45
|
constructor(operation, callback = () => { }) {
|
|
42
|
-
|
|
43
|
-
this.callback = callback;
|
|
46
|
+
super(operation, callback);
|
|
44
47
|
}
|
|
45
48
|
}
|
|
46
49
|
exports.CmsTrigger = CmsTrigger;
|
|
@@ -138,6 +138,12 @@ class CollectionDefinition {
|
|
|
138
138
|
this.schema = schema;
|
|
139
139
|
this.permissions = permissions;
|
|
140
140
|
this.triggers = triggers;
|
|
141
|
+
// Apply triggers to schema
|
|
142
|
+
if (this.triggers) {
|
|
143
|
+
this.triggers.forEach(trigger => {
|
|
144
|
+
trigger.applyToSchema(this.schema);
|
|
145
|
+
});
|
|
146
|
+
}
|
|
141
147
|
}
|
|
142
148
|
/**
|
|
143
149
|
* Get the mongoose model for this collection
|
|
@@ -1,22 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Type for database operations that can trigger a callback
|
|
3
3
|
*/
|
|
4
|
-
export type DatabaseOperation = 'find' | 'find-one' | 'count' | 'update-one' | 'insert-one' | 'remove-one' | 'aggregate';
|
|
4
|
+
export type DatabaseOperation = 'find' | 'find-one' | 'count' | 'update-one' | 'insert-one' | 'remove-one' | 'aggregate' | 'insert-many' | 'delete-many' | 'find-one-and-update' | 'find-one-and-delete' | 'distinct' | 'validate';
|
|
5
5
|
/**
|
|
6
6
|
* Context interface for database trigger callbacks
|
|
7
7
|
* @interface DatabaseTriggerContext
|
|
8
|
-
* @property {Record<string, any>} doc - The document data for insert/update operations
|
|
9
|
-
* @property {Record<string, any>}
|
|
8
|
+
* @property {Record<string, any>} doc - The document data for insert/update/validate operations.
|
|
9
|
+
* @property {Record<string, any>[]} docs - The documents data for insert-many operations.
|
|
10
|
+
* @property {Record<string, any>} query - The query data for find/remove/count/distinct operations
|
|
10
11
|
* @property {Record<string, any>} update - The update data for update operations
|
|
11
12
|
* @property {Record<string, any>[]} pipelines - The aggregation pipelines for aggregate operations
|
|
12
|
-
* @property {
|
|
13
|
+
* @property {any} queryResult - The result of the database operation (can be document, array of documents, number, or null)
|
|
13
14
|
*/
|
|
14
15
|
export interface DatabaseTriggerContext {
|
|
15
16
|
doc?: Record<string, any>;
|
|
17
|
+
docs?: Record<string, any>[];
|
|
16
18
|
query?: Record<string, any>;
|
|
17
19
|
update?: Record<string, any>;
|
|
18
20
|
pipelines?: Record<string, any>[];
|
|
19
|
-
queryResult:
|
|
21
|
+
queryResult: any;
|
|
20
22
|
}
|
|
21
23
|
/**
|
|
22
24
|
* The callback function to be executed on specific database operations
|
|
@@ -30,36 +32,39 @@ export interface DatabaseTriggerContext {
|
|
|
30
32
|
*/
|
|
31
33
|
type DatabaseTriggerCallback = (context: DatabaseTriggerContext) => void;
|
|
32
34
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
+
* In a complex application, you may need to perform additional actions after a database operation.
|
|
36
|
+
* This is where DatabaseTrigger comes in, allowing you to define callbacks for specific database operations on a collection.
|
|
35
37
|
*
|
|
36
|
-
* Supported
|
|
38
|
+
* ### Supported Triggers and Contexts
|
|
37
39
|
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* |
|
|
41
|
-
* |
|
|
42
|
-
* | `
|
|
43
|
-
* | `
|
|
44
|
-
* | `
|
|
45
|
-
* | `
|
|
46
|
-
* | `
|
|
40
|
+
* The `callback` function associated with a trigger receives a {@link DatabaseTriggerContext} object. The properties available in this context vary depending on the trigger operation.
|
|
41
|
+
*
|
|
42
|
+
* | Trigger Operation | Description | Context Data |
|
|
43
|
+
* | :--- | :--- | :--- |
|
|
44
|
+
* | `insert-one` | Triggered after a single document insertion. | `doc`: The inserted document.<br>`queryResult`: The inserted document. |
|
|
45
|
+
* | `insert-many` | Triggered after multiple document insertions. | `docs`: The array of inserted documents.<br>`queryResult`: The array of inserted documents. |
|
|
46
|
+
* | `update-one` | Triggered after updating a single document (using `updateOne`). | `query`: The query filter used.<br>`update`: The update operations applied.<br>`queryResult`: The operation result. |
|
|
47
|
+
* | `find-one-and-update` | Triggered after `findOneAndUpdate`. | `query`: The query filter used.<br>`update`: The update operations applied.<br>`queryResult`: The updated document. |
|
|
48
|
+
* | `delete-many` | Triggered after removing multiple documents (using `deleteMany`). | `query`: The query filter used.<br>`queryResult`: The operation result. |
|
|
49
|
+
* | `remove-one` | Triggered after removing a single document (using `deleteOne`). | `query`: The query filter used.<br>`queryResult`: The operation result. |
|
|
50
|
+
* | `find-one-and-delete` | Triggered after `findOneAndDelete` or `findOneAndRemove`. | `query`: The query filter used.<br>`queryResult`: The removed document. |
|
|
51
|
+
* | `find` | Triggered after a `find` query. | `query`: The query filter used.<br>`queryResult`: Array of found documents. |
|
|
52
|
+
* | `find-one` | Triggered after a `findOne` query. | `query`: The query filter used.<br>`queryResult`: The found document. |
|
|
53
|
+
* | `count` | Triggered after a `countDocuments` query. | `query`: The query filter used.<br>`queryResult`: The count (number). |
|
|
54
|
+
* | `aggregate` | Triggered after an aggregation pipeline. | `pipelines`: The aggregation pipeline used.<br>`queryResult`: The aggregation result. |
|
|
55
|
+
* | `distinct` | Triggered after a `distinct` query. | `query`: The query filter used.<br>`queryResult`: The distinct values. |
|
|
56
|
+
* | `validate` | Triggered after document validation. | `doc`: The validated document.<br>`queryResult`: The validated document. |
|
|
47
57
|
*
|
|
48
58
|
* @property {DatabaseOperation} operation - The database operation that triggers the callback
|
|
49
59
|
* @property {DatabaseTriggerCallback} callback - The callback function to be executed
|
|
50
60
|
*
|
|
51
61
|
* @example
|
|
52
62
|
* ```typescript
|
|
53
|
-
* import { DatabaseTrigger } from '@
|
|
63
|
+
* import { DatabaseTrigger } from '@modular-rest/server';
|
|
54
64
|
*
|
|
55
|
-
* const trigger = new DatabaseTrigger('insert-one', ({
|
|
65
|
+
* const trigger = new DatabaseTrigger('insert-one', ({ doc, queryResult }) => {
|
|
56
66
|
* console.log('New document inserted:', queryResult);
|
|
57
|
-
*
|
|
58
|
-
* try {
|
|
59
|
-
* // Perform additional actions after document insertion
|
|
60
|
-
* } catch (error) {
|
|
61
|
-
* console.error('Error performing additional actions:', error);
|
|
62
|
-
* }
|
|
67
|
+
* // Perform additional actions
|
|
63
68
|
* });
|
|
64
69
|
*
|
|
65
70
|
* // Use the trigger in a collection definition
|
|
@@ -86,5 +91,10 @@ export declare class DatabaseTrigger {
|
|
|
86
91
|
*
|
|
87
92
|
*/
|
|
88
93
|
constructor(operation: DatabaseOperation, callback?: DatabaseTriggerCallback);
|
|
94
|
+
/**
|
|
95
|
+
* Applies the trigger to a Mongoose schema
|
|
96
|
+
* @param {Schema} schema - The mongoose schema to apply the trigger to
|
|
97
|
+
*/
|
|
98
|
+
applyToSchema(schema: any): void;
|
|
89
99
|
}
|
|
90
100
|
export default DatabaseTrigger;
|
|
@@ -2,36 +2,39 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.DatabaseTrigger = void 0;
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* In a complex application, you may need to perform additional actions after a database operation.
|
|
6
|
+
* This is where DatabaseTrigger comes in, allowing you to define callbacks for specific database operations on a collection.
|
|
7
7
|
*
|
|
8
|
-
* Supported
|
|
8
|
+
* ### Supported Triggers and Contexts
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* |
|
|
13
|
-
* |
|
|
14
|
-
* | `
|
|
15
|
-
* | `
|
|
16
|
-
* | `
|
|
17
|
-
* | `
|
|
18
|
-
* | `
|
|
10
|
+
* The `callback` function associated with a trigger receives a {@link DatabaseTriggerContext} object. The properties available in this context vary depending on the trigger operation.
|
|
11
|
+
*
|
|
12
|
+
* | Trigger Operation | Description | Context Data |
|
|
13
|
+
* | :--- | :--- | :--- |
|
|
14
|
+
* | `insert-one` | Triggered after a single document insertion. | `doc`: The inserted document.<br>`queryResult`: The inserted document. |
|
|
15
|
+
* | `insert-many` | Triggered after multiple document insertions. | `docs`: The array of inserted documents.<br>`queryResult`: The array of inserted documents. |
|
|
16
|
+
* | `update-one` | Triggered after updating a single document (using `updateOne`). | `query`: The query filter used.<br>`update`: The update operations applied.<br>`queryResult`: The operation result. |
|
|
17
|
+
* | `find-one-and-update` | Triggered after `findOneAndUpdate`. | `query`: The query filter used.<br>`update`: The update operations applied.<br>`queryResult`: The updated document. |
|
|
18
|
+
* | `delete-many` | Triggered after removing multiple documents (using `deleteMany`). | `query`: The query filter used.<br>`queryResult`: The operation result. |
|
|
19
|
+
* | `remove-one` | Triggered after removing a single document (using `deleteOne`). | `query`: The query filter used.<br>`queryResult`: The operation result. |
|
|
20
|
+
* | `find-one-and-delete` | Triggered after `findOneAndDelete` or `findOneAndRemove`. | `query`: The query filter used.<br>`queryResult`: The removed document. |
|
|
21
|
+
* | `find` | Triggered after a `find` query. | `query`: The query filter used.<br>`queryResult`: Array of found documents. |
|
|
22
|
+
* | `find-one` | Triggered after a `findOne` query. | `query`: The query filter used.<br>`queryResult`: The found document. |
|
|
23
|
+
* | `count` | Triggered after a `countDocuments` query. | `query`: The query filter used.<br>`queryResult`: The count (number). |
|
|
24
|
+
* | `aggregate` | Triggered after an aggregation pipeline. | `pipelines`: The aggregation pipeline used.<br>`queryResult`: The aggregation result. |
|
|
25
|
+
* | `distinct` | Triggered after a `distinct` query. | `query`: The query filter used.<br>`queryResult`: The distinct values. |
|
|
26
|
+
* | `validate` | Triggered after document validation. | `doc`: The validated document.<br>`queryResult`: The validated document. |
|
|
19
27
|
*
|
|
20
28
|
* @property {DatabaseOperation} operation - The database operation that triggers the callback
|
|
21
29
|
* @property {DatabaseTriggerCallback} callback - The callback function to be executed
|
|
22
30
|
*
|
|
23
31
|
* @example
|
|
24
32
|
* ```typescript
|
|
25
|
-
* import { DatabaseTrigger } from '@
|
|
33
|
+
* import { DatabaseTrigger } from '@modular-rest/server';
|
|
26
34
|
*
|
|
27
|
-
* const trigger = new DatabaseTrigger('insert-one', ({
|
|
35
|
+
* const trigger = new DatabaseTrigger('insert-one', ({ doc, queryResult }) => {
|
|
28
36
|
* console.log('New document inserted:', queryResult);
|
|
29
|
-
*
|
|
30
|
-
* try {
|
|
31
|
-
* // Perform additional actions after document insertion
|
|
32
|
-
* } catch (error) {
|
|
33
|
-
* console.error('Error performing additional actions:', error);
|
|
34
|
-
* }
|
|
37
|
+
* // Perform additional actions
|
|
35
38
|
* });
|
|
36
39
|
*
|
|
37
40
|
* // Use the trigger in a collection definition
|
|
@@ -59,6 +62,130 @@ class DatabaseTrigger {
|
|
|
59
62
|
this.operation = operation;
|
|
60
63
|
this.callback = callback;
|
|
61
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* Applies the trigger to a Mongoose schema
|
|
67
|
+
* @param {Schema} schema - The mongoose schema to apply the trigger to
|
|
68
|
+
*/
|
|
69
|
+
applyToSchema(schema) {
|
|
70
|
+
const callback = this.callback;
|
|
71
|
+
switch (this.operation) {
|
|
72
|
+
case 'insert-one':
|
|
73
|
+
schema.post('save', function (doc) {
|
|
74
|
+
callback({
|
|
75
|
+
doc: doc.toObject ? doc.toObject() : doc,
|
|
76
|
+
queryResult: doc,
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
break;
|
|
80
|
+
case 'update-one':
|
|
81
|
+
schema.post('updateOne', function (result) {
|
|
82
|
+
callback({
|
|
83
|
+
query: this.getQuery(),
|
|
84
|
+
update: this.getUpdate(),
|
|
85
|
+
queryResult: result,
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
break;
|
|
89
|
+
case 'remove-one':
|
|
90
|
+
schema.post('deleteOne', function (result) {
|
|
91
|
+
callback({
|
|
92
|
+
query: this.getQuery(),
|
|
93
|
+
queryResult: result,
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
break;
|
|
97
|
+
case 'find':
|
|
98
|
+
schema.post('find', function (docs) {
|
|
99
|
+
callback({
|
|
100
|
+
query: this.getQuery(),
|
|
101
|
+
queryResult: docs, // Pass docs directly, Mongoose returns documents
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
break;
|
|
105
|
+
case 'find-one':
|
|
106
|
+
schema.post('findOne', function (doc) {
|
|
107
|
+
callback({
|
|
108
|
+
query: this.getQuery(),
|
|
109
|
+
queryResult: doc,
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
break;
|
|
113
|
+
case 'count':
|
|
114
|
+
schema.post('countDocuments', function (count) {
|
|
115
|
+
callback({
|
|
116
|
+
query: this.getQuery(),
|
|
117
|
+
queryResult: count,
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
break;
|
|
121
|
+
case 'aggregate':
|
|
122
|
+
schema.post('aggregate', function (docs) {
|
|
123
|
+
callback({
|
|
124
|
+
pipelines: this.pipeline(),
|
|
125
|
+
queryResult: docs,
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
break;
|
|
129
|
+
case 'insert-many':
|
|
130
|
+
schema.post('insertMany', function (docs) {
|
|
131
|
+
callback({
|
|
132
|
+
docs: docs,
|
|
133
|
+
queryResult: docs,
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
break;
|
|
137
|
+
case 'delete-many':
|
|
138
|
+
schema.post('deleteMany', function (result) {
|
|
139
|
+
callback({
|
|
140
|
+
query: this.getQuery(),
|
|
141
|
+
queryResult: result,
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
break;
|
|
145
|
+
case 'find-one-and-update':
|
|
146
|
+
schema.post('findOneAndUpdate', function (doc) {
|
|
147
|
+
callback({
|
|
148
|
+
query: this.getQuery(),
|
|
149
|
+
update: this.getUpdate(),
|
|
150
|
+
queryResult: doc,
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
break;
|
|
154
|
+
case 'find-one-and-delete':
|
|
155
|
+
schema.post('findOneAndDelete', function (doc) {
|
|
156
|
+
callback({
|
|
157
|
+
query: this.getQuery(),
|
|
158
|
+
queryResult: doc,
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
// Also support findOneAndRemove which is deprecated but might be used
|
|
162
|
+
schema.post('findOneAndRemove', function (doc) {
|
|
163
|
+
callback({
|
|
164
|
+
query: this.getQuery(),
|
|
165
|
+
queryResult: doc,
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
break;
|
|
169
|
+
case 'distinct':
|
|
170
|
+
schema.post('distinct', function (result) {
|
|
171
|
+
callback({
|
|
172
|
+
query: this.getQuery(),
|
|
173
|
+
queryResult: result,
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
break;
|
|
177
|
+
case 'validate':
|
|
178
|
+
schema.post('validate', function (doc) {
|
|
179
|
+
callback({
|
|
180
|
+
doc: doc.toObject ? doc.toObject() : doc,
|
|
181
|
+
queryResult: doc,
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
break;
|
|
185
|
+
default:
|
|
186
|
+
console.warn(`Unsupported trigger operation: ${this.operation}`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
62
189
|
}
|
|
63
190
|
exports.DatabaseTrigger = DatabaseTrigger;
|
|
64
191
|
exports.default = DatabaseTrigger;
|
|
@@ -111,11 +111,6 @@ dataProvider.post('/find', async (ctx) => {
|
|
|
111
111
|
await queryRequest
|
|
112
112
|
.exec()
|
|
113
113
|
.then(async (docs) => {
|
|
114
|
-
// Call trigger
|
|
115
|
-
service.triggers.call('find', body.database, body.collection, {
|
|
116
|
-
query: body.query,
|
|
117
|
-
queryResult: docs,
|
|
118
|
-
});
|
|
119
114
|
ctx.body = { data: docs };
|
|
120
115
|
})
|
|
121
116
|
.catch(err => {
|
|
@@ -157,11 +152,6 @@ dataProvider.post('/find-one', async (ctx) => {
|
|
|
157
152
|
await queryRequest
|
|
158
153
|
.exec()
|
|
159
154
|
.then(async (doc) => {
|
|
160
|
-
// Call trigger
|
|
161
|
-
service.triggers.call('find-one', body.database, body.collection, {
|
|
162
|
-
query: body.query,
|
|
163
|
-
queryResult: doc,
|
|
164
|
-
});
|
|
165
155
|
ctx.body = { data: doc };
|
|
166
156
|
})
|
|
167
157
|
.catch(err => {
|
|
@@ -189,11 +179,6 @@ dataProvider.post('/count', async (ctx) => {
|
|
|
189
179
|
.countDocuments(body.query)
|
|
190
180
|
.exec()
|
|
191
181
|
.then(count => {
|
|
192
|
-
// Call trigger
|
|
193
|
-
service.triggers.call('count', body.database, body.collection, {
|
|
194
|
-
query: body.query,
|
|
195
|
-
queryResult: count,
|
|
196
|
-
});
|
|
197
182
|
ctx.body = { data: count };
|
|
198
183
|
})
|
|
199
184
|
.catch(err => {
|
|
@@ -224,12 +209,6 @@ dataProvider.post('/update-one', async (ctx) => {
|
|
|
224
209
|
.updateOne(body.query, body.update, body.options)
|
|
225
210
|
.exec()
|
|
226
211
|
.then(writeOpResult => {
|
|
227
|
-
// Call trigger
|
|
228
|
-
service.triggers.call('update-one', body.database, body.collection, {
|
|
229
|
-
query: body.query,
|
|
230
|
-
update: body.update,
|
|
231
|
-
queryResult: writeOpResult,
|
|
232
|
-
});
|
|
233
212
|
ctx.body = { data: writeOpResult };
|
|
234
213
|
})
|
|
235
214
|
.catch(err => {
|
|
@@ -260,11 +239,6 @@ dataProvider.post('/insert-one', async (ctx) => {
|
|
|
260
239
|
await new collection(body.doc)
|
|
261
240
|
.save()
|
|
262
241
|
.then(async (newDoc) => {
|
|
263
|
-
// Call trigger
|
|
264
|
-
service.triggers.call('insert-one', body.database, body.collection, {
|
|
265
|
-
doc: body.doc,
|
|
266
|
-
queryResult: newDoc,
|
|
267
|
-
});
|
|
268
242
|
ctx.body = { data: newDoc };
|
|
269
243
|
})
|
|
270
244
|
.catch(err => {
|
|
@@ -295,11 +269,6 @@ dataProvider.post('/remove-one', async (ctx) => {
|
|
|
295
269
|
.deleteOne(body.query)
|
|
296
270
|
.exec()
|
|
297
271
|
.then(async (result) => {
|
|
298
|
-
// Call trigger
|
|
299
|
-
service.triggers.call('remove-one', body.database, body.collection, {
|
|
300
|
-
query: body.query,
|
|
301
|
-
queryResult: result,
|
|
302
|
-
});
|
|
303
272
|
ctx.body = { data: result };
|
|
304
273
|
})
|
|
305
274
|
.catch((err) => {
|
|
@@ -328,11 +297,6 @@ dataProvider.post('/aggregate', async (ctx) => {
|
|
|
328
297
|
.aggregate(body.pipelines)
|
|
329
298
|
.exec()
|
|
330
299
|
.then(async (result) => {
|
|
331
|
-
// Call trigger
|
|
332
|
-
service.triggers.call('aggregate', body.database, body.collection, {
|
|
333
|
-
pipelines: body.pipelines,
|
|
334
|
-
queryResult: result,
|
|
335
|
-
});
|
|
336
300
|
ctx.body = { data: result };
|
|
337
301
|
})
|
|
338
302
|
.catch((err) => {
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import mongoose, { Model, PopulateOptions, Query } from 'mongoose';
|
|
2
|
-
import triggerOperator from '../../class/trigger_operator';
|
|
3
2
|
import TypeCasters from './typeCasters';
|
|
4
3
|
import { CollectionDefinition } from '../../class/collection_definition';
|
|
5
4
|
import { User } from '../../class/user';
|
|
@@ -129,4 +128,4 @@ export declare function performPopulateToQueryObject<T = any>(queryObj: Query<T,
|
|
|
129
128
|
* ```
|
|
130
129
|
*/
|
|
131
130
|
export declare function performAdditionalOptionsToQueryObject<T = any>(queryObj: Query<T, any>, options: Record<string, any>): Query<T, any>;
|
|
132
|
-
export {
|
|
131
|
+
export { TypeCasters };
|
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.TypeCasters = exports.
|
|
6
|
+
exports.TypeCasters = exports.name = void 0;
|
|
7
7
|
exports.addCollectionDefinitionByList = addCollectionDefinitionByList;
|
|
8
8
|
exports.getCollection = getCollection;
|
|
9
9
|
exports.checkAccess = checkAccess;
|
|
@@ -12,8 +12,6 @@ exports.performPopulateToQueryObject = performPopulateToQueryObject;
|
|
|
12
12
|
exports.performAdditionalOptionsToQueryObject = performAdditionalOptionsToQueryObject;
|
|
13
13
|
const mongoose_1 = __importDefault(require("mongoose"));
|
|
14
14
|
const security_1 = require("../../class/security");
|
|
15
|
-
const trigger_operator_1 = __importDefault(require("../../class/trigger_operator"));
|
|
16
|
-
exports.triggers = trigger_operator_1.default;
|
|
17
15
|
const typeCasters_1 = __importDefault(require("./typeCasters"));
|
|
18
16
|
exports.TypeCasters = typeCasters_1.default;
|
|
19
17
|
const model_registry_1 = __importDefault(require("./model_registry"));
|
|
@@ -100,19 +98,6 @@ function connectToDatabaseByCollectionDefinitionList(dbName, collectionDefinitio
|
|
|
100
98
|
collection: collection,
|
|
101
99
|
permissionList: collectionDefinition.permissions,
|
|
102
100
|
});
|
|
103
|
-
// add trigger
|
|
104
|
-
if (collectionDefinition.triggers != undefined) {
|
|
105
|
-
if (!Array.isArray(collectionDefinition.triggers)) {
|
|
106
|
-
throw new Error('Triggers must be an array');
|
|
107
|
-
}
|
|
108
|
-
collectionDefinition.triggers.forEach(trigger => {
|
|
109
|
-
trigger_operator_1.default.addTrigger({
|
|
110
|
-
...trigger,
|
|
111
|
-
database: collectionDefinition.database,
|
|
112
|
-
collection: collectionDefinition.collection,
|
|
113
|
-
});
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
101
|
});
|
|
117
102
|
// If connection is already connected, resolve immediately
|
|
118
103
|
if (connection.readyState === 1) {
|
|
@@ -163,6 +163,16 @@ class FileService {
|
|
|
163
163
|
let storedFile;
|
|
164
164
|
return new Promise(async (done, reject) => {
|
|
165
165
|
storedFile = FileService.instance.createStoredDetail(fileType, tag);
|
|
166
|
+
// Ensure destination directory exists
|
|
167
|
+
const destDir = path_1.default.dirname(storedFile.fullPath);
|
|
168
|
+
try {
|
|
169
|
+
if (!fs_1.default.existsSync(destDir)) {
|
|
170
|
+
fs_1.default.mkdirSync(destDir, { recursive: true });
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
catch (err) {
|
|
174
|
+
return reject(err);
|
|
175
|
+
}
|
|
166
176
|
fs_1.default.copyFile(filePath, storedFile.fullPath, (err) => {
|
|
167
177
|
if (err) {
|
|
168
178
|
reject(err);
|
package/package.json
CHANGED
package/src/class/cms_trigger.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DatabaseTriggerContext } from './database_trigger';
|
|
1
|
+
import DatabaseTrigger, { DatabaseTriggerContext } from './database_trigger';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Type for CMS operations that can trigger a callback
|
|
@@ -29,9 +29,8 @@ export type CmsOperation = 'update-one' | 'insert-one' | 'remove-one';
|
|
|
29
29
|
* });
|
|
30
30
|
* ```
|
|
31
31
|
*/
|
|
32
|
-
export class CmsTrigger {
|
|
33
|
-
|
|
34
|
-
callback: (context: DatabaseTriggerContext) => void;
|
|
32
|
+
export class CmsTrigger extends DatabaseTrigger {
|
|
33
|
+
|
|
35
34
|
|
|
36
35
|
/**
|
|
37
36
|
* Creates a new CmsTrigger instance
|
|
@@ -52,10 +51,9 @@ export class CmsTrigger {
|
|
|
52
51
|
*/
|
|
53
52
|
constructor(
|
|
54
53
|
operation: CmsOperation,
|
|
55
|
-
callback: (context: DatabaseTriggerContext) => void = () => {}
|
|
54
|
+
callback: (context: DatabaseTriggerContext) => void = () => { }
|
|
56
55
|
) {
|
|
57
|
-
|
|
58
|
-
this.callback = callback;
|
|
56
|
+
super(operation, callback);
|
|
59
57
|
}
|
|
60
58
|
}
|
|
61
59
|
|
|
@@ -117,8 +117,8 @@ export function defineCollection(
|
|
|
117
117
|
|
|
118
118
|
throw new Error(
|
|
119
119
|
`Model for ${definition.database}.${definition.collection} is not available. ` +
|
|
120
|
-
|
|
121
|
-
|
|
120
|
+
`Ensure mongoOption is provided in defineCollection options, config.mongo is set, ` +
|
|
121
|
+
`or the collection is registered via addCollectionDefinitionByList before accessing the model.`
|
|
122
122
|
);
|
|
123
123
|
},
|
|
124
124
|
enumerable: true,
|
|
@@ -204,6 +204,13 @@ export class CollectionDefinition {
|
|
|
204
204
|
this.schema = schema;
|
|
205
205
|
this.permissions = permissions;
|
|
206
206
|
this.triggers = triggers;
|
|
207
|
+
|
|
208
|
+
// Apply triggers to schema
|
|
209
|
+
if (this.triggers) {
|
|
210
|
+
this.triggers.forEach(trigger => {
|
|
211
|
+
trigger.applyToSchema(this.schema);
|
|
212
|
+
});
|
|
213
|
+
}
|
|
207
214
|
}
|
|
208
215
|
|
|
209
216
|
/**
|
|
@@ -8,25 +8,34 @@ export type DatabaseOperation =
|
|
|
8
8
|
| 'update-one'
|
|
9
9
|
| 'insert-one'
|
|
10
10
|
| 'remove-one'
|
|
11
|
-
| 'aggregate'
|
|
11
|
+
| 'aggregate'
|
|
12
|
+
| 'insert-many'
|
|
13
|
+
| 'delete-many'
|
|
14
|
+
| 'find-one-and-update'
|
|
15
|
+
| 'find-one-and-delete'
|
|
16
|
+
| 'distinct'
|
|
17
|
+
| 'validate';
|
|
12
18
|
|
|
13
19
|
/**
|
|
14
20
|
* Context interface for database trigger callbacks
|
|
15
21
|
* @interface DatabaseTriggerContext
|
|
16
|
-
* @property {Record<string, any>} doc - The document data for insert/update operations
|
|
17
|
-
* @property {Record<string, any>}
|
|
22
|
+
* @property {Record<string, any>} doc - The document data for insert/update/validate operations.
|
|
23
|
+
* @property {Record<string, any>[]} docs - The documents data for insert-many operations.
|
|
24
|
+
* @property {Record<string, any>} query - The query data for find/remove/count/distinct operations
|
|
18
25
|
* @property {Record<string, any>} update - The update data for update operations
|
|
19
26
|
* @property {Record<string, any>[]} pipelines - The aggregation pipelines for aggregate operations
|
|
20
|
-
* @property {
|
|
27
|
+
* @property {any} queryResult - The result of the database operation (can be document, array of documents, number, or null)
|
|
21
28
|
*/
|
|
22
29
|
export interface DatabaseTriggerContext {
|
|
23
30
|
doc?: Record<string, any>;
|
|
31
|
+
docs?: Record<string, any>[];
|
|
24
32
|
query?: Record<string, any>;
|
|
25
33
|
update?: Record<string, any>;
|
|
26
34
|
pipelines?: Record<string, any>[];
|
|
27
|
-
queryResult:
|
|
35
|
+
queryResult: any;
|
|
28
36
|
}
|
|
29
37
|
|
|
38
|
+
|
|
30
39
|
/**
|
|
31
40
|
* The callback function to be executed on specific database operations
|
|
32
41
|
* @param {DatabaseTriggerContext} context - The context of the database operation
|
|
@@ -40,36 +49,39 @@ export interface DatabaseTriggerContext {
|
|
|
40
49
|
type DatabaseTriggerCallback = (context: DatabaseTriggerContext) => void;
|
|
41
50
|
|
|
42
51
|
/**
|
|
43
|
-
*
|
|
44
|
-
*
|
|
52
|
+
* In a complex application, you may need to perform additional actions after a database operation.
|
|
53
|
+
* This is where DatabaseTrigger comes in, allowing you to define callbacks for specific database operations on a collection.
|
|
54
|
+
*
|
|
55
|
+
* ### Supported Triggers and Contexts
|
|
45
56
|
*
|
|
46
|
-
*
|
|
57
|
+
* The `callback` function associated with a trigger receives a {@link DatabaseTriggerContext} object. The properties available in this context vary depending on the trigger operation.
|
|
47
58
|
*
|
|
48
|
-
* | Trigger
|
|
49
|
-
* |
|
|
50
|
-
* | `
|
|
51
|
-
* | `
|
|
52
|
-
* | `
|
|
53
|
-
* | `
|
|
54
|
-
* | `
|
|
55
|
-
* | `remove-one` | Triggered
|
|
56
|
-
* | `
|
|
59
|
+
* | Trigger Operation | Description | Context Data |
|
|
60
|
+
* | :--- | :--- | :--- |
|
|
61
|
+
* | `insert-one` | Triggered after a single document insertion. | `doc`: The inserted document.<br>`queryResult`: The inserted document. |
|
|
62
|
+
* | `insert-many` | Triggered after multiple document insertions. | `docs`: The array of inserted documents.<br>`queryResult`: The array of inserted documents. |
|
|
63
|
+
* | `update-one` | Triggered after updating a single document (using `updateOne`). | `query`: The query filter used.<br>`update`: The update operations applied.<br>`queryResult`: The operation result. |
|
|
64
|
+
* | `find-one-and-update` | Triggered after `findOneAndUpdate`. | `query`: The query filter used.<br>`update`: The update operations applied.<br>`queryResult`: The updated document. |
|
|
65
|
+
* | `delete-many` | Triggered after removing multiple documents (using `deleteMany`). | `query`: The query filter used.<br>`queryResult`: The operation result. |
|
|
66
|
+
* | `remove-one` | Triggered after removing a single document (using `deleteOne`). | `query`: The query filter used.<br>`queryResult`: The operation result. |
|
|
67
|
+
* | `find-one-and-delete` | Triggered after `findOneAndDelete` or `findOneAndRemove`. | `query`: The query filter used.<br>`queryResult`: The removed document. |
|
|
68
|
+
* | `find` | Triggered after a `find` query. | `query`: The query filter used.<br>`queryResult`: Array of found documents. |
|
|
69
|
+
* | `find-one` | Triggered after a `findOne` query. | `query`: The query filter used.<br>`queryResult`: The found document. |
|
|
70
|
+
* | `count` | Triggered after a `countDocuments` query. | `query`: The query filter used.<br>`queryResult`: The count (number). |
|
|
71
|
+
* | `aggregate` | Triggered after an aggregation pipeline. | `pipelines`: The aggregation pipeline used.<br>`queryResult`: The aggregation result. |
|
|
72
|
+
* | `distinct` | Triggered after a `distinct` query. | `query`: The query filter used.<br>`queryResult`: The distinct values. |
|
|
73
|
+
* | `validate` | Triggered after document validation. | `doc`: The validated document.<br>`queryResult`: The validated document. |
|
|
57
74
|
*
|
|
58
75
|
* @property {DatabaseOperation} operation - The database operation that triggers the callback
|
|
59
76
|
* @property {DatabaseTriggerCallback} callback - The callback function to be executed
|
|
60
77
|
*
|
|
61
78
|
* @example
|
|
62
79
|
* ```typescript
|
|
63
|
-
* import { DatabaseTrigger } from '@
|
|
80
|
+
* import { DatabaseTrigger } from '@modular-rest/server';
|
|
64
81
|
*
|
|
65
|
-
* const trigger = new DatabaseTrigger('insert-one', ({
|
|
82
|
+
* const trigger = new DatabaseTrigger('insert-one', ({ doc, queryResult }) => {
|
|
66
83
|
* console.log('New document inserted:', queryResult);
|
|
67
|
-
*
|
|
68
|
-
* try {
|
|
69
|
-
* // Perform additional actions after document insertion
|
|
70
|
-
* } catch (error) {
|
|
71
|
-
* console.error('Error performing additional actions:', error);
|
|
72
|
-
* }
|
|
84
|
+
* // Perform additional actions
|
|
73
85
|
* });
|
|
74
86
|
*
|
|
75
87
|
* // Use the trigger in a collection definition
|
|
@@ -96,10 +108,149 @@ export class DatabaseTrigger {
|
|
|
96
108
|
* });
|
|
97
109
|
*
|
|
98
110
|
*/
|
|
99
|
-
constructor(operation: DatabaseOperation, callback: DatabaseTriggerCallback = () => {}) {
|
|
111
|
+
constructor(operation: DatabaseOperation, callback: DatabaseTriggerCallback = () => { }) {
|
|
100
112
|
this.operation = operation;
|
|
101
113
|
this.callback = callback;
|
|
102
114
|
}
|
|
115
|
+
/**
|
|
116
|
+
* Applies the trigger to a Mongoose schema
|
|
117
|
+
* @param {Schema} schema - The mongoose schema to apply the trigger to
|
|
118
|
+
*/
|
|
119
|
+
applyToSchema(schema: any): void {
|
|
120
|
+
const callback = this.callback;
|
|
121
|
+
|
|
122
|
+
switch (this.operation) {
|
|
123
|
+
case 'insert-one':
|
|
124
|
+
schema.post('save', function (doc: any) {
|
|
125
|
+
callback({
|
|
126
|
+
doc: doc.toObject ? doc.toObject() : doc,
|
|
127
|
+
queryResult: doc,
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
break;
|
|
131
|
+
|
|
132
|
+
case 'update-one':
|
|
133
|
+
schema.post('updateOne', function (this: any, result: any) {
|
|
134
|
+
callback({
|
|
135
|
+
query: this.getQuery(),
|
|
136
|
+
update: this.getUpdate(),
|
|
137
|
+
queryResult: result,
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
break;
|
|
141
|
+
|
|
142
|
+
case 'remove-one':
|
|
143
|
+
schema.post('deleteOne', function (this: any, result: any) {
|
|
144
|
+
callback({
|
|
145
|
+
query: this.getQuery(),
|
|
146
|
+
queryResult: result,
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
break;
|
|
150
|
+
|
|
151
|
+
case 'find':
|
|
152
|
+
schema.post('find', function (this: any, docs: any[]) {
|
|
153
|
+
callback({
|
|
154
|
+
query: this.getQuery(),
|
|
155
|
+
queryResult: docs, // Pass docs directly, Mongoose returns documents
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
break;
|
|
159
|
+
|
|
160
|
+
case 'find-one':
|
|
161
|
+
schema.post('findOne', function (this: any, doc: any) {
|
|
162
|
+
callback({
|
|
163
|
+
query: this.getQuery(),
|
|
164
|
+
queryResult: doc,
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
break;
|
|
168
|
+
|
|
169
|
+
case 'count':
|
|
170
|
+
schema.post('countDocuments', function (this: any, count: any) {
|
|
171
|
+
callback({
|
|
172
|
+
query: this.getQuery(),
|
|
173
|
+
queryResult: count,
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
break;
|
|
177
|
+
|
|
178
|
+
case 'aggregate':
|
|
179
|
+
schema.post('aggregate', function (this: any, docs: any[]) {
|
|
180
|
+
callback({
|
|
181
|
+
pipelines: this.pipeline(),
|
|
182
|
+
queryResult: docs,
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
break;
|
|
186
|
+
|
|
187
|
+
case 'insert-many':
|
|
188
|
+
schema.post('insertMany', function (docs: any[]) {
|
|
189
|
+
callback({
|
|
190
|
+
docs: docs,
|
|
191
|
+
queryResult: docs,
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
break;
|
|
195
|
+
|
|
196
|
+
case 'delete-many':
|
|
197
|
+
schema.post('deleteMany', function (this: any, result: any) {
|
|
198
|
+
callback({
|
|
199
|
+
query: this.getQuery(),
|
|
200
|
+
queryResult: result,
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
break;
|
|
204
|
+
|
|
205
|
+
case 'find-one-and-update':
|
|
206
|
+
schema.post('findOneAndUpdate', function (this: any, doc: any) {
|
|
207
|
+
callback({
|
|
208
|
+
query: this.getQuery(),
|
|
209
|
+
update: this.getUpdate(),
|
|
210
|
+
queryResult: doc,
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
break;
|
|
214
|
+
|
|
215
|
+
case 'find-one-and-delete':
|
|
216
|
+
schema.post('findOneAndDelete', function (this: any, doc: any) {
|
|
217
|
+
callback({
|
|
218
|
+
query: this.getQuery(),
|
|
219
|
+
queryResult: doc,
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
// Also support findOneAndRemove which is deprecated but might be used
|
|
223
|
+
schema.post('findOneAndRemove', function (this: any, doc: any) {
|
|
224
|
+
callback({
|
|
225
|
+
query: this.getQuery(),
|
|
226
|
+
queryResult: doc,
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
break;
|
|
230
|
+
|
|
231
|
+
case 'distinct':
|
|
232
|
+
schema.post('distinct', function (this: any, result: any) {
|
|
233
|
+
callback({
|
|
234
|
+
query: this.getQuery(),
|
|
235
|
+
queryResult: result,
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
break;
|
|
239
|
+
|
|
240
|
+
case 'validate':
|
|
241
|
+
schema.post('validate', function (doc: any) {
|
|
242
|
+
callback({
|
|
243
|
+
doc: doc.toObject ? doc.toObject() : doc,
|
|
244
|
+
queryResult: doc,
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
break;
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
default:
|
|
251
|
+
console.warn(`Unsupported trigger operation: ${this.operation}`);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
103
254
|
}
|
|
104
255
|
|
|
105
256
|
export default DatabaseTrigger;
|
|
@@ -92,11 +92,7 @@ dataProvider.post('/find', async (ctx: Context) => {
|
|
|
92
92
|
await queryRequest
|
|
93
93
|
.exec()
|
|
94
94
|
.then(async docs => {
|
|
95
|
-
|
|
96
|
-
service.triggers.call('find', body.database, body.collection, {
|
|
97
|
-
query: body.query,
|
|
98
|
-
queryResult: docs,
|
|
99
|
-
});
|
|
95
|
+
|
|
100
96
|
|
|
101
97
|
ctx.body = { data: docs };
|
|
102
98
|
})
|
|
@@ -151,11 +147,7 @@ dataProvider.post('/find-one', async (ctx: Context) => {
|
|
|
151
147
|
await queryRequest
|
|
152
148
|
.exec()
|
|
153
149
|
.then(async doc => {
|
|
154
|
-
|
|
155
|
-
service.triggers.call('find-one', body.database, body.collection, {
|
|
156
|
-
query: body.query,
|
|
157
|
-
queryResult: doc,
|
|
158
|
-
});
|
|
150
|
+
|
|
159
151
|
|
|
160
152
|
ctx.body = { data: doc };
|
|
161
153
|
})
|
|
@@ -194,11 +186,7 @@ dataProvider.post('/count', async (ctx: Context) => {
|
|
|
194
186
|
.countDocuments(body.query)
|
|
195
187
|
.exec()
|
|
196
188
|
.then(count => {
|
|
197
|
-
|
|
198
|
-
service.triggers.call('count', body.database, body.collection, {
|
|
199
|
-
query: body.query,
|
|
200
|
-
queryResult: count,
|
|
201
|
-
});
|
|
189
|
+
|
|
202
190
|
|
|
203
191
|
ctx.body = { data: count };
|
|
204
192
|
})
|
|
@@ -241,12 +229,7 @@ dataProvider.post('/update-one', async (ctx: Context) => {
|
|
|
241
229
|
.updateOne(body.query, body.update, body.options)
|
|
242
230
|
.exec()
|
|
243
231
|
.then(writeOpResult => {
|
|
244
|
-
|
|
245
|
-
service.triggers.call('update-one', body.database, body.collection, {
|
|
246
|
-
query: body.query,
|
|
247
|
-
update: body.update,
|
|
248
|
-
queryResult: writeOpResult,
|
|
249
|
-
});
|
|
232
|
+
|
|
250
233
|
|
|
251
234
|
ctx.body = { data: writeOpResult };
|
|
252
235
|
})
|
|
@@ -289,11 +272,7 @@ dataProvider.post('/insert-one', async (ctx: Context) => {
|
|
|
289
272
|
await new collection(body.doc)
|
|
290
273
|
.save()
|
|
291
274
|
.then(async newDoc => {
|
|
292
|
-
|
|
293
|
-
service.triggers.call('insert-one', body.database, body.collection, {
|
|
294
|
-
doc: body.doc,
|
|
295
|
-
queryResult: newDoc,
|
|
296
|
-
});
|
|
275
|
+
|
|
297
276
|
|
|
298
277
|
ctx.body = { data: newDoc };
|
|
299
278
|
})
|
|
@@ -336,11 +315,7 @@ dataProvider.post('/remove-one', async (ctx: Context) => {
|
|
|
336
315
|
.deleteOne(body.query)
|
|
337
316
|
.exec()
|
|
338
317
|
.then(async (result: any) => {
|
|
339
|
-
|
|
340
|
-
service.triggers.call('remove-one', body.database, body.collection, {
|
|
341
|
-
query: body.query,
|
|
342
|
-
queryResult: result,
|
|
343
|
-
});
|
|
318
|
+
|
|
344
319
|
|
|
345
320
|
ctx.body = { data: result };
|
|
346
321
|
})
|
|
@@ -380,11 +355,7 @@ dataProvider.post('/aggregate', async (ctx: Context) => {
|
|
|
380
355
|
.aggregate(body.pipelines)
|
|
381
356
|
.exec()
|
|
382
357
|
.then(async (result: any) => {
|
|
383
|
-
|
|
384
|
-
service.triggers.call('aggregate', body.database, body.collection, {
|
|
385
|
-
pipelines: body.pipelines,
|
|
386
|
-
queryResult: result,
|
|
387
|
-
});
|
|
358
|
+
|
|
388
359
|
|
|
389
360
|
ctx.body = { data: result };
|
|
390
361
|
})
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import mongoose, { Connection, Model, PopulateOptions, Query } from 'mongoose';
|
|
2
2
|
import { AccessTypes, AccessDefinition, Permission } from '../../class/security';
|
|
3
|
-
|
|
3
|
+
|
|
4
4
|
import TypeCasters from './typeCasters';
|
|
5
5
|
import { config } from '../../config';
|
|
6
6
|
import { CollectionDefinition } from '../../class/collection_definition';
|
|
@@ -128,20 +128,7 @@ function connectToDatabaseByCollectionDefinitionList(
|
|
|
128
128
|
permissionList: collectionDefinition.permissions,
|
|
129
129
|
});
|
|
130
130
|
|
|
131
|
-
// add trigger
|
|
132
|
-
if (collectionDefinition.triggers != undefined) {
|
|
133
|
-
if (!Array.isArray(collectionDefinition.triggers)) {
|
|
134
|
-
throw new Error('Triggers must be an array');
|
|
135
|
-
}
|
|
136
131
|
|
|
137
|
-
collectionDefinition.triggers.forEach(trigger => {
|
|
138
|
-
triggerOperator.addTrigger({
|
|
139
|
-
...trigger,
|
|
140
|
-
database: collectionDefinition.database,
|
|
141
|
-
collection: collectionDefinition.collection,
|
|
142
|
-
});
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
132
|
});
|
|
146
133
|
|
|
147
134
|
// If connection is already connected, resolve immediately
|
|
@@ -362,5 +349,5 @@ export function performAdditionalOptionsToQueryObject<T = any>(
|
|
|
362
349
|
return queryObj;
|
|
363
350
|
}
|
|
364
351
|
|
|
365
|
-
// Instead, export
|
|
366
|
-
export {
|
|
352
|
+
// Instead, export TypeCasters
|
|
353
|
+
export { TypeCasters };
|
|
@@ -191,6 +191,16 @@ class FileService {
|
|
|
191
191
|
return new Promise(async (done, reject) => {
|
|
192
192
|
storedFile = FileService.instance.createStoredDetail(fileType, tag);
|
|
193
193
|
|
|
194
|
+
// Ensure destination directory exists
|
|
195
|
+
const destDir = pathModule.dirname(storedFile.fullPath);
|
|
196
|
+
try {
|
|
197
|
+
if (!fs.existsSync(destDir)) {
|
|
198
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
199
|
+
}
|
|
200
|
+
} catch (err) {
|
|
201
|
+
return reject(err);
|
|
202
|
+
}
|
|
203
|
+
|
|
194
204
|
fs.copyFile(filePath, storedFile.fullPath, (err: Error | null) => {
|
|
195
205
|
if (err) {
|
|
196
206
|
reject(err);
|
|
@@ -28,6 +28,7 @@ export interface TestAppContext {
|
|
|
28
28
|
uploadDir: string;
|
|
29
29
|
adminToken: string;
|
|
30
30
|
dbPrefix: string;
|
|
31
|
+
mongoOption?: RestOptions['mongo'];
|
|
31
32
|
cleanup: () => Promise<void>;
|
|
32
33
|
}
|
|
33
34
|
|
|
@@ -138,6 +139,7 @@ export async function createIntegrationTestApp(
|
|
|
138
139
|
uploadDir,
|
|
139
140
|
adminToken,
|
|
140
141
|
dbPrefix,
|
|
142
|
+
mongoOption: options.mongo,
|
|
141
143
|
cleanup,
|
|
142
144
|
};
|
|
143
145
|
}
|
|
@@ -189,4 +189,162 @@ describe('data-provider router integration', () => {
|
|
|
189
189
|
});
|
|
190
190
|
expect(verifyRes.body.data).toBeNull();
|
|
191
191
|
});
|
|
192
|
+
|
|
193
|
+
it('triggers are executed on direct model usage and api usage', async () => {
|
|
194
|
+
// 1. Define a trigger
|
|
195
|
+
const triggerMock = jest.fn();
|
|
196
|
+
|
|
197
|
+
// We need to access the collection definition to add a trigger dynamically for testing
|
|
198
|
+
// or we assume the existing setup allows us to mock/spy on something.
|
|
199
|
+
// However, since we are in an integration test with a real app context,
|
|
200
|
+
// modifying the collection definition on the fly might be tricky if models are already compiled.
|
|
201
|
+
// Ideally we should have a test collection with triggers defined.
|
|
202
|
+
|
|
203
|
+
// Let's create a dynamic collection definition with a trigger for this test if possible,
|
|
204
|
+
// or allow the test helper to set one up.
|
|
205
|
+
// Since we cannot easily modify the setup here without changing helpers,
|
|
206
|
+
// and the user asked to "add new test case" to this file,
|
|
207
|
+
// we need to assume there is a way or we need to add a collection definition here.
|
|
208
|
+
|
|
209
|
+
// Looking at the imports, we can import defineCollection etc.
|
|
210
|
+
// But the app is already created in beforeAll via createIntegrationTestApp().
|
|
211
|
+
|
|
212
|
+
// IF we cannot add a new collection easily, we'll try to spy on console or a side effect
|
|
213
|
+
// if there are existing triggers. But the current test app seems to use standard collections.
|
|
214
|
+
|
|
215
|
+
// Let's assume we can register a new collection via a service method if available,
|
|
216
|
+
// or we might need to modify the test helper.
|
|
217
|
+
// For now, I will write the test assuming we can verify the trigger via a mocked callback
|
|
218
|
+
// attached to a new collection we define locally and register.
|
|
219
|
+
|
|
220
|
+
const { defineCollection } = require('../../src/class/collection_definition');
|
|
221
|
+
const { Permission } = require('../../src/class/security');
|
|
222
|
+
const { DatabaseTrigger } = require('../../src/class/database_trigger');
|
|
223
|
+
const { Schema } = require('mongoose');
|
|
224
|
+
const modelRegistry = require('../../src/services/data_provider/model_registry').default;
|
|
225
|
+
const service = require('../../src/services/data_provider/service');
|
|
226
|
+
|
|
227
|
+
const triggerCallback = jest.fn();
|
|
228
|
+
const trigger = new DatabaseTrigger('insert-one', triggerCallback);
|
|
229
|
+
|
|
230
|
+
const testCollectionDef = defineCollection({
|
|
231
|
+
database: 'test_db',
|
|
232
|
+
collection: 'trigger_test',
|
|
233
|
+
schema: new Schema({ name: String }),
|
|
234
|
+
permissions: [
|
|
235
|
+
new Permission({ accessType: 'anonymous_access', read: true, write: true }),
|
|
236
|
+
new Permission({ accessType: 'god_access', read: true, write: true })
|
|
237
|
+
],
|
|
238
|
+
triggers: [trigger]
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// Register this new collection
|
|
242
|
+
await service.addCollectionDefinitionByList({
|
|
243
|
+
list: [testCollectionDef],
|
|
244
|
+
mongoOption: ctx.mongoOption // We need to access mongoOption from ctx
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// 2. Perform operation via API
|
|
248
|
+
await ctx.request
|
|
249
|
+
.post('/data-provider/insert-one')
|
|
250
|
+
.set('authorization', ctx.adminToken)
|
|
251
|
+
.send({
|
|
252
|
+
database: 'test_db',
|
|
253
|
+
collection: 'trigger_test',
|
|
254
|
+
doc: { name: 'api_insert' }
|
|
255
|
+
})
|
|
256
|
+
.expect(200);
|
|
257
|
+
|
|
258
|
+
// Verify trigger called
|
|
259
|
+
expect(triggerCallback).toHaveBeenCalledTimes(1);
|
|
260
|
+
expect(triggerCallback.mock.calls[0][0].doc).toMatchObject({ name: 'api_insert' });
|
|
261
|
+
|
|
262
|
+
// 3. Perform operation via Mongoose Model directly
|
|
263
|
+
const Model = testCollectionDef.model;
|
|
264
|
+
await new Model({ name: 'direct_insert' }).save();
|
|
265
|
+
|
|
266
|
+
// Verify trigger called again
|
|
267
|
+
expect(triggerCallback).toHaveBeenCalledTimes(2);
|
|
268
|
+
expect(triggerCallback.mock.calls[1][0].doc).toMatchObject({ name: 'direct_insert' });
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it('triggers find-one-and-update', async () => {
|
|
272
|
+
// 1. Setup Trigger
|
|
273
|
+
const { defineCollection } = require('../../src/class/collection_definition');
|
|
274
|
+
const { Permission } = require('../../src/class/security');
|
|
275
|
+
const { DatabaseTrigger } = require('../../src/class/database_trigger');
|
|
276
|
+
const { Schema } = require('mongoose');
|
|
277
|
+
const service = require('../../src/services/data_provider/service');
|
|
278
|
+
|
|
279
|
+
const triggerCallback = jest.fn();
|
|
280
|
+
const trigger = new DatabaseTrigger('find-one-and-update', triggerCallback);
|
|
281
|
+
|
|
282
|
+
const testCollectionDef = defineCollection({
|
|
283
|
+
database: 'test_db',
|
|
284
|
+
collection: 'trigger_update_test',
|
|
285
|
+
schema: new Schema({ name: String, version: Number }),
|
|
286
|
+
permissions: [
|
|
287
|
+
new Permission({ accessType: 'anonymous_access', read: true, write: true }),
|
|
288
|
+
new Permission({ accessType: 'god_access', read: true, write: true })
|
|
289
|
+
],
|
|
290
|
+
triggers: [trigger]
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
await service.addCollectionDefinitionByList({
|
|
294
|
+
list: [testCollectionDef],
|
|
295
|
+
mongoOption: ctx.mongoOption
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// 2. Insert initial doc
|
|
299
|
+
const Model = testCollectionDef.model;
|
|
300
|
+
const doc = await new Model({ name: 'initial', version: 1 }).save();
|
|
301
|
+
|
|
302
|
+
// 3. Update via Mongoose
|
|
303
|
+
await Model.findOneAndUpdate({ _id: doc._id }, { $set: { version: 2 } });
|
|
304
|
+
|
|
305
|
+
// 4. Verify Trigger
|
|
306
|
+
expect(triggerCallback).toHaveBeenCalledTimes(1);
|
|
307
|
+
expect(triggerCallback.mock.calls[0][0].query).toMatchObject({ _id: doc._id });
|
|
308
|
+
expect(triggerCallback.mock.calls[0][0].update).toMatchObject({ $set: { version: 2 } });
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it('triggers delete-many', async () => {
|
|
312
|
+
// 1. Setup Trigger
|
|
313
|
+
const { defineCollection } = require('../../src/class/collection_definition');
|
|
314
|
+
const { Permission } = require('../../src/class/security');
|
|
315
|
+
const { DatabaseTrigger } = require('../../src/class/database_trigger');
|
|
316
|
+
const { Schema } = require('mongoose');
|
|
317
|
+
const service = require('../../src/services/data_provider/service');
|
|
318
|
+
|
|
319
|
+
const triggerCallback = jest.fn();
|
|
320
|
+
const trigger = new DatabaseTrigger('delete-many', triggerCallback);
|
|
321
|
+
|
|
322
|
+
const testCollectionDef = defineCollection({
|
|
323
|
+
database: 'test_db',
|
|
324
|
+
collection: 'trigger_delete_many_test',
|
|
325
|
+
schema: new Schema({ name: String }),
|
|
326
|
+
permissions: [
|
|
327
|
+
new Permission({ accessType: 'anonymous_access', read: true, write: true }),
|
|
328
|
+
new Permission({ accessType: 'god_access', read: true, write: true })
|
|
329
|
+
],
|
|
330
|
+
triggers: [trigger]
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
await service.addCollectionDefinitionByList({
|
|
334
|
+
list: [testCollectionDef],
|
|
335
|
+
mongoOption: ctx.mongoOption
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
// 2. Insert docs
|
|
339
|
+
const Model = testCollectionDef.model;
|
|
340
|
+
await Model.insertMany([{ name: 'doc1' }, { name: 'doc2' }]);
|
|
341
|
+
|
|
342
|
+
// 3. Delete via Mongoose
|
|
343
|
+
await Model.deleteMany({ name: { $regex: 'doc' } });
|
|
344
|
+
|
|
345
|
+
// 4. Verify Trigger
|
|
346
|
+
expect(triggerCallback).toHaveBeenCalledTimes(1);
|
|
347
|
+
expect(triggerCallback.mock.calls[0][0].query).toMatchObject({ name: { $regex: 'doc' } });
|
|
348
|
+
expect(triggerCallback.mock.calls[0][0].queryResult.deletedCount).toBe(2);
|
|
349
|
+
});
|
|
192
350
|
});
|