@lenne.tech/nest-server 8.6.10 → 8.6.13

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 (88) hide show
  1. package/dist/core/common/helpers/db.helper.d.ts +1 -1
  2. package/dist/core/common/helpers/db.helper.js +6 -2
  3. package/dist/core/common/helpers/db.helper.js.map +1 -1
  4. package/dist/core/common/interfaces/service-options.interface.d.ts +1 -1
  5. package/dist/core/common/types/field-selection.type.d.ts +1 -1
  6. package/dist/core/common/types/maybe-promise.type.d.ts +1 -0
  7. package/dist/core/common/types/maybe-promise.type.js +3 -0
  8. package/dist/core/common/types/maybe-promise.type.js.map +1 -0
  9. package/dist/core/modules/auth/core-auth.model.d.ts +1 -0
  10. package/dist/core/modules/auth/core-auth.model.js +4 -0
  11. package/dist/core/modules/auth/core-auth.model.js.map +1 -1
  12. package/dist/core/modules/file/{file-info.output.d.ts → core-file-info.model.d.ts} +4 -3
  13. package/dist/core/modules/file/{file-info.output.js → core-file-info.model.js} +35 -20
  14. package/dist/core/modules/file/core-file-info.model.js.map +1 -0
  15. package/dist/core/modules/file/core-file.controller.d.ts +7 -0
  16. package/dist/core/modules/file/core-file.controller.js +52 -0
  17. package/dist/core/modules/file/core-file.controller.js.map +1 -0
  18. package/dist/core/modules/file/core-file.resolver.d.ts +10 -0
  19. package/dist/core/modules/file/core-file.resolver.js +70 -0
  20. package/dist/core/modules/file/core-file.resolver.js.map +1 -0
  21. package/dist/core/modules/file/core-file.service.d.ts +20 -15
  22. package/dist/core/modules/file/core-file.service.js +59 -22
  23. package/dist/core/modules/file/core-file.service.js.map +1 -1
  24. package/dist/core/modules/user/core-user.model.d.ts +1 -0
  25. package/dist/core/modules/user/core-user.model.js +4 -0
  26. package/dist/core/modules/user/core-user.model.js.map +1 -1
  27. package/dist/core.module.js +1 -1
  28. package/dist/core.module.js.map +1 -1
  29. package/dist/index.d.ts +4 -1
  30. package/dist/index.js +4 -1
  31. package/dist/index.js.map +1 -1
  32. package/dist/server/common/models/persistence.model.d.ts +3 -3
  33. package/dist/server/common/models/persistence.model.js +8 -4
  34. package/dist/server/common/models/persistence.model.js.map +1 -1
  35. package/dist/server/modules/auth/auth.model.d.ts +1 -0
  36. package/dist/server/modules/auth/auth.model.js +5 -0
  37. package/dist/server/modules/auth/auth.model.js.map +1 -1
  38. package/dist/server/modules/file/file-info.model.d.ts +26 -0
  39. package/dist/server/modules/file/file-info.model.js +21 -0
  40. package/dist/server/modules/file/file-info.model.js.map +1 -0
  41. package/dist/server/modules/file/file.controller.d.ts +2 -3
  42. package/dist/server/modules/file/file.controller.js +4 -26
  43. package/dist/server/modules/file/file.controller.js.map +1 -1
  44. package/dist/server/modules/file/file.module.d.ts +2 -0
  45. package/dist/server/modules/file/file.module.js +27 -0
  46. package/dist/server/modules/file/file.module.js.map +1 -0
  47. package/dist/server/modules/file/file.resolver.d.ts +3 -4
  48. package/dist/server/modules/file/file.resolver.js +4 -5
  49. package/dist/server/modules/file/file.resolver.js.map +1 -1
  50. package/dist/server/modules/file/file.service.js +1 -1
  51. package/dist/server/modules/file/file.service.js.map +1 -1
  52. package/dist/server/modules/user/inputs/user-create.input.js.map +1 -1
  53. package/dist/server/modules/user/inputs/user.input.js.map +1 -1
  54. package/dist/server/modules/user/user.model.d.ts +3 -3
  55. package/dist/server/modules/user/user.model.js +4 -4
  56. package/dist/server/modules/user/user.model.js.map +1 -1
  57. package/dist/server/server.module.js +4 -6
  58. package/dist/server/server.module.js.map +1 -1
  59. package/dist/test/test.helper.js +1 -1
  60. package/dist/test/test.helper.js.map +1 -1
  61. package/dist/tsconfig.build.tsbuildinfo +1 -1
  62. package/package.json +1 -1
  63. package/src/core/common/helpers/db.helper.ts +7 -3
  64. package/src/core/common/interfaces/service-options.interface.ts +1 -1
  65. package/src/core/common/types/field-selection.type.ts +6 -1
  66. package/src/core/common/types/maybe-promise.type.ts +4 -0
  67. package/src/core/modules/auth/core-auth.model.ts +11 -1
  68. package/src/core/modules/file/core-file-info.model.ts +40 -0
  69. package/src/core/modules/file/core-file.controller.ts +34 -0
  70. package/src/core/modules/file/core-file.resolver.ts +56 -0
  71. package/src/core/modules/file/core-file.service.ts +81 -27
  72. package/src/core/modules/user/core-user.model.ts +10 -0
  73. package/src/core.module.ts +1 -2
  74. package/src/index.ts +4 -1
  75. package/src/server/common/models/persistence.model.ts +25 -11
  76. package/src/server/modules/auth/auth.model.ts +9 -0
  77. package/src/server/modules/file/file-info.model.ts +15 -0
  78. package/src/server/modules/file/file.controller.ts +8 -37
  79. package/src/server/modules/file/file.module.ts +17 -0
  80. package/src/server/modules/file/file.resolver.ts +1 -4
  81. package/src/server/modules/file/file.service.ts +4 -1
  82. package/src/server/modules/user/inputs/user-create.input.ts +0 -4
  83. package/src/server/modules/user/inputs/user.input.ts +0 -4
  84. package/src/server/modules/user/user.model.ts +8 -8
  85. package/src/server/server.module.ts +6 -8
  86. package/src/test/test.helper.ts +1 -1
  87. package/dist/core/modules/file/file-info.output.js.map +0 -1
  88. package/src/core/modules/file/file-info.output.ts +0 -26
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lenne.tech/nest-server",
3
- "version": "8.6.10",
3
+ "version": "8.6.13",
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",
@@ -543,7 +543,7 @@ export function removeIds(source: any[], ids: StringOrObjectId | StringOrObjectI
543
543
  */
544
544
  export async function setPopulates<T = Query<any, any> | Document>(
545
545
  queryOrDocument: T,
546
- populateOptions: PopulateOptions[],
546
+ populateOptions: string[] | PopulateOptions[] | (string | PopulateOptions)[],
547
547
  modelSchemaPaths?: { [key: string]: SchemaType }
548
548
  ): Promise<T> {
549
549
  // Check parameters
@@ -553,8 +553,12 @@ export async function setPopulates<T = Query<any, any> | Document>(
553
553
 
554
554
  // Filter populate options via model schema paths
555
555
  if (modelSchemaPaths) {
556
- populateOptions = populateOptions.filter((options) => {
557
- return Object.keys(modelSchemaPaths).includes(options.path);
556
+ populateOptions = populateOptions.filter((option: string | PopulateOptions) => {
557
+ let key: string = option as string;
558
+ if ((option as PopulateOptions)?.path) {
559
+ key = (option as PopulateOptions)?.path;
560
+ }
561
+ return Object.keys(modelSchemaPaths).includes(key);
558
562
  });
559
563
  }
560
564
 
@@ -35,7 +35,7 @@ export interface ServiceOptions {
35
35
  outputType?: new (...params: any[]) => any;
36
36
 
37
37
  // Alias for fieldSelection (if both are set fieldSelection is overwritten by populate)
38
- populate?: PopulateOptions | (PopulateOptions | string)[];
38
+ populate?: string | PopulateOptions | (PopulateOptions | string)[];
39
39
 
40
40
  // Process field selection
41
41
  // If {} or not set, then the field selection runs with defaults
@@ -5,4 +5,9 @@ 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 | (PopulateOptions | string)[] | SelectionNode[] | ResolveSelector;
8
+ export type FieldSelection =
9
+ | string
10
+ | PopulateOptions
11
+ | (PopulateOptions | string)[]
12
+ | SelectionNode[]
13
+ | ResolveSelector;
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Promise or direct value
3
+ */
4
+ export type MaybePromise<T = any> = T | Promise<T>;
@@ -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,14 @@ 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. Non-primitive variables should always be mapped.
38
+ // If something comes up, you can use `mapClasses` / `mapClassesAsync` from ModelHelper.
39
+ return this;
40
+ }
31
41
  }
@@ -0,0 +1,40 @@
1
+ import { Field, ObjectType } from '@nestjs/graphql';
2
+ import { Prop } from '@nestjs/mongoose';
3
+ import { Types } from 'mongoose';
4
+ import { CoreModel } from '../../common/models/core-model.model';
5
+
6
+ /**
7
+ * File info
8
+ */
9
+ @ObjectType({ description: 'Information about file' })
10
+ export class CoreFileInfo extends CoreModel {
11
+ _id: Types.ObjectId;
12
+
13
+ @Field(() => String, { description: 'ID of the file' })
14
+ id: string = undefined;
15
+
16
+ @Field(() => Number, {
17
+ description:
18
+ 'The size of each chunk in bytes. GridFS divides the document into chunks of size chunkSize, ' +
19
+ 'except for the last, which is only as large as needed. The default size is 255 kilobytes (kB)',
20
+ nullable: true,
21
+ })
22
+ @Prop({ type: Number, required: false })
23
+ chunkSize: number = undefined;
24
+
25
+ @Field(() => String, { description: 'Content type', nullable: true })
26
+ @Prop({ type: String, required: false })
27
+ contentType?: string = undefined;
28
+
29
+ @Field(() => String, { description: 'Name of the file', nullable: true })
30
+ @Prop({ type: String, required: false })
31
+ filename?: string = undefined;
32
+
33
+ @Field(() => Number, { description: 'The size of the document in bytes', nullable: true })
34
+ @Prop({ type: Number, required: false })
35
+ length: number = undefined;
36
+
37
+ @Field(() => Date, { description: 'The date the file was first stored', nullable: true })
38
+ @Prop({ type: Date, required: false })
39
+ uploadDate: Date = undefined;
40
+ }
@@ -0,0 +1,34 @@
1
+ import { BadRequestException, Controller, Get, NotFoundException, Param, Res } from '@nestjs/common';
2
+ import { User } from '../../../server/modules/user/user.model';
3
+ import { RESTUser } from '../../common/decorators/rest-user.decorator';
4
+ import { CoreFileService } from './core-file.service';
5
+
6
+ /**
7
+ * File controller
8
+ */
9
+ @Controller('files')
10
+ export abstract class CoreFileController {
11
+ /**
12
+ * Include services
13
+ */
14
+ protected constructor(protected fileService: CoreFileService) {}
15
+
16
+ /**
17
+ * Download file
18
+ */
19
+ @Get(':filename')
20
+ async getFile(@Param('filename') filename: string, @Res() res, @RESTUser() user: User) {
21
+ if (!filename) {
22
+ throw new BadRequestException('Missing filename for download');
23
+ }
24
+
25
+ const file = await this.fileService.getFileInfoByName(filename);
26
+ if (!file) {
27
+ throw new NotFoundException('File not found');
28
+ }
29
+ const filestream = await this.fileService.getFileStream(file.id);
30
+ res.header('Content-Type', file.contentType);
31
+ res.header('Content-Disposition', 'attachment; filename=' + file.filename);
32
+ return filestream.pipe(res);
33
+ }
34
+ }
@@ -0,0 +1,56 @@
1
+ import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
2
+ import * as GraphQLUpload from 'graphql-upload/GraphQLUpload.js';
3
+ import { CoreFileInfo } from './core-file-info.model';
4
+ import { CoreFileService } from './core-file.service';
5
+ import { FileUpload } from './interfaces/file-upload.interface';
6
+
7
+ /**
8
+ * File resolver
9
+ */
10
+ @Resolver()
11
+ export class CoreFileResolver {
12
+ /**
13
+ * Integrate services
14
+ */
15
+ constructor(protected readonly fileService: CoreFileService) {}
16
+
17
+ // ===========================================================================
18
+ // Queries
19
+ // ===========================================================================
20
+
21
+ /**
22
+ * Get file info
23
+ */
24
+ @Query(() => CoreFileInfo, { nullable: true })
25
+ async getFileInfo(@Args({ name: 'filename', type: () => String }) filename: string): Promise<any> {
26
+ return await this.fileService.getFileInfoByName(filename);
27
+ }
28
+
29
+ // ===========================================================================
30
+ // Mutations
31
+ // ===========================================================================
32
+
33
+ /**
34
+ * Delete file
35
+ */
36
+ @Mutation(() => CoreFileInfo)
37
+ async deleteFile(@Args({ name: 'filename', type: () => String }) filename: string): Promise<any> {
38
+ return await this.fileService.deleteFileByName(filename);
39
+ }
40
+
41
+ /**
42
+ * Upload file
43
+ */
44
+ @Mutation(() => CoreFileInfo)
45
+ async uploadFile(@Args({ name: 'file', type: () => GraphQLUpload }) file: FileUpload): Promise<any> {
46
+ return await this.fileService.createFile(file);
47
+ }
48
+
49
+ /**
50
+ * Upload files
51
+ */
52
+ @Mutation(() => [CoreFileInfo])
53
+ async uploadFiles(@Args({ name: 'files', type: () => [GraphQLUpload] }) files: FileUpload[]): Promise<any> {
54
+ return await this.fileService.createFiles(files);
55
+ }
56
+ }
@@ -7,10 +7,16 @@ import { getObjectIds, getStringIds } from '../../common/helpers/db.helper';
7
7
  import { convertFilterArgsToQuery } from '../../common/helpers/filter.helper';
8
8
  import { check } from '../../common/helpers/input.helper';
9
9
  import { prepareOutput } from '../../common/helpers/service.helper';
10
- import { FileInfo } from './file-info.output';
10
+ import { MaybePromise } from '../../common/types/maybe-promise.type';
11
+ import { CoreFileInfo } from './core-file-info.model';
11
12
  import { FileServiceOptions } from './interfaces/file-service-options.interface';
12
13
  import { FileUpload } from './interfaces/file-upload.interface';
13
14
 
15
+ /**
16
+ * Type for checking input
17
+ */
18
+ export type FileInputCheckType = 'file' | 'files' | 'filterArgs' | 'id' | 'filename';
19
+
14
20
  /**
15
21
  * Abstract core file service
16
22
  */
@@ -20,16 +26,19 @@ export abstract class CoreFileService {
20
26
  /**
21
27
  * Include MongoDB connection and create File bucket
22
28
  */
23
- protected constructor(protected readonly connection: Connection, modelName = 'File') {
24
- this.files = createBucket({ modelName, connection });
29
+ protected constructor(protected readonly connection: Connection, bucketName = 'fs') {
30
+ this.files = createBucket({ bucketName, connection });
25
31
  }
26
32
 
27
33
  /**
28
34
  * Save file in DB
29
35
  */
30
- createFile(file: FileUpload, serviceOptions?: FileServiceOptions): Promise<FileInfo> {
31
- return new Promise(async (resolve, reject) => {
32
- const { filename, mimetype, encoding, createReadStream } = file;
36
+ async createFile(file: MaybePromise<FileUpload>, serviceOptions?: FileServiceOptions): Promise<CoreFileInfo> {
37
+ if (!(await this.checkRights(file, { ...serviceOptions, checkInputType: 'file' }))) {
38
+ return null;
39
+ }
40
+ return await new Promise(async (resolve, reject) => {
41
+ const { filename, mimetype, encoding, createReadStream } = await file;
33
42
  const readStream = createReadStream();
34
43
  const options: MongoGridFSOptions = { filename, contentType: mimetype };
35
44
  this.files.writeFile(options, readStream, (error, fileInfo) => {
@@ -41,8 +50,12 @@ export abstract class CoreFileService {
41
50
  /**
42
51
  * Save files in DB
43
52
  */
44
- async createFiles(files: FileUpload[], serviceOptions?: FileServiceOptions): Promise<FileInfo[]> {
45
- const promises: Promise<FileInfo>[] = [];
53
+ async createFiles(files: MaybePromise<FileUpload>[], serviceOptions?: FileServiceOptions): Promise<CoreFileInfo[]> {
54
+ if (!(await this.checkRights(files, { ...serviceOptions, checkInputType: 'files' }))) {
55
+ return null;
56
+ }
57
+ console.log(files);
58
+ const promises: Promise<CoreFileInfo>[] = [];
46
59
  for (const file of files) {
47
60
  promises.push(this.createFile(file, serviceOptions));
48
61
  }
@@ -52,8 +65,11 @@ export abstract class CoreFileService {
52
65
  /**
53
66
  * Get file infos via filter
54
67
  */
55
- findFileInfo(filterArgs?: FilterArgs, serviceOptions?: FileServiceOptions): Promise<FileInfo[]> {
56
- return new Promise((resolve, reject) => {
68
+ async findFileInfo(filterArgs?: FilterArgs, serviceOptions?: FileServiceOptions): Promise<CoreFileInfo[]> {
69
+ if (!(await this.checkRights(filterArgs, { ...serviceOptions, checkInputType: 'filterArgs' }))) {
70
+ return null;
71
+ }
72
+ return await new Promise((resolve, reject) => {
57
73
  const filterQuery = convertFilterArgsToQuery(filterArgs);
58
74
  const cursor = this.files.find(filterQuery[0], filterQuery[1]);
59
75
  if (!cursor) {
@@ -68,8 +84,11 @@ export abstract class CoreFileService {
68
84
  /**
69
85
  * Get info about file via file ID
70
86
  */
71
- getFileInfo(id: string | Types.ObjectId, serviceOptions?: FileServiceOptions): Promise<FileInfo> {
72
- return new Promise((resolve, reject) => {
87
+ async getFileInfo(id: string | Types.ObjectId, serviceOptions?: FileServiceOptions): Promise<CoreFileInfo> {
88
+ if (!(await this.checkRights(id, { ...serviceOptions, checkInputType: 'id' }))) {
89
+ return null;
90
+ }
91
+ return await new Promise((resolve, reject) => {
73
92
  this.files.findById(getObjectIds(id), (error, fileInfo) => {
74
93
  error ? reject(error) : resolve(this.prepareOutput(fileInfo, serviceOptions));
75
94
  });
@@ -79,8 +98,11 @@ export abstract class CoreFileService {
79
98
  /**
80
99
  * Get info about file via filename
81
100
  */
82
- getFileInfoByName(filename: string, serviceOptions?: FileServiceOptions): Promise<FileInfo> {
83
- return new Promise((resolve, reject) => {
101
+ async getFileInfoByName(filename: string, serviceOptions?: FileServiceOptions): Promise<CoreFileInfo> {
102
+ if (!(await this.checkRights(filename, { ...serviceOptions, checkInputType: 'filename' }))) {
103
+ return null;
104
+ }
105
+ return await new Promise((resolve, reject) => {
84
106
  this.files.findOne({ filename }, (error, fileInfo) => {
85
107
  error ? reject(error) : resolve(this.prepareOutput(fileInfo, serviceOptions));
86
108
  });
@@ -90,22 +112,34 @@ export abstract class CoreFileService {
90
112
  /**
91
113
  * Get file stream (for big files) via file ID
92
114
  */
93
- getFileStream(id: string | Types.ObjectId, options?: GridFSBucketReadStreamOptions) {
94
- return this.files.openDownloadStream(getObjectIds(id), options);
115
+ async getFileStream(id: string | Types.ObjectId, serviceOptions?: FileServiceOptions) {
116
+ if (!(await this.checkRights(id, { ...serviceOptions, checkInputType: 'id' }))) {
117
+ return null;
118
+ }
119
+ return this.files.openDownloadStream(getObjectIds(id));
95
120
  }
96
121
 
97
122
  /**
98
123
  * Get file stream (for big files) via filename
99
124
  */
100
- getFileStreamByName(filename: string): GridFSBucketReadStreamOptions {
125
+ async getFileStreamByName(
126
+ filename: string,
127
+ serviceOptions?: FileServiceOptions
128
+ ): Promise<GridFSBucketReadStreamOptions> {
129
+ if (!(await this.checkRights(filename, { ...serviceOptions, checkInputType: 'filename' }))) {
130
+ return null;
131
+ }
101
132
  return this.files.readFile({ filename });
102
133
  }
103
134
 
104
135
  /**
105
136
  * Get file buffer (for small files) via file ID
106
137
  */
107
- getBuffer(id: string | Types.ObjectId): Promise<Buffer> {
108
- return new Promise((resolve, reject) => {
138
+ async getBuffer(id: string | Types.ObjectId, serviceOptions?: FileServiceOptions): Promise<Buffer> {
139
+ if (!(await this.checkRights(id, { ...serviceOptions, checkInputType: 'id' }))) {
140
+ return null;
141
+ }
142
+ return await new Promise((resolve, reject) => {
109
143
  this.files.readFile({ _id: getObjectIds(id) }, (error, buffer) => {
110
144
  error ? reject(error) : resolve(buffer);
111
145
  });
@@ -115,8 +149,11 @@ export abstract class CoreFileService {
115
149
  /**
116
150
  * Get file buffer (for small files) via file ID
117
151
  */
118
- getBufferByName(filename: string): Promise<Buffer> {
119
- return new Promise((resolve, reject) => {
152
+ async getBufferByName(filename: string, serviceOptions?: FileServiceOptions): Promise<Buffer> {
153
+ if (!(await this.checkRights(filename, { ...serviceOptions, checkInputType: 'filename' }))) {
154
+ return null;
155
+ }
156
+ return await new Promise((resolve, reject) => {
120
157
  this.files.readFile({ filename }, (error, buffer) => {
121
158
  error ? reject(error) : resolve(buffer);
122
159
  });
@@ -126,8 +163,11 @@ export abstract class CoreFileService {
126
163
  /**
127
164
  * Delete file reference of avatar
128
165
  */
129
- deleteFile(id: string | Types.ObjectId, serviceOptions?: FileServiceOptions): Promise<FileInfo> {
130
- return new Promise((resolve, reject) => {
166
+ async deleteFile(id: string | Types.ObjectId, serviceOptions?: FileServiceOptions): Promise<CoreFileInfo> {
167
+ if (!(await this.checkRights(id, { ...serviceOptions, checkInputType: 'id' }))) {
168
+ return null;
169
+ }
170
+ return await new Promise((resolve, reject) => {
131
171
  return this.files.unlink(getObjectIds(id), (error, fileInfo) => {
132
172
  error ? reject(error) : resolve(this.prepareOutput(fileInfo, serviceOptions));
133
173
  });
@@ -137,7 +177,10 @@ export abstract class CoreFileService {
137
177
  /**
138
178
  * Delete file reference of avatar
139
179
  */
140
- async deleteFileByName(filename: string, serviceOptions?: FileServiceOptions): Promise<FileInfo> {
180
+ async deleteFileByName(filename: string, serviceOptions?: FileServiceOptions): Promise<CoreFileInfo> {
181
+ if (!(await this.checkRights(filename, { ...serviceOptions, checkInputType: 'filename' }))) {
182
+ return null;
183
+ }
141
184
  const fileInfo = await this.getFileInfoByName(filename);
142
185
  if (!fileInfo) {
143
186
  throw new NotFoundException('File not found with filename ' + filename);
@@ -149,22 +192,33 @@ export abstract class CoreFileService {
149
192
  // Helper methods
150
193
  // ===================================================================================================================
151
194
 
195
+ /**
196
+ * Check rights before processing file handling
197
+ * Can throw an exception if the rights do not fit
198
+ */
199
+ protected checkRights(
200
+ input: any,
201
+ options?: FileServiceOptions & { checkInputType: FileInputCheckType }
202
+ ): MaybePromise<boolean> {
203
+ return true;
204
+ }
205
+
152
206
  /**
153
207
  * Prepare output before return
154
208
  */
155
- protected async prepareOutput(fileInfo: FileInfo | FileInfo[], options?: FileServiceOptions) {
209
+ protected async prepareOutput(fileInfo: CoreFileInfo | CoreFileInfo[], options?: FileServiceOptions) {
156
210
  if (!fileInfo) {
157
211
  return fileInfo;
158
212
  }
159
213
  this.setId(fileInfo);
160
- fileInfo = await prepareOutput(fileInfo, { targetModel: FileInfo });
214
+ fileInfo = await prepareOutput(fileInfo, { targetModel: CoreFileInfo });
161
215
  return check(fileInfo, options?.currentUser, { roles: options?.roles });
162
216
  }
163
217
 
164
218
  /**
165
219
  * Set file info ID via _id
166
220
  */
167
- protected setId(fileInfo: FileInfo | FileInfo[]) {
221
+ protected setId(fileInfo: CoreFileInfo | CoreFileInfo[]) {
168
222
  if (Array.isArray(fileInfo)) {
169
223
  fileInfo.forEach((item) => {
170
224
  if (typeof item === 'object') {
@@ -122,4 +122,14 @@ 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. Non-primitive variables should always be mapped.
132
+ // If something comes up, you can use `mapClasses` / `mapClassesAsync` from ModelHelper.
133
+ return this;
134
+ }
125
135
  }
@@ -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
@@ -56,6 +56,7 @@ export * from './core/common/services/template.service';
56
56
  export * from './core/common/types/core-model-constructor.type';
57
57
  export * from './core/common/types/field-selection.type';
58
58
  export * from './core/common/types/ids.type';
59
+ export * from './core/common/types/maybe-promise.type';
59
60
  export * from './core/common/types/plain-input.type';
60
61
  export * from './core/common/types/require-only-one.type';
61
62
  export * from './core/common/types/required-at-least-one.type';
@@ -84,8 +85,10 @@ export * from './core/modules/auth/jwt.strategy';
84
85
 
85
86
  export * from './core/modules/file/interfaces/file-service-options.interface';
86
87
  export * from './core/modules/file/interfaces/file-upload.interface';
88
+ export * from './core/modules/file/core-file.controller';
89
+ export * from './core/modules/file/core-file.resolver';
87
90
  export * from './core/modules/file/core-file.service';
88
- export * from './core/modules/file/file-info.output';
91
+ export * from './core/modules/file/core-file-info.model';
89
92
 
90
93
  // =====================================================================================================================
91
94
  // Core - Modules - User
@@ -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,14 @@ 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. Non-primitive variables should always be mapped.
65
+ // If something comes up, you can use `mapClasses` / `mapClassesAsync` from ModelHelper.
66
+ return this;
67
+ }
54
68
  }
@@ -1,4 +1,5 @@
1
1
  import { Field, ObjectType } from '@nestjs/graphql';
2
+ import { mapClasses } from '../../../core/common/helpers/model.helper';
2
3
  import { CoreAuthModel } from '../../../core/modules/auth/core-auth.model';
3
4
  import { User } from '../user/user.model';
4
5
 
@@ -29,4 +30,12 @@ export class Auth extends CoreAuthModel {
29
30
  // Nothing more to initialize yet
30
31
  return this;
31
32
  }
33
+
34
+ /**
35
+ * Map input
36
+ */
37
+ map(input) {
38
+ super.map(input);
39
+ return mapClasses(input, { user: User }, this);
40
+ }
32
41
  }
@@ -0,0 +1,15 @@
1
+ import { ObjectType } from '@nestjs/graphql';
2
+ import { Schema as MongooseSchema, SchemaFactory } from '@nestjs/mongoose';
3
+ import { CoreFileInfo } from '../../../core/modules/file/core-file-info.model';
4
+
5
+ /**
6
+ * File info model
7
+ */
8
+ @ObjectType({ description: 'Information about file' })
9
+ @MongooseSchema({ collection: 'fs.files' })
10
+ export class FileInfo extends CoreFileInfo {}
11
+
12
+ /**
13
+ * File info schema
14
+ */
15
+ export const FileInfoSchema = SchemaFactory.createForClass(FileInfo);
@@ -1,34 +1,25 @@
1
- import {
2
- BadRequestException,
3
- Body,
4
- Controller,
5
- Get,
6
- NotFoundException,
7
- Param,
8
- Post,
9
- Res,
10
- UploadedFiles,
11
- UseInterceptors,
12
- } from '@nestjs/common';
1
+ import { Body, Controller, Post, UploadedFiles, UseInterceptors } from '@nestjs/common';
13
2
  import { FilesInterceptor } from '@nestjs/platform-express';
14
3
  import { diskStorage } from 'multer';
15
4
  import envConfig from '../../../config.env';
16
- import { RESTUser } from '../../../core/common/decorators/rest-user.decorator';
17
5
  import { Roles } from '../../../core/common/decorators/roles.decorator';
18
6
  import { RoleEnum } from '../../../core/common/enums/role.enum';
19
7
  import { multerRandomFileName } from '../../../core/common/helpers/file.helper';
20
- import { User } from '../user/user.model';
8
+ import { CoreFileController } from '../../../core/modules/file/core-file.controller';
21
9
  import { FileService } from './file.service';
22
10
 
23
11
  /**
24
- * File controller for
12
+ * File controller
25
13
  */
14
+ @Roles(RoleEnum.S_USER)
26
15
  @Controller('files')
27
- export class FileController {
16
+ export class FileController extends CoreFileController {
28
17
  /**
29
18
  * Include services
30
19
  */
31
- constructor(protected fileService: FileService) {}
20
+ constructor(protected fileService: FileService) {
21
+ super(fileService);
22
+ }
32
23
 
33
24
  /**
34
25
  * Upload files via REST as an alternative to uploading via GraphQL (see file.resolver.ts)
@@ -53,24 +44,4 @@ export class FileController {
53
44
  uploadFiles(@UploadedFiles() files, @Body() fields: any) {
54
45
  console.log('Saved file info', JSON.stringify({ files, fields }, null, 2));
55
46
  }
56
-
57
- /**
58
- * Download file
59
- */
60
- @Roles(RoleEnum.ADMIN)
61
- @Get(':filename')
62
- async getFile(@Param('filename') filename: string, @Res() res, @RESTUser() user: User) {
63
- if (!filename) {
64
- throw new BadRequestException('Missing filename for download');
65
- }
66
-
67
- const file = await this.fileService.getFileInfoByName(filename);
68
- if (!file) {
69
- throw new NotFoundException('File not found');
70
- }
71
- const filestream = await this.fileService.getFileStream(file.id);
72
- res.header('Content-Type', file.contentType);
73
- res.header('Content-Disposition', 'attachment; filename=' + file.filename);
74
- return filestream.pipe(res);
75
- }
76
47
  }