@lenne.tech/nest-server 8.6.6 → 8.6.9
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/core/common/decorators/graphql-user.decorator.js +2 -3
- package/dist/core/common/decorators/graphql-user.decorator.js.map +1 -1
- package/dist/core/common/decorators/restricted.decorator.js +14 -13
- package/dist/core/common/decorators/restricted.decorator.js.map +1 -1
- package/dist/core/common/helpers/context.helper.js +4 -5
- package/dist/core/common/helpers/context.helper.js.map +1 -1
- package/dist/core/common/helpers/db.helper.js +34 -22
- package/dist/core/common/helpers/db.helper.js.map +1 -1
- package/dist/core/common/helpers/file.helper.js +5 -1
- package/dist/core/common/helpers/file.helper.js.map +1 -1
- package/dist/core/common/helpers/filter.helper.js +16 -9
- package/dist/core/common/helpers/filter.helper.js.map +1 -1
- package/dist/core/common/helpers/graphql.helper.js +11 -8
- package/dist/core/common/helpers/graphql.helper.js.map +1 -1
- package/dist/core/common/helpers/input.helper.js +17 -11
- package/dist/core/common/helpers/input.helper.js.map +1 -1
- package/dist/core/common/helpers/model.helper.d.ts +2 -0
- package/dist/core/common/helpers/model.helper.js +125 -3
- package/dist/core/common/helpers/model.helper.js.map +1 -1
- package/dist/core/common/helpers/service.helper.d.ts +2 -0
- package/dist/core/common/helpers/service.helper.js +30 -16
- package/dist/core/common/helpers/service.helper.js.map +1 -1
- package/dist/core/common/inputs/core-input.input.js +6 -1
- package/dist/core/common/inputs/core-input.input.js.map +1 -1
- package/dist/core/common/inputs/single-filter.input.d.ts +1 -0
- package/dist/core/common/inputs/single-filter.input.js +8 -0
- package/dist/core/common/inputs/single-filter.input.js.map +1 -1
- package/dist/core/common/interfaces/prepare-input-options.interface.d.ts +1 -0
- package/dist/core/common/interfaces/prepare-output-options.interface.d.ts +1 -0
- package/dist/core/common/interfaces/service-options.interface.d.ts +3 -1
- package/dist/core/common/models/core-model.model.js +14 -2
- package/dist/core/common/models/core-model.model.js.map +1 -1
- package/dist/core/common/pipes/check-input.pipe.js +1 -1
- package/dist/core/common/pipes/check-input.pipe.js.map +1 -1
- package/dist/core/common/pipes/map-and-validate.pipe.js +2 -2
- package/dist/core/common/pipes/map-and-validate.pipe.js.map +1 -1
- package/dist/core/common/services/crud.service.js +3 -5
- package/dist/core/common/services/crud.service.js.map +1 -1
- package/dist/core/common/services/email.service.js +5 -1
- package/dist/core/common/services/email.service.js.map +1 -1
- package/dist/core/common/services/mailjet.service.d.ts +1 -1
- package/dist/core/common/services/mailjet.service.js +10 -3
- package/dist/core/common/services/mailjet.service.js.map +1 -1
- package/dist/core/common/services/module.service.js +41 -7
- package/dist/core/common/services/module.service.js.map +1 -1
- package/dist/core/common/types/field-selection.type.d.ts +1 -1
- package/dist/core/modules/auth/core-auth.module.js +2 -2
- package/dist/core/modules/auth/core-auth.module.js.map +1 -1
- package/dist/core/modules/auth/guards/auth.guard.js +4 -5
- package/dist/core/modules/auth/guards/auth.guard.js.map +1 -1
- package/dist/core/modules/auth/guards/roles.guard.js +1 -2
- package/dist/core/modules/auth/guards/roles.guard.js.map +1 -1
- package/dist/core/modules/file/core-file.service.d.ts +26 -0
- package/dist/core/modules/file/core-file.service.js +116 -0
- package/dist/core/modules/file/core-file.service.js.map +1 -0
- package/dist/core/modules/file/file-info.output.d.ts +10 -0
- package/dist/core/modules/file/file-info.output.js +49 -0
- package/dist/core/modules/file/file-info.output.js.map +1 -0
- package/dist/core/modules/file/interfaces/file-service-options.interface.d.ts +7 -0
- package/dist/core/modules/file/interfaces/file-service-options.interface.js +3 -0
- package/dist/core/modules/file/interfaces/file-service-options.interface.js.map +1 -0
- package/dist/core/modules/file/interfaces/file-upload.interface.d.ts +13 -0
- package/dist/core/modules/file/interfaces/file-upload.interface.js +3 -0
- package/dist/core/modules/file/interfaces/file-upload.interface.js.map +1 -0
- package/dist/core/modules/user/core-user.service.js +7 -3
- package/dist/core/modules/user/core-user.service.js.map +1 -1
- package/dist/core.module.js +34 -39
- package/dist/core.module.js.map +1 -1
- package/dist/server/modules/auth/auth.module.js +4 -1
- package/dist/server/modules/auth/auth.module.js.map +1 -1
- package/dist/server/modules/file/file.controller.d.ts +6 -1
- package/dist/server/modules/file/file.controller.js +33 -3
- package/dist/server/modules/file/file.controller.js.map +1 -1
- package/dist/server/modules/file/file.resolver.d.ts +8 -2
- package/dist/server/modules/file/file.resolver.js +47 -11
- package/dist/server/modules/file/file.resolver.js.map +1 -1
- package/dist/server/modules/file/file.service.d.ts +6 -0
- package/dist/server/modules/file/file.service.js +32 -0
- package/dist/server/modules/file/file.service.js.map +1 -0
- package/dist/server/modules/user/user.model.d.ts +15 -1
- package/dist/server/modules/user/user.model.js +5 -0
- package/dist/server/modules/user/user.model.js.map +1 -1
- package/dist/server/modules/user/user.resolver.js +1 -2
- package/dist/server/modules/user/user.resolver.js.map +1 -1
- package/dist/server/modules/user/user.service.js +2 -2
- package/dist/server/modules/user/user.service.js.map +1 -1
- package/dist/server/server.module.js +2 -1
- package/dist/server/server.module.js.map +1 -1
- package/dist/test/test.helper.d.ts +21 -1
- package/dist/test/test.helper.js +99 -12
- package/dist/test/test.helper.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +31 -29
- package/src/core/common/helpers/db.helper.ts +9 -2
- package/src/core/common/helpers/filter.helper.ts +7 -1
- package/src/core/common/helpers/model.helper.ts +152 -0
- package/src/core/common/helpers/service.helper.ts +6 -3
- package/src/core/common/inputs/single-filter.input.ts +10 -1
- package/src/core/common/interfaces/prepare-input-options.interface.ts +1 -0
- package/src/core/common/interfaces/prepare-output-options.interface.ts +1 -0
- package/src/core/common/interfaces/service-options.interface.ts +8 -2
- package/src/core/common/services/mailjet.service.ts +1 -1
- package/src/core/common/services/module.service.ts +18 -1
- package/src/core/common/types/field-selection.type.ts +1 -1
- package/src/core/modules/file/core-file.service.ts +179 -0
- package/src/core/modules/file/file-info.output.ts +26 -0
- package/src/core/modules/file/interfaces/file-service-options.interface.ts +7 -0
- package/src/core/modules/file/interfaces/file-upload.interface.ts +38 -0
- package/src/core.module.ts +1 -1
- package/src/server/modules/file/file.controller.ts +42 -3
- package/src/server/modules/file/file.resolver.ts +58 -33
- package/src/server/modules/file/file.service.ts +11 -0
- package/src/server/modules/user/user.model.ts +9 -0
- package/src/server/server.module.ts +2 -1
- package/src/test/test.helper.ts +122 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lenne.tech/nest-server",
|
|
3
|
-
"version": "8.6.
|
|
3
|
+
"version": "8.6.9",
|
|
4
4
|
"description": "Modern, fast, powerful Node.js web framework in TypeScript based on Nest with a GraphQL API and a connection to MongoDB (or other databases).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"node",
|
|
@@ -26,8 +26,9 @@
|
|
|
26
26
|
"reinit": "rimraf package-lock.json && rimraf node_modules && npm cache clean --force && npm i && npm run test:e2e && npm run build",
|
|
27
27
|
"reinit:force": "rimraf package-lock.json && rimraf node_modules && npm cache clean --force && npm i --force && npm run test:e2e",
|
|
28
28
|
"reinit:legacy": "rimraf package-lock.json && rimraf node_modules && npm cache clean --force && npm i --legacy-peer-deps && npm run test:e2e",
|
|
29
|
-
"start": "
|
|
29
|
+
"start": "npm run start:dev",
|
|
30
30
|
"stop": "./node_modules/.bin/pm2 delete nest",
|
|
31
|
+
"start:pm2": "./node_modules/.bin/grunt",
|
|
31
32
|
"start:prod": "./node_modules/.bin/grunt productive",
|
|
32
33
|
"start:nodemon": "ts-node -r tsconfig-paths/register src/main.ts",
|
|
33
34
|
"start:debug": "nodemon --config nodemon-debug.json",
|
|
@@ -56,54 +57,55 @@
|
|
|
56
57
|
"node": ">= 16.13.0"
|
|
57
58
|
},
|
|
58
59
|
"dependencies": {
|
|
59
|
-
"@apollo/gateway": "0.
|
|
60
|
-
"@nestjs/apollo": "10.0.
|
|
61
|
-
"@nestjs/common": "8.4.
|
|
62
|
-
"@nestjs/core": "8.4.
|
|
63
|
-
"@nestjs/graphql": "10.0.
|
|
60
|
+
"@apollo/gateway": "2.0.5",
|
|
61
|
+
"@nestjs/apollo": "10.0.16",
|
|
62
|
+
"@nestjs/common": "8.4.7",
|
|
63
|
+
"@nestjs/core": "8.4.7",
|
|
64
|
+
"@nestjs/graphql": "10.0.16",
|
|
64
65
|
"@nestjs/jwt": "8.0.1",
|
|
65
|
-
"@nestjs/mongoose": "9.1.
|
|
66
|
-
"@nestjs/passport": "8.2.
|
|
67
|
-
"@nestjs/platform-express": "8.4.
|
|
68
|
-
"apollo-server-core": "3.
|
|
69
|
-
"apollo-server-express": "3.
|
|
66
|
+
"@nestjs/mongoose": "9.1.1",
|
|
67
|
+
"@nestjs/passport": "8.2.2",
|
|
68
|
+
"@nestjs/platform-express": "8.4.7",
|
|
69
|
+
"apollo-server-core": "3.9.0",
|
|
70
|
+
"apollo-server-express": "3.9.0",
|
|
70
71
|
"bcrypt": "5.0.1",
|
|
71
72
|
"class-transformer": "0.5.1",
|
|
72
73
|
"class-validator": "0.13.2",
|
|
73
74
|
"ejs": "3.1.8",
|
|
74
75
|
"graphql": "16.5.0",
|
|
75
76
|
"graphql-subscriptions": "2.0.0",
|
|
76
|
-
"graphql-upload": "
|
|
77
|
+
"graphql-upload": "15.0.1",
|
|
77
78
|
"json-to-graphql-query": "2.2.4",
|
|
78
79
|
"light-my-request": "5.0.0",
|
|
79
80
|
"lodash": "4.17.21",
|
|
80
|
-
"mongodb": "4.
|
|
81
|
-
"mongoose": "6.3.
|
|
81
|
+
"mongodb": "4.7.0",
|
|
82
|
+
"mongoose": "6.3.9",
|
|
83
|
+
"mongoose-gridfs": "1.3.0",
|
|
82
84
|
"multer": "1.4.4",
|
|
83
85
|
"node-mailjet": "3.4.1",
|
|
84
86
|
"nodemailer": "6.7.5",
|
|
85
87
|
"nodemon": "2.0.16",
|
|
86
|
-
"passport": "0.
|
|
88
|
+
"passport": "0.6.0",
|
|
87
89
|
"passport-jwt": "4.0.0",
|
|
88
90
|
"reflect-metadata": "0.1.13",
|
|
89
91
|
"rimraf": "3.0.2",
|
|
90
92
|
"rxjs": "7.5.5"
|
|
91
93
|
},
|
|
92
94
|
"devDependencies": {
|
|
93
|
-
"@nestjs/testing": "8.4.
|
|
95
|
+
"@nestjs/testing": "8.4.7",
|
|
94
96
|
"@types/ejs": "3.1.1",
|
|
95
|
-
"@types/jest": "
|
|
97
|
+
"@types/jest": "28.1.3",
|
|
96
98
|
"@types/lodash": "4.14.182",
|
|
97
99
|
"@types/multer": "1.4.7",
|
|
98
|
-
"@types/node": "
|
|
100
|
+
"@types/node": "18.0.0",
|
|
99
101
|
"@types/node-mailjet": "3.3.9",
|
|
100
102
|
"@types/nodemailer": "6.4.4",
|
|
101
|
-
"@types/passport": "1.0.
|
|
103
|
+
"@types/passport": "1.0.9",
|
|
102
104
|
"@types/supertest": "2.0.12",
|
|
103
|
-
"@typescript-eslint/eslint-plugin": "5.
|
|
104
|
-
"@typescript-eslint/parser": "5.
|
|
105
|
+
"@typescript-eslint/eslint-plugin": "5.29.0",
|
|
106
|
+
"@typescript-eslint/parser": "5.29.0",
|
|
105
107
|
"coffeescript": "2.7.0",
|
|
106
|
-
"eslint": "8.
|
|
108
|
+
"eslint": "8.18.0",
|
|
107
109
|
"eslint-config-prettier": "8.5.0",
|
|
108
110
|
"find-file-up": "2.0.1",
|
|
109
111
|
"grunt": "1.5.3",
|
|
@@ -112,16 +114,16 @@
|
|
|
112
114
|
"grunt-contrib-watch": "1.1.0",
|
|
113
115
|
"grunt-sync": "0.8.2",
|
|
114
116
|
"husky": "8.0.1",
|
|
115
|
-
"jest": "28.1.
|
|
117
|
+
"jest": "28.1.1",
|
|
116
118
|
"pm2": "5.2.0",
|
|
117
|
-
"prettier": "2.
|
|
119
|
+
"prettier": "2.7.1",
|
|
118
120
|
"pretty-quick": "3.1.3",
|
|
119
121
|
"supertest": "6.2.3",
|
|
120
|
-
"ts-jest": "28.0.
|
|
121
|
-
"ts-morph": "
|
|
122
|
-
"ts-node": "10.8.
|
|
122
|
+
"ts-jest": "28.0.5",
|
|
123
|
+
"ts-morph": "15.1.0",
|
|
124
|
+
"ts-node": "10.8.1",
|
|
123
125
|
"tsconfig-paths": "4.0.0",
|
|
124
|
-
"typescript": "4.
|
|
126
|
+
"typescript": "4.7.4"
|
|
125
127
|
},
|
|
126
128
|
"jest": {
|
|
127
129
|
"collectCoverage": true,
|
|
@@ -423,7 +423,9 @@ export function removeUnresolvedReferences<T = any>(
|
|
|
423
423
|
if (typeof populated === 'object') {
|
|
424
424
|
// populatedOptions is an array
|
|
425
425
|
if (Array.isArray(populatedOptions)) {
|
|
426
|
-
populatedOptions.forEach((po) =>
|
|
426
|
+
populatedOptions.forEach((po) =>
|
|
427
|
+
removeUnresolvedReferences(populated, ignoreFirst && typeof po === 'object' ? po.populate : po, false)
|
|
428
|
+
);
|
|
427
429
|
return populated;
|
|
428
430
|
}
|
|
429
431
|
|
|
@@ -462,12 +464,17 @@ export async function popAndMap<T extends CoreModel>(
|
|
|
462
464
|
let result;
|
|
463
465
|
let populateOptions: PopulateOptions[] = [];
|
|
464
466
|
if (populate) {
|
|
465
|
-
if (
|
|
467
|
+
if (
|
|
468
|
+
Array.isArray(populate) &&
|
|
469
|
+
(typeof (populate as string[])[0] === 'string' || typeof (populate as PopulateOptions[])[0]?.path === 'string')
|
|
470
|
+
) {
|
|
466
471
|
populateOptions = populate as PopulateOptions[];
|
|
467
472
|
} else if (Array.isArray(populate) && typeof (populate as SelectionNode[])[0]?.kind === 'string') {
|
|
468
473
|
populateOptions = getPopulatOptionsFromSelections(populate as SelectionNode[]);
|
|
469
474
|
} else if ((populate as ResolveSelector).info) {
|
|
470
475
|
populateOptions = getPopulateOptions((populate as ResolveSelector).info, (populate as ResolveSelector).select);
|
|
476
|
+
} else if (typeof populate === 'string' || (populate as PopulateOptions).path) {
|
|
477
|
+
populateOptions = [populate as PopulateOptions];
|
|
471
478
|
}
|
|
472
479
|
}
|
|
473
480
|
if (queryOrDocument instanceof Query) {
|
|
@@ -139,7 +139,13 @@ export function generateFilterQuery<T = any>(filter?: Partial<FilterInput>): Fil
|
|
|
139
139
|
// Process single filter
|
|
140
140
|
if (filter.singleFilter) {
|
|
141
141
|
// Init variables
|
|
142
|
-
const { not, options, field,
|
|
142
|
+
const { not, options, field, convertToObjectId } = filter.singleFilter;
|
|
143
|
+
let value = filter.singleFilter.value;
|
|
144
|
+
|
|
145
|
+
// Convert value to object ID(s)
|
|
146
|
+
if (convertToObjectId) {
|
|
147
|
+
value = getObjectIds(value);
|
|
148
|
+
}
|
|
143
149
|
|
|
144
150
|
// Convert filter
|
|
145
151
|
switch (filter.singleFilter.operator) {
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { plainToInstance } from 'class-transformer';
|
|
1
2
|
import * as _ from 'lodash';
|
|
3
|
+
import { Types } from 'mongoose';
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
6
|
* Helper class for models
|
|
@@ -150,3 +152,153 @@ export function maps<T = Record<string, any>>(
|
|
|
150
152
|
return (targetClass as any).map(item, { cloneDeep });
|
|
151
153
|
});
|
|
152
154
|
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* It takes an object, a mapping of properties to classes, and returns a new object with the properties mapped to instances
|
|
158
|
+
* of the classes
|
|
159
|
+
* @param input - The input object to map
|
|
160
|
+
* @param mapping - A mapping of property names to classes
|
|
161
|
+
* @param [target] - The object to map the input to. If not provided, a new object will be created
|
|
162
|
+
* @returns Record with mapped objects
|
|
163
|
+
*/
|
|
164
|
+
export function mapClasses<T = Record<string, any>>(
|
|
165
|
+
input: Record<string, any>,
|
|
166
|
+
mapping: Record<string, new (...args: any[]) => any>,
|
|
167
|
+
target?: T
|
|
168
|
+
): T {
|
|
169
|
+
// Check params
|
|
170
|
+
if (!target) {
|
|
171
|
+
target = {} as T;
|
|
172
|
+
}
|
|
173
|
+
if (!input || !mapping) {
|
|
174
|
+
return target;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Process input
|
|
178
|
+
for (const [prop, value] of Object.entries(input)) {
|
|
179
|
+
if (prop in mapping) {
|
|
180
|
+
const targetClass = mapping[prop] as any;
|
|
181
|
+
|
|
182
|
+
// Process array
|
|
183
|
+
if (Array.isArray(value)) {
|
|
184
|
+
const arr = [];
|
|
185
|
+
for (const item of value) {
|
|
186
|
+
if (value instanceof targetClass) {
|
|
187
|
+
arr.push(value);
|
|
188
|
+
}
|
|
189
|
+
if (value instanceof Types.ObjectId) {
|
|
190
|
+
arr.push(value);
|
|
191
|
+
} else if (typeof value === 'object') {
|
|
192
|
+
if (targetClass.map) {
|
|
193
|
+
arr.push(targetClass.map(item));
|
|
194
|
+
} else if (typeof value === 'object') {
|
|
195
|
+
arr.push(plainToInstance(targetClass, item));
|
|
196
|
+
}
|
|
197
|
+
} else {
|
|
198
|
+
arr.push(value);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
target[prop] = arr as any;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Process ObjectId
|
|
205
|
+
else if (value instanceof Types.ObjectId) {
|
|
206
|
+
target[prop] = value as any;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Process object
|
|
210
|
+
else if (typeof value === 'object') {
|
|
211
|
+
if (value instanceof targetClass) {
|
|
212
|
+
target[prop] = value as any;
|
|
213
|
+
}
|
|
214
|
+
if (targetClass.map) {
|
|
215
|
+
target[prop] = targetClass.map(value);
|
|
216
|
+
} else {
|
|
217
|
+
target[prop] = plainToInstance(targetClass, value) as any;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Others
|
|
222
|
+
else {
|
|
223
|
+
target[prop] = value;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return target;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* It takes an object, a mapping of properties to classes, and returns a new object with the properties mapped to instances
|
|
233
|
+
* of the classes async
|
|
234
|
+
* @param input - The input object to map
|
|
235
|
+
* @param mapping - A mapping of property names to classes
|
|
236
|
+
* @param [target] - The object to map the input to. If not provided, a new object will be created
|
|
237
|
+
* @returns Record with mapped objects
|
|
238
|
+
*/
|
|
239
|
+
export async function mapClassesAsync<T = Record<string, any>>(
|
|
240
|
+
input: Record<string, any>,
|
|
241
|
+
mapping: Record<string, new (...args: any[]) => any>,
|
|
242
|
+
target?: T
|
|
243
|
+
): Promise<T> {
|
|
244
|
+
// Check params
|
|
245
|
+
if (!target) {
|
|
246
|
+
target = {} as T;
|
|
247
|
+
}
|
|
248
|
+
if (!input || !mapping) {
|
|
249
|
+
return target;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Process input
|
|
253
|
+
for (const [prop, value] of Object.entries(input)) {
|
|
254
|
+
if (prop in mapping) {
|
|
255
|
+
const targetClass = mapping[prop] as any;
|
|
256
|
+
|
|
257
|
+
// Process array
|
|
258
|
+
if (Array.isArray(value)) {
|
|
259
|
+
const arr = [];
|
|
260
|
+
for (const item of value) {
|
|
261
|
+
if (value instanceof targetClass) {
|
|
262
|
+
arr.push(value);
|
|
263
|
+
}
|
|
264
|
+
if (value instanceof Types.ObjectId) {
|
|
265
|
+
arr.push(value);
|
|
266
|
+
} else if (typeof value === 'object') {
|
|
267
|
+
if (targetClass.map) {
|
|
268
|
+
arr.push(await targetClass.map(item));
|
|
269
|
+
} else if (typeof value === 'object') {
|
|
270
|
+
arr.push(plainToInstance(targetClass, item));
|
|
271
|
+
}
|
|
272
|
+
} else {
|
|
273
|
+
arr.push(value);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
target[prop] = arr as any;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Process ObjectId
|
|
280
|
+
else if (value instanceof Types.ObjectId) {
|
|
281
|
+
target[prop] = value as any;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Process object
|
|
285
|
+
else if (typeof value === 'object') {
|
|
286
|
+
if (value instanceof targetClass) {
|
|
287
|
+
target[prop] = value as any;
|
|
288
|
+
}
|
|
289
|
+
if (targetClass.map) {
|
|
290
|
+
target[prop] = await targetClass.map(value);
|
|
291
|
+
} else {
|
|
292
|
+
target[prop] = plainToInstance(targetClass, value) as any;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Others
|
|
297
|
+
else {
|
|
298
|
+
target[prop] = value;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return target;
|
|
304
|
+
}
|
|
@@ -55,6 +55,7 @@ export async function prepareInput<T = any>(
|
|
|
55
55
|
currentUser: { [key: string]: any; id: string },
|
|
56
56
|
options: {
|
|
57
57
|
[key: string]: any;
|
|
58
|
+
checkRoles?: boolean;
|
|
58
59
|
create?: boolean;
|
|
59
60
|
clone?: boolean;
|
|
60
61
|
getNewArray?: boolean;
|
|
@@ -148,6 +149,7 @@ export async function prepareOutput<T = { [key: string]: any; map: (...args: any
|
|
|
148
149
|
[key: string]: any;
|
|
149
150
|
clone?: boolean;
|
|
150
151
|
getNewArray?: boolean;
|
|
152
|
+
removeSecrets?: boolean;
|
|
151
153
|
removeUndefined?: boolean;
|
|
152
154
|
targetModel?: new (...args: any[]) => T;
|
|
153
155
|
} = {}
|
|
@@ -156,6 +158,7 @@ export async function prepareOutput<T = { [key: string]: any; map: (...args: any
|
|
|
156
158
|
const config = {
|
|
157
159
|
clone: false,
|
|
158
160
|
getNewArray: false,
|
|
161
|
+
removeSecrets: true,
|
|
159
162
|
removeUndefined: false,
|
|
160
163
|
targetModel: undefined,
|
|
161
164
|
...options,
|
|
@@ -191,17 +194,17 @@ export async function prepareOutput<T = { [key: string]: any; map: (...args: any
|
|
|
191
194
|
}
|
|
192
195
|
|
|
193
196
|
// Remove password if exists
|
|
194
|
-
if (output.password) {
|
|
197
|
+
if (config.removeSecrets && output.password) {
|
|
195
198
|
output.password = undefined;
|
|
196
199
|
}
|
|
197
200
|
|
|
198
201
|
// Remove verification token if exists
|
|
199
|
-
if (output.verificationToken) {
|
|
202
|
+
if (config.removeSecrets && output.verificationToken) {
|
|
200
203
|
output.verificationToken = undefined;
|
|
201
204
|
}
|
|
202
205
|
|
|
203
206
|
// Remove password reset token if exists
|
|
204
|
-
if (output.passwordResetToken) {
|
|
207
|
+
if (config.removeSecrets && output.passwordResetToken) {
|
|
205
208
|
output.passwordResetToken = undefined;
|
|
206
209
|
}
|
|
207
210
|
|
|
@@ -9,7 +9,16 @@ import { CoreInput } from './core-input.input';
|
|
|
9
9
|
@InputType({ description: 'Input for a configuration of a filter' })
|
|
10
10
|
export class SingleFilterInput extends CoreInput {
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
12
|
+
* Convert value to ObjectId
|
|
13
|
+
*/
|
|
14
|
+
@Field({
|
|
15
|
+
description: 'Convert value to ObjectId',
|
|
16
|
+
nullable: true,
|
|
17
|
+
})
|
|
18
|
+
convertToObjectId?: boolean = undefined;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Name of the property to be used for the filter
|
|
13
22
|
*/
|
|
14
23
|
@Field({ description: 'Name of the property to be used for the filter' })
|
|
15
24
|
field: string = undefined;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Model } from 'mongoose';
|
|
1
|
+
import { Model, PopulateOptions } from 'mongoose';
|
|
2
2
|
import { FieldSelection } from '../types/field-selection.type';
|
|
3
3
|
import { PrepareInputOptions } from './prepare-input-options.interface';
|
|
4
4
|
import { PrepareOutputOptions } from './prepare-output-options.interface';
|
|
@@ -22,15 +22,21 @@ export interface ServiceOptions {
|
|
|
22
22
|
roles?: string[];
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
-
// Field selection for results
|
|
25
|
+
// Field selection for results (will be overwritten by populate if populate was set)
|
|
26
26
|
fieldSelection?: FieldSelection;
|
|
27
27
|
|
|
28
|
+
// Determines whether all restrictions are ignored
|
|
29
|
+
force?: boolean;
|
|
30
|
+
|
|
28
31
|
// Overwrites type of input (array items)
|
|
29
32
|
inputType?: new (...params: any[]) => any;
|
|
30
33
|
|
|
31
34
|
// Overwrites type of output (array items)
|
|
32
35
|
outputType?: new (...params: any[]) => any;
|
|
33
36
|
|
|
37
|
+
// Alias for fieldSelection (if both are set fieldSelection is overwritten by populate)
|
|
38
|
+
populate?: PopulateOptions | (PopulateOptions | string)[];
|
|
39
|
+
|
|
34
40
|
// Process field selection
|
|
35
41
|
// If {} or not set, then the field selection runs with defaults
|
|
36
42
|
// If falsy, then the field selection will not be automatically executed
|
|
@@ -71,9 +71,10 @@ export abstract class ModuleService<T extends CoreModel = any> {
|
|
|
71
71
|
}
|
|
72
72
|
) {
|
|
73
73
|
// Configuration with default values
|
|
74
|
-
const config = {
|
|
74
|
+
const config: { dbObject: string | Types.ObjectId | any; input: any } & ServiceOptions = {
|
|
75
75
|
checkRights: true,
|
|
76
76
|
dbObject: options?.dbObject,
|
|
77
|
+
force: false,
|
|
77
78
|
input: options?.input,
|
|
78
79
|
processFieldSelection: {},
|
|
79
80
|
prepareInput: {},
|
|
@@ -82,6 +83,22 @@ export abstract class ModuleService<T extends CoreModel = any> {
|
|
|
82
83
|
...options?.serviceOptions,
|
|
83
84
|
};
|
|
84
85
|
|
|
86
|
+
// Note force configuration
|
|
87
|
+
if (config.force) {
|
|
88
|
+
config.checkRights = false;
|
|
89
|
+
if (typeof config.prepareInput === 'object') {
|
|
90
|
+
config.prepareInput.checkRoles = false;
|
|
91
|
+
}
|
|
92
|
+
if (typeof config.prepareOutput === 'object') {
|
|
93
|
+
config.prepareOutput.removeSecrets = false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Note populate
|
|
98
|
+
if (config.populate) {
|
|
99
|
+
config.fieldSelection = config.populate;
|
|
100
|
+
}
|
|
101
|
+
|
|
85
102
|
// Prepare input
|
|
86
103
|
if (config.prepareInput && this.prepareInput) {
|
|
87
104
|
const opts = config.prepareInput;
|
|
@@ -5,4 +5,4 @@ import { ResolveSelector } from '../interfaces/resolve-selector.interface';
|
|
|
5
5
|
/**
|
|
6
6
|
* Field selection to set fields of (populated) result
|
|
7
7
|
*/
|
|
8
|
-
export type FieldSelection = PopulateOptions[] | SelectionNode[] | ResolveSelector;
|
|
8
|
+
export type FieldSelection = PopulateOptions | (PopulateOptions | string)[] | SelectionNode[] | ResolveSelector;
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { NotFoundException } from '@nestjs/common';
|
|
2
|
+
import { GridFSBucket, GridFSBucketReadStreamOptions } from 'mongodb';
|
|
3
|
+
import { Connection, Types } from 'mongoose';
|
|
4
|
+
import { createBucket, MongoGridFSOptions, MongooseGridFS } from 'mongoose-gridfs';
|
|
5
|
+
import { FilterArgs } from '../../common/args/filter.args';
|
|
6
|
+
import { getObjectIds, getStringIds } from '../../common/helpers/db.helper';
|
|
7
|
+
import { convertFilterArgsToQuery } from '../../common/helpers/filter.helper';
|
|
8
|
+
import { check } from '../../common/helpers/input.helper';
|
|
9
|
+
import { prepareOutput } from '../../common/helpers/service.helper';
|
|
10
|
+
import { FileInfo } from './file-info.output';
|
|
11
|
+
import { FileServiceOptions } from './interfaces/file-service-options.interface';
|
|
12
|
+
import { FileUpload } from './interfaces/file-upload.interface';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Abstract core file service
|
|
16
|
+
*/
|
|
17
|
+
export abstract class CoreFileService {
|
|
18
|
+
files: GridFSBucket & MongooseGridFS;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Include MongoDB connection and create File bucket
|
|
22
|
+
*/
|
|
23
|
+
protected constructor(protected readonly connection: Connection, modelName = 'File') {
|
|
24
|
+
this.files = createBucket({ modelName, connection });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Save file in DB
|
|
29
|
+
*/
|
|
30
|
+
createFile(file: FileUpload, serviceOptions?: FileServiceOptions): Promise<FileInfo> {
|
|
31
|
+
return new Promise(async (resolve, reject) => {
|
|
32
|
+
const { filename, mimetype, encoding, createReadStream } = file;
|
|
33
|
+
const readStream = createReadStream();
|
|
34
|
+
const options: MongoGridFSOptions = { filename, contentType: mimetype };
|
|
35
|
+
this.files.writeFile(options, readStream, (error, fileInfo) => {
|
|
36
|
+
error ? reject(error) : resolve(this.prepareOutput(fileInfo, serviceOptions));
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Save files in DB
|
|
43
|
+
*/
|
|
44
|
+
async createFiles(files: FileUpload[], serviceOptions?: FileServiceOptions): Promise<FileInfo[]> {
|
|
45
|
+
const promises: Promise<FileInfo>[] = [];
|
|
46
|
+
for (const file of files) {
|
|
47
|
+
promises.push(this.createFile(file, serviceOptions));
|
|
48
|
+
}
|
|
49
|
+
return await Promise.all(promises);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Get file infos via filter
|
|
54
|
+
*/
|
|
55
|
+
findFileInfo(filterArgs?: FilterArgs, serviceOptions?: FileServiceOptions): Promise<FileInfo[]> {
|
|
56
|
+
return new Promise((resolve, reject) => {
|
|
57
|
+
const filterQuery = convertFilterArgsToQuery(filterArgs);
|
|
58
|
+
const cursor = this.files.find(filterQuery[0], filterQuery[1]);
|
|
59
|
+
if (!cursor) {
|
|
60
|
+
throw new Error('File collection not found');
|
|
61
|
+
}
|
|
62
|
+
cursor.toArray((error, docs) => {
|
|
63
|
+
error ? reject(error) : resolve(this.prepareOutput(docs, serviceOptions));
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Get info about file via file ID
|
|
70
|
+
*/
|
|
71
|
+
getFileInfo(id: string | Types.ObjectId, serviceOptions?: FileServiceOptions): Promise<FileInfo> {
|
|
72
|
+
return new Promise((resolve, reject) => {
|
|
73
|
+
this.files.findById(getObjectIds(id), (error, fileInfo) => {
|
|
74
|
+
error ? reject(error) : resolve(this.prepareOutput(fileInfo, serviceOptions));
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Get info about file via filename
|
|
81
|
+
*/
|
|
82
|
+
getFileInfoByName(filename: string, serviceOptions?: FileServiceOptions): Promise<FileInfo> {
|
|
83
|
+
return new Promise((resolve, reject) => {
|
|
84
|
+
this.files.findOne({ filename }, (error, fileInfo) => {
|
|
85
|
+
error ? reject(error) : resolve(this.prepareOutput(fileInfo, serviceOptions));
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Get file stream (for big files) via file ID
|
|
92
|
+
*/
|
|
93
|
+
getFileStream(id: string | Types.ObjectId, options?: GridFSBucketReadStreamOptions) {
|
|
94
|
+
return this.files.openDownloadStream(getObjectIds(id), options);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Get file stream (for big files) via filename
|
|
99
|
+
*/
|
|
100
|
+
getFileStreamByName(filename: string): GridFSBucketReadStreamOptions {
|
|
101
|
+
return this.files.readFile({ filename });
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Get file buffer (for small files) via file ID
|
|
106
|
+
*/
|
|
107
|
+
getBuffer(id: string | Types.ObjectId): Promise<Buffer> {
|
|
108
|
+
return new Promise((resolve, reject) => {
|
|
109
|
+
this.files.readFile({ _id: getObjectIds(id) }, (error, buffer) => {
|
|
110
|
+
error ? reject(error) : resolve(buffer);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Get file buffer (for small files) via file ID
|
|
117
|
+
*/
|
|
118
|
+
getBufferByName(filename: string): Promise<Buffer> {
|
|
119
|
+
return new Promise((resolve, reject) => {
|
|
120
|
+
this.files.readFile({ filename }, (error, buffer) => {
|
|
121
|
+
error ? reject(error) : resolve(buffer);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Delete file reference of avatar
|
|
128
|
+
*/
|
|
129
|
+
deleteFile(id: string | Types.ObjectId, serviceOptions?: FileServiceOptions): Promise<FileInfo> {
|
|
130
|
+
return new Promise((resolve, reject) => {
|
|
131
|
+
return this.files.unlink(getObjectIds(id), (error, fileInfo) => {
|
|
132
|
+
error ? reject(error) : resolve(this.prepareOutput(fileInfo, serviceOptions));
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Delete file reference of avatar
|
|
139
|
+
*/
|
|
140
|
+
async deleteFileByName(filename: string, serviceOptions?: FileServiceOptions): Promise<FileInfo> {
|
|
141
|
+
const fileInfo = await this.getFileInfoByName(filename);
|
|
142
|
+
if (!fileInfo) {
|
|
143
|
+
throw new NotFoundException('File not found with filename ' + filename);
|
|
144
|
+
}
|
|
145
|
+
return await this.deleteFile(fileInfo.id, serviceOptions);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ===================================================================================================================
|
|
149
|
+
// Helper methods
|
|
150
|
+
// ===================================================================================================================
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Prepare output before return
|
|
154
|
+
*/
|
|
155
|
+
protected async prepareOutput(fileInfo: FileInfo | FileInfo[], options?: FileServiceOptions) {
|
|
156
|
+
if (!fileInfo) {
|
|
157
|
+
return fileInfo;
|
|
158
|
+
}
|
|
159
|
+
this.setId(fileInfo);
|
|
160
|
+
fileInfo = await prepareOutput(fileInfo, { targetModel: FileInfo });
|
|
161
|
+
return check(fileInfo, options?.currentUser, { roles: options?.roles });
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Set file info ID via _id
|
|
166
|
+
*/
|
|
167
|
+
protected setId(fileInfo: FileInfo | FileInfo[]) {
|
|
168
|
+
if (Array.isArray(fileInfo)) {
|
|
169
|
+
fileInfo.forEach((item) => {
|
|
170
|
+
if (typeof item === 'object') {
|
|
171
|
+
item.id = getStringIds(item._id);
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
} else if (typeof fileInfo === 'object') {
|
|
175
|
+
fileInfo.id = getStringIds(fileInfo._id);
|
|
176
|
+
}
|
|
177
|
+
return fileInfo;
|
|
178
|
+
}
|
|
179
|
+
}
|