@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.
- 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 +32 -21
- 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.js +27 -13
- 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/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.js +8 -1
- package/dist/core/common/services/mailjet.service.js.map +1 -1
- package/dist/core/common/services/module.service.js +29 -7
- package/dist/core/common/services/module.service.js.map +1 -1
- package/dist/core/modules/auth/core-auth.model.d.ts +1 -0
- package/dist/core/modules/auth/core-auth.model.js +4 -0
- package/dist/core/modules/auth/core-auth.model.js.map +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.model.d.ts +1 -0
- package/dist/core/modules/user/core-user.model.js +4 -0
- package/dist/core/modules/user/core-user.model.js.map +1 -1
- 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 +32 -37
- package/dist/core.module.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/server/common/models/persistence.model.d.ts +3 -3
- package/dist/server/common/models/persistence.model.js +8 -4
- package/dist/server/common/models/persistence.model.js.map +1 -1
- package/dist/server/modules/auth/auth.model.d.ts +1 -0
- package/dist/server/modules/auth/auth.model.js +4 -0
- package/dist/server/modules/auth/auth.model.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 +34 -4
- 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 +43 -6
- 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/inputs/user-create.input.js.map +1 -1
- package/dist/server/modules/user/inputs/user.input.js.map +1 -1
- package/dist/server/modules/user/user.model.d.ts +4 -3
- package/dist/server/modules/user/user.model.js +9 -4
- 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 +24 -1
- package/dist/test/test.helper.js +100 -13
- package/dist/test/test.helper.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +8 -7
- package/src/core/common/helpers/db.helper.ts +2 -0
- package/src/core/common/helpers/filter.helper.ts +7 -1
- package/src/core/common/helpers/model.helper.ts +152 -0
- package/src/core/common/inputs/single-filter.input.ts +10 -1
- package/src/core/common/services/module.service.ts +1 -1
- package/src/core/modules/auth/core-auth.model.ts +10 -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/modules/user/core-user.model.ts +9 -0
- package/src/core.module.ts +1 -2
- package/src/index.ts +9 -0
- package/src/server/common/models/persistence.model.ts +24 -11
- package/src/server/modules/auth/auth.model.ts +9 -0
- package/src/server/modules/file/file.controller.ts +44 -5
- package/src/server/modules/file/file.resolver.ts +57 -33
- package/src/server/modules/file/file.service.ts +11 -0
- package/src/server/modules/user/inputs/user-create.input.ts +0 -4
- package/src/server/modules/user/inputs/user.input.ts +0 -4
- package/src/server/modules/user/user.model.ts +17 -8
- package/src/server/server.module.ts +2 -1
- 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.
|
|
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.
|
|
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.
|
|
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.
|
|
105
|
-
"@typescript-eslint/parser": "5.
|
|
105
|
+
"@typescript-eslint/eslint-plugin": "5.29.0",
|
|
106
|
+
"@typescript-eslint/parser": "5.29.0",
|
|
106
107
|
"coffeescript": "2.7.0",
|
|
107
|
-
"eslint": "8.
|
|
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.
|
|
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,
|
|
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
|
-
*
|
|
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
|
-
//
|
|
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,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
|
}
|
package/src/core.module.ts
CHANGED
|
@@ -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
|
-
*
|
|
23
|
+
* ID of the user who created the object
|
|
20
24
|
*
|
|
21
25
|
* Not set when created by system
|
|
22
26
|
*/
|
|
23
|
-
@Field((
|
|
24
|
-
description: '
|
|
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 |
|
|
32
|
+
createdBy?: Types.ObjectId | string = undefined;
|
|
29
33
|
|
|
30
34
|
/**
|
|
31
|
-
*
|
|
35
|
+
* ID of the user who updated the object
|
|
32
36
|
*
|
|
33
37
|
* Not set when updated by system
|
|
34
38
|
*/
|
|
35
|
-
@Field((
|
|
36
|
-
description: '
|
|
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 |
|
|
44
|
+
updatedBy?: Types.ObjectId | string = undefined;
|
|
41
45
|
|
|
42
|
-
//
|
|
43
|
-
//
|
|
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
|
}
|