@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.
Files changed (115) hide show
  1. package/dist/core/common/decorators/graphql-user.decorator.js +2 -3
  2. package/dist/core/common/decorators/graphql-user.decorator.js.map +1 -1
  3. package/dist/core/common/decorators/restricted.decorator.js +14 -13
  4. package/dist/core/common/decorators/restricted.decorator.js.map +1 -1
  5. package/dist/core/common/helpers/context.helper.js +4 -5
  6. package/dist/core/common/helpers/context.helper.js.map +1 -1
  7. package/dist/core/common/helpers/db.helper.js +34 -22
  8. package/dist/core/common/helpers/db.helper.js.map +1 -1
  9. package/dist/core/common/helpers/file.helper.js +5 -1
  10. package/dist/core/common/helpers/file.helper.js.map +1 -1
  11. package/dist/core/common/helpers/filter.helper.js +16 -9
  12. package/dist/core/common/helpers/filter.helper.js.map +1 -1
  13. package/dist/core/common/helpers/graphql.helper.js +11 -8
  14. package/dist/core/common/helpers/graphql.helper.js.map +1 -1
  15. package/dist/core/common/helpers/input.helper.js +17 -11
  16. package/dist/core/common/helpers/input.helper.js.map +1 -1
  17. package/dist/core/common/helpers/model.helper.d.ts +2 -0
  18. package/dist/core/common/helpers/model.helper.js +125 -3
  19. package/dist/core/common/helpers/model.helper.js.map +1 -1
  20. package/dist/core/common/helpers/service.helper.d.ts +2 -0
  21. package/dist/core/common/helpers/service.helper.js +30 -16
  22. package/dist/core/common/helpers/service.helper.js.map +1 -1
  23. package/dist/core/common/inputs/core-input.input.js +6 -1
  24. package/dist/core/common/inputs/core-input.input.js.map +1 -1
  25. package/dist/core/common/inputs/single-filter.input.d.ts +1 -0
  26. package/dist/core/common/inputs/single-filter.input.js +8 -0
  27. package/dist/core/common/inputs/single-filter.input.js.map +1 -1
  28. package/dist/core/common/interfaces/prepare-input-options.interface.d.ts +1 -0
  29. package/dist/core/common/interfaces/prepare-output-options.interface.d.ts +1 -0
  30. package/dist/core/common/interfaces/service-options.interface.d.ts +3 -1
  31. package/dist/core/common/models/core-model.model.js +14 -2
  32. package/dist/core/common/models/core-model.model.js.map +1 -1
  33. package/dist/core/common/pipes/check-input.pipe.js +1 -1
  34. package/dist/core/common/pipes/check-input.pipe.js.map +1 -1
  35. package/dist/core/common/pipes/map-and-validate.pipe.js +2 -2
  36. package/dist/core/common/pipes/map-and-validate.pipe.js.map +1 -1
  37. package/dist/core/common/services/crud.service.js +3 -5
  38. package/dist/core/common/services/crud.service.js.map +1 -1
  39. package/dist/core/common/services/email.service.js +5 -1
  40. package/dist/core/common/services/email.service.js.map +1 -1
  41. package/dist/core/common/services/mailjet.service.d.ts +1 -1
  42. package/dist/core/common/services/mailjet.service.js +10 -3
  43. package/dist/core/common/services/mailjet.service.js.map +1 -1
  44. package/dist/core/common/services/module.service.js +41 -7
  45. package/dist/core/common/services/module.service.js.map +1 -1
  46. package/dist/core/common/types/field-selection.type.d.ts +1 -1
  47. package/dist/core/modules/auth/core-auth.module.js +2 -2
  48. package/dist/core/modules/auth/core-auth.module.js.map +1 -1
  49. package/dist/core/modules/auth/guards/auth.guard.js +4 -5
  50. package/dist/core/modules/auth/guards/auth.guard.js.map +1 -1
  51. package/dist/core/modules/auth/guards/roles.guard.js +1 -2
  52. package/dist/core/modules/auth/guards/roles.guard.js.map +1 -1
  53. package/dist/core/modules/file/core-file.service.d.ts +26 -0
  54. package/dist/core/modules/file/core-file.service.js +116 -0
  55. package/dist/core/modules/file/core-file.service.js.map +1 -0
  56. package/dist/core/modules/file/file-info.output.d.ts +10 -0
  57. package/dist/core/modules/file/file-info.output.js +49 -0
  58. package/dist/core/modules/file/file-info.output.js.map +1 -0
  59. package/dist/core/modules/file/interfaces/file-service-options.interface.d.ts +7 -0
  60. package/dist/core/modules/file/interfaces/file-service-options.interface.js +3 -0
  61. package/dist/core/modules/file/interfaces/file-service-options.interface.js.map +1 -0
  62. package/dist/core/modules/file/interfaces/file-upload.interface.d.ts +13 -0
  63. package/dist/core/modules/file/interfaces/file-upload.interface.js +3 -0
  64. package/dist/core/modules/file/interfaces/file-upload.interface.js.map +1 -0
  65. package/dist/core/modules/user/core-user.service.js +7 -3
  66. package/dist/core/modules/user/core-user.service.js.map +1 -1
  67. package/dist/core.module.js +34 -39
  68. package/dist/core.module.js.map +1 -1
  69. package/dist/server/modules/auth/auth.module.js +4 -1
  70. package/dist/server/modules/auth/auth.module.js.map +1 -1
  71. package/dist/server/modules/file/file.controller.d.ts +6 -1
  72. package/dist/server/modules/file/file.controller.js +33 -3
  73. package/dist/server/modules/file/file.controller.js.map +1 -1
  74. package/dist/server/modules/file/file.resolver.d.ts +8 -2
  75. package/dist/server/modules/file/file.resolver.js +47 -11
  76. package/dist/server/modules/file/file.resolver.js.map +1 -1
  77. package/dist/server/modules/file/file.service.d.ts +6 -0
  78. package/dist/server/modules/file/file.service.js +32 -0
  79. package/dist/server/modules/file/file.service.js.map +1 -0
  80. package/dist/server/modules/user/user.model.d.ts +15 -1
  81. package/dist/server/modules/user/user.model.js +5 -0
  82. package/dist/server/modules/user/user.model.js.map +1 -1
  83. package/dist/server/modules/user/user.resolver.js +1 -2
  84. package/dist/server/modules/user/user.resolver.js.map +1 -1
  85. package/dist/server/modules/user/user.service.js +2 -2
  86. package/dist/server/modules/user/user.service.js.map +1 -1
  87. package/dist/server/server.module.js +2 -1
  88. package/dist/server/server.module.js.map +1 -1
  89. package/dist/test/test.helper.d.ts +21 -1
  90. package/dist/test/test.helper.js +99 -12
  91. package/dist/test/test.helper.js.map +1 -1
  92. package/dist/tsconfig.build.tsbuildinfo +1 -1
  93. package/package.json +31 -29
  94. package/src/core/common/helpers/db.helper.ts +9 -2
  95. package/src/core/common/helpers/filter.helper.ts +7 -1
  96. package/src/core/common/helpers/model.helper.ts +152 -0
  97. package/src/core/common/helpers/service.helper.ts +6 -3
  98. package/src/core/common/inputs/single-filter.input.ts +10 -1
  99. package/src/core/common/interfaces/prepare-input-options.interface.ts +1 -0
  100. package/src/core/common/interfaces/prepare-output-options.interface.ts +1 -0
  101. package/src/core/common/interfaces/service-options.interface.ts +8 -2
  102. package/src/core/common/services/mailjet.service.ts +1 -1
  103. package/src/core/common/services/module.service.ts +18 -1
  104. package/src/core/common/types/field-selection.type.ts +1 -1
  105. package/src/core/modules/file/core-file.service.ts +179 -0
  106. package/src/core/modules/file/file-info.output.ts +26 -0
  107. package/src/core/modules/file/interfaces/file-service-options.interface.ts +7 -0
  108. package/src/core/modules/file/interfaces/file-upload.interface.ts +38 -0
  109. package/src/core.module.ts +1 -1
  110. package/src/server/modules/file/file.controller.ts +42 -3
  111. package/src/server/modules/file/file.resolver.ts +58 -33
  112. package/src/server/modules/file/file.service.ts +11 -0
  113. package/src/server/modules/user/user.model.ts +9 -0
  114. package/src/server/server.module.ts +2 -1
  115. 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.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": "./node_modules/.bin/grunt",
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.50.2",
60
- "@nestjs/apollo": "10.0.12",
61
- "@nestjs/common": "8.4.5",
62
- "@nestjs/core": "8.4.5",
63
- "@nestjs/graphql": "10.0.12",
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.0",
66
- "@nestjs/passport": "8.2.1",
67
- "@nestjs/platform-express": "8.4.5",
68
- "apollo-server-core": "3.7.0",
69
- "apollo-server-express": "3.7.0",
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": "13.0.0",
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.6.0",
81
- "mongoose": "6.3.4",
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.5.3",
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.5",
95
+ "@nestjs/testing": "8.4.7",
94
96
  "@types/ejs": "3.1.1",
