@lenne.tech/nest-server 8.6.8 → 8.6.11

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 (127) 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 +32 -21
  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.js +27 -13
  21. package/dist/core/common/helpers/service.helper.js.map +1 -1
  22. package/dist/core/common/inputs/core-input.input.js +6 -1
  23. package/dist/core/common/inputs/core-input.input.js.map +1 -1
  24. package/dist/core/common/inputs/single-filter.input.d.ts +1 -0
  25. package/dist/core/common/inputs/single-filter.input.js +8 -0
  26. package/dist/core/common/inputs/single-filter.input.js.map +1 -1
  27. package/dist/core/common/models/core-model.model.js +14 -2
  28. package/dist/core/common/models/core-model.model.js.map +1 -1
  29. package/dist/core/common/pipes/check-input.pipe.js +1 -1
  30. package/dist/core/common/pipes/check-input.pipe.js.map +1 -1
  31. package/dist/core/common/pipes/map-and-validate.pipe.js +2 -2
  32. package/dist/core/common/pipes/map-and-validate.pipe.js.map +1 -1
  33. package/dist/core/common/services/crud.service.js +3 -5
  34. package/dist/core/common/services/crud.service.js.map +1 -1
  35. package/dist/core/common/services/email.service.js +5 -1
  36. package/dist/core/common/services/email.service.js.map +1 -1
  37. package/dist/core/common/services/mailjet.service.js +8 -1
  38. package/dist/core/common/services/mailjet.service.js.map +1 -1
  39. package/dist/core/common/services/module.service.js +29 -7
  40. package/dist/core/common/services/module.service.js.map +1 -1
  41. package/dist/core/modules/auth/core-auth.model.d.ts +1 -0
  42. package/dist/core/modules/auth/core-auth.model.js +4 -0
  43. package/dist/core/modules/auth/core-auth.model.js.map +1 -1
  44. package/dist/core/modules/auth/core-auth.module.js +2 -2
  45. package/dist/core/modules/auth/core-auth.module.js.map +1 -1
  46. package/dist/core/modules/auth/guards/auth.guard.js +4 -5
  47. package/dist/core/modules/auth/guards/auth.guard.js.map +1 -1
  48. package/dist/core/modules/auth/guards/roles.guard.js +1 -2
  49. package/dist/core/modules/auth/guards/roles.guard.js.map +1 -1
  50. package/dist/core/modules/file/core-file.service.d.ts +26 -0
  51. package/dist/core/modules/file/core-file.service.js +116 -0
  52. package/dist/core/modules/file/core-file.service.js.map +1 -0
  53. package/dist/core/modules/file/file-info.output.d.ts +10 -0
  54. package/dist/core/modules/file/file-info.output.js +49 -0
  55. package/dist/core/modules/file/file-info.output.js.map +1 -0
  56. package/dist/core/modules/file/interfaces/file-service-options.interface.d.ts +7 -0
  57. package/dist/core/modules/file/interfaces/file-service-options.interface.js +3 -0
  58. package/dist/core/modules/file/interfaces/file-service-options.interface.js.map +1 -0
  59. package/dist/core/modules/file/interfaces/file-upload.interface.d.ts +13 -0
  60. package/dist/core/modules/file/interfaces/file-upload.interface.js +3 -0
  61. package/dist/core/modules/file/interfaces/file-upload.interface.js.map +1 -0
  62. package/dist/core/modules/user/core-user.model.d.ts +1 -0
  63. package/dist/core/modules/user/core-user.model.js +4 -0
  64. package/dist/core/modules/user/core-user.model.js.map +1 -1
  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 +32 -37
  68. package/dist/core.module.js.map +1 -1
  69. package/dist/index.d.ts +4 -0
  70. package/dist/index.js +4 -0
  71. package/dist/index.js.map +1 -1
  72. package/dist/server/common/models/persistence.model.d.ts +3 -3
  73. package/dist/server/common/models/persistence.model.js +8 -4
  74. package/dist/server/common/models/persistence.model.js.map +1 -1
  75. package/dist/server/modules/auth/auth.model.d.ts +1 -0
  76. package/dist/server/modules/auth/auth.model.js +4 -0
  77. package/dist/server/modules/auth/auth.model.js.map +1 -1
  78. package/dist/server/modules/auth/auth.module.js +4 -1
  79. package/dist/server/modules/auth/auth.module.js.map +1 -1
  80. package/dist/server/modules/file/file.controller.d.ts +6 -1
  81. package/dist/server/modules/file/file.controller.js +34 -4
  82. package/dist/server/modules/file/file.controller.js.map +1 -1
  83. package/dist/server/modules/file/file.resolver.d.ts +8 -2
  84. package/dist/server/modules/file/file.resolver.js +43 -6
  85. package/dist/server/modules/file/file.resolver.js.map +1 -1
  86. package/dist/server/modules/file/file.service.d.ts +6 -0
  87. package/dist/server/modules/file/file.service.js +32 -0
  88. package/dist/server/modules/file/file.service.js.map +1 -0
  89. package/dist/server/modules/user/inputs/user-create.input.js.map +1 -1
  90. package/dist/server/modules/user/inputs/user.input.js.map +1 -1
  91. package/dist/server/modules/user/user.model.d.ts +4 -3
  92. package/dist/server/modules/user/user.model.js +9 -4
  93. package/dist/server/modules/user/user.model.js.map +1 -1
  94. package/dist/server/modules/user/user.resolver.js +1 -2
  95. package/dist/server/modules/user/user.resolver.js.map +1 -1
  96. package/dist/server/modules/user/user.service.js +2 -2
  97. package/dist/server/modules/user/user.service.js.map +1 -1
  98. package/dist/server/server.module.js +2 -1
  99. package/dist/server/server.module.js.map +1 -1
  100. package/dist/test/test.helper.d.ts +24 -1
  101. package/dist/test/test.helper.js +100 -13
  102. package/dist/test/test.helper.js.map +1 -1
  103. package/dist/tsconfig.build.tsbuildinfo +1 -1
  104. package/package.json +8 -7
  105. package/src/core/common/helpers/db.helper.ts +2 -0
  106. package/src/core/common/helpers/filter.helper.ts +7 -1
  107. package/src/core/common/helpers/model.helper.ts +152 -0
  108. package/src/core/common/inputs/single-filter.input.ts +10 -1
  109. package/src/core/common/services/module.service.ts +1 -1
  110. package/src/core/modules/auth/core-auth.model.ts +10 -1
  111. package/src/core/modules/file/core-file.service.ts +179 -0
  112. package/src/core/modules/file/file-info.output.ts +26 -0
  113. package/src/core/modules/file/interfaces/file-service-options.interface.ts +7 -0
  114. package/src/core/modules/file/interfaces/file-upload.interface.ts +38 -0
  115. package/src/core/modules/user/core-user.model.ts +9 -0
  116. package/src/core.module.ts +1 -2
  117. package/src/index.ts +9 -0
  118. package/src/server/common/models/persistence.model.ts +24 -11
  119. package/src/server/modules/auth/auth.model.ts +9 -0
  120. package/src/server/modules/file/file.controller.ts +44 -5
  121. package/src/server/modules/file/file.resolver.ts +57 -33
  122. package/src/server/modules/file/file.service.ts +11 -0
  123. package/src/server/modules/user/inputs/user-create.input.ts +0 -4
  124. package/src/server/modules/user/inputs/user.input.ts +0 -4
  125. package/src/server/modules/user/user.model.ts +17 -8
  126. package/src/server/server.module.ts +2 -1
  127. package/src/test/test.helper.ts +125 -7
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lenne.tech/nest-server",
3
- "version": "8.6.8",
3
+ "version": "8.6.11",
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",
@@ -79,7 +79,8 @@
79
79
  "light-my-request": "5.0.0",