95
- "@types/jest": "27.5.1",
97
+ "@types/jest": "28.1.3",
96
98
  "@types/lodash": "4.14.182",
97
99
  "@types/multer": "1.4.7",
98
- "@types/node": "16.11.36",
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.7",
103
+ "@types/passport": "1.0.9",
102
104
  "@types/supertest": "2.0.12",
103
- "@typescript-eslint/eslint-plugin": "5.25.0",
104
- "@typescript-eslint/parser": "5.25.0",
105
+ "@typescript-eslint/eslint-plugin": "5.29.0",
106
+ "@typescript-eslint/parser": "5.29.0",
105
107
  "coffeescript": "2.7.0",
106
- "eslint": "8.16.0",
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.0",
117
+ "jest": "28.1.1",
116
118
  "pm2": "5.2.0",
117
- "prettier": "2.6.2",
119
+ "prettier": "2.7.1",
118
120
  "pretty-quick": "3.1.3",
119
121
  "supertest": "6.2.3",
120
- "ts-jest": "28.0.2",
121
- "ts-morph": "14.0.0",
122
- "ts-node": "10.8.0",
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.6.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) => removeUnresolvedReferences(populated, ignoreFirst ? po.populate : po, false));
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 (Array.isArray(populate) && typeof (populate as PopulateOptions[])[0]?.path === 'string') {
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, value } = filter.singleFilter;
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
- * Name of the property to be used for the filter'
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;
@@ -3,6 +3,7 @@
3
3
  */
4
4
  export interface PrepareInputOptions {
5
5
  [key: string]: any;
6
+ checkRoles?: boolean;
6
7
  create?: boolean;
7
8
  clone?: boolean;
8
9
  getNewArray?: boolean;
@@ -5,6 +5,7 @@ export interface PrepareOutputOptions {
5
5
  [key: string]: any;
6
6
  clone?: boolean;
7
7
  getNewArray?: boolean;
8
+ removeSecrets?: boolean;
8
9
  removeUndefined?: boolean;
9
10
  targetModel?: new (...args: any[]) => any;
10
11
  }
@@ -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
@@ -1,6 +1,6 @@
1
1
  import { Injectable } from '@nestjs/common';
2
2
  import { ConfigService } from './config.service';
3
- import * as mailjet from 'node-mailjet';
3
+ import mailjet from 'node-mailjet';
4
4
 
5
5
  /**
6
6
  * Mailjet service
@@ -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
+ }