80
80
  "lodash": "4.17.21",
81
81
  "mongodb": "4.7.0",
82
- "mongoose": "6.3.8",
82
+ "mongoose": "6.3.9",
83
+ "mongoose-gridfs": "1.3.0",
83
84
  "multer": "1.4.4",
84
85
  "node-mailjet": "3.4.1",
85
86
  "nodemailer": "6.7.5",
@@ -93,7 +94,7 @@
93
94
  "devDependencies": {
94
95
  "@nestjs/testing": "8.4.7",
95
96
  "@types/ejs": "3.1.1",
96
- "@types/jest": "28.1.1",
97
+ "@types/jest": "28.1.3",
97
98
  "@types/lodash": "4.14.182",
98
99
  "@types/multer": "1.4.7",
99
100
  "@types/node": "18.0.0",
@@ -101,10 +102,10 @@
101
102
  "@types/nodemailer": "6.4.4",
102
103
  "@types/passport": "1.0.9",
103
104
  "@types/supertest": "2.0.12",
104
- "@typescript-eslint/eslint-plugin": "5.28.0",
105
- "@typescript-eslint/parser": "5.28.0",
105
+ "@typescript-eslint/eslint-plugin": "5.29.0",
106
+ "@typescript-eslint/parser": "5.29.0",
106
107
  "coffeescript": "2.7.0",
107
- "eslint": "8.17.0",
108
+ "eslint": "8.18.0",
108
109
  "eslint-config-prettier": "8.5.0",
109
110
  "find-file-up": "2.0.1",
110
111
  "grunt": "1.5.3",
@@ -122,7 +123,7 @@
122
123
  "ts-morph": "15.1.0",
123
124
  "ts-node": "10.8.1",
124
125
  "tsconfig-paths": "4.0.0",
125
- "typescript": "4.7.3"
126
+ "typescript": "4.7.4"
126
127
  },
127
128
  "jest": {
128
129
  "collectCoverage": true,
@@ -473,6 +473,8 @@ export async function popAndMap<T extends CoreModel>(
473
473
  populateOptions = getPopulatOptionsFromSelections(populate as SelectionNode[]);
474
474
  } else if ((populate as ResolveSelector).info) {
475
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];
476
478
  }
477
479
  }
478
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
+ }
@@ -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;
@@ -71,7 +71,7 @@ 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
77
  force: false,
@@ -17,7 +17,7 @@ export class CoreAuthModel extends CoreModel {
17
17
  token: string = undefined;
18
18
 
19
19
  // ===================================================================================================================
20
- // Properties
20
+ // Methods
21
21
  // ===================================================================================================================
22
22
 
23
23
  /**
@@ -28,4 +28,13 @@ export class CoreAuthModel extends CoreModel {
28
28
  // Nothing more to initialize yet
29
29
  return this;
30
30
  }
31
+
32
+ /**
33
+ * Map input
34
+ */
35
+ map(input) {
36
+ super.map(input);
37
+ // There is nothing to map yet, if something comes up you can use `mapClass` / `mapClassAsync` from ModelHelper
38
+ return this;
39
+ }
31
40
  }
@@ -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
+ }
@@ -0,0 +1,26 @@
1
+ import { Field, ObjectType } from '@nestjs/graphql';
2
+ import { Types } from 'mongoose';
3
+ import { CoreModel } from '../../common/models/core-model.model';
4
+
5
+ /**
6
+ * File info (output)
7
+ */
8
+ @ObjectType({ description: 'Information about attachment file' })
9
+ export class FileInfo extends CoreModel {
10
+ _id: Types.ObjectId;
11
+
12
+ @Field(() => String, { description: 'ID of the file in bytes' })
13
+ id: string = undefined;
14
+
15
+ @Field(() => Number, { description: 'Length of the file in bytes', nullable: true })
16
+ length: number = undefined;
17
+
18
+ @Field(() => Number, { description: 'Size of the chunk', nullable: true })
19
+ chunkSize: number = undefined;
20
+
21
+ @Field(() => String, { description: 'Name of the file', nullable: true })
22
+ filename?: string = undefined;
23
+
24
+ @Field(() => String, { description: 'Content type', nullable: true })
25
+ contentType?: string = undefined;
26
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Interface for service options in file services
3
+ */
4
+ export interface FileServiceOptions {
5
+ currentUser?: { id: any; hasRole: (roles: string[]) => boolean };
6
+ roles?: string | string[];
7
+ }
@@ -0,0 +1,38 @@
1
+ import { WriteStream } from 'fs-capacitor';
2
+ import { Readable } from 'stream';
3
+
4
+ /**
5
+ * Interface for file uploads
6
+ */
7
+ export interface FileUpload {
8
+ /**
9
+ * A private implementation detail that shouldn’t be used outside
10
+ */
11
+ capacitor: WriteStream;
12
+
13
+ /**
14
+ * A function that returns a FileUploadCreateReadStream.
15
+ */
16
+ createReadStream: (options?: {
17
+ /** Specify an encoding for the chunks, default: utf8 */
18
+ encoding?: 'utf8' | 'utf8' | 'ucs2' | 'utf16le' | 'latin1' | 'ascii' | 'base64' | 'base64url' | 'hex';
19
+
20
+ /** Maximum number of bytes to store in the internal buffer before ceasing to read from the underlying resource, default: 16384 */
21
+ highWaterMark?: number;
22
+ }) => Readable;
23
+
24
+ /**
25
+ * Stream transfer encoding of the file
26
+ */
27
+ encoding: string;
28
+
29
+ /**
30
+ * Name of the file
31
+ */
32
+ filename: string;
33
+
34
+ /**
35
+ * Mimetype of the file
36
+ */
37
+ mimetype: string;
38
+ }
@@ -122,4 +122,13 @@ export abstract class CoreUserModel extends CorePersistenceModel {
122
122
  this.roles = this.roles === undefined ? [] : this.roles;
123
123
  return this;
124
124
  }
125
+
126
+ /**
127
+ * Map input
128
+ */
129
+ map(input) {
130
+ super.map(input);
131
+ // There is nothing to map yet, if something comes up you can use `mapClass` / `mapClassAsync` from ModelHelper
132
+ return this;
133
+ }
125
134
  }
@@ -79,8 +79,7 @@ export class CoreModule implements NestModule {
79
79
  const { connectionParams, extra } = context;
80
80
  if (config.graphQl.enableSubscriptionAuth) {
81
81
  // get authToken from authorization header
82
- const authToken: string =
83
- 'Authorization' in connectionParams && connectionParams?.Authorization?.split(' ')[1];
82
+ const authToken: string = connectionParams?.Authorization?.split(' ')[1];
84
83
  if (authToken) {
85
84
  // verify authToken/getJwtPayLoad
86
85
  const payload = authService.decodeJwt(authToken);
package/src/index.ts CHANGED
@@ -78,6 +78,15 @@ export * from './core/modules/auth/core-auth.module';
78
78
  export * from './core/modules/auth/core-auth.resolver';
79
79
  export * from './core/modules/auth/jwt.strategy';
80
80
 
81
+ // =====================================================================================================================
82
+ // Core - Modules - File
83
+ // =====================================================================================================================
84
+
85
+ export * from './core/modules/file/interfaces/file-service-options.interface';
86
+ export * from './core/modules/file/interfaces/file-upload.interface';
87
+ export * from './core/modules/file/core-file.service';
88
+ export * from './core/modules/file/file-info.output';
89
+
81
90
  // =====================================================================================================================
82
91
  // Core - Modules - User
83
92
  // =====================================================================================================================
@@ -15,33 +15,37 @@ import { User } from '../../modules/user/user.model';
15
15
  isAbstract: true,
16
16
  })
17
17
  export abstract class PersistenceModel extends CorePersistenceModel {
18
+ // ===================================================================================================================
19
+ // Properties
20
+ // ===================================================================================================================
21
+
18
22
  /**
19
- * User who created the object
23
+ * ID of the user who created the object
20
24
  *
21
25
  * Not set when created by system
22
26
  */
23
- @Field((type) => User, {
24
- description: 'User who created the object',
27
+ @Field(() => User, {
28
+ description: 'ID of the user who created the object',
25
29
  nullable: true,
26
30
  })
27
31
  @Prop({ type: mongoose.Schema.Types.ObjectId, ref: 'User' })
28
- createdBy?: Types.ObjectId | User = undefined;
32
+ createdBy?: Types.ObjectId | string = undefined;
29
33
 
30
34
  /**
31
- * User who last updated the object
35
+ * ID of the user who updated the object
32
36
  *
33
37
  * Not set when updated by system
34
38
  */
35
- @Field((type) => User, {
36
- description: 'User who last updated the object',
39
+ @Field(() => User, {
40
+ description: 'ID of the user who updated the object',
37
41
  nullable: true,
38
42
  })
39
43
  @Prop({ type: mongoose.Schema.Types.ObjectId, ref: 'User' })
40
- updatedBy?: Types.ObjectId | User = undefined;
44
+ updatedBy?: Types.ObjectId | string = undefined;
41
45
 
42
- // ===========================================================================
43
- // Properties
44
- // ===========================================================================
46
+ // ===================================================================================================================
47
+ // Methods
48
+ // ===================================================================================================================
45
49
 
46
50
  /**
47
51
  * Initialize instance with default values instead of undefined
@@ -51,4 +55,13 @@ export abstract class PersistenceModel extends CorePersistenceModel {
51
55
  // Nothing more to initialize yet
52
56
  return this;
53
57
  }
58
+
59
+ /**
60
+ * Map input
61
+ */
62
+ map(input) {
63
+ super.map(input);
64
+ // There is nothing to map yet, if something comes up you can use `mapClass` / `mapClassAsync` from ModelHelper
65
+ return this;
66
+ }
54
67
  }
@@ -29,4 +29,13 @@ export class Auth extends CoreAuthModel {
29
29
  // Nothing more to initialize yet
30
30
  return this;
31
31
  }
32
+
33
+ /**
34
+ * Map input
35
+ */
36
+ map(input) {
37
+ super.map(input);
38
+ // There is nothing to map yet, if something comes up you can use `mapClass` / `mapClassAsync` from ModelHelper
39
+ return this;
40
+ }
32
41
  }