@lenne.tech/nest-server 11.1.14 → 11.3.0

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 (77) hide show
  1. package/dist/core/common/decorators/unified-field.decorator.d.ts +2 -0
  2. package/dist/core/common/decorators/unified-field.decorator.js +26 -9
  3. package/dist/core/common/decorators/unified-field.decorator.js.map +1 -1
  4. package/dist/core/common/helpers/gridfs.helper.d.ts +42 -0
  5. package/dist/core/common/helpers/gridfs.helper.js +107 -0
  6. package/dist/core/common/helpers/gridfs.helper.js.map +1 -0
  7. package/dist/core/common/models/core-persistence.model.js +2 -2
  8. package/dist/core/common/models/core-persistence.model.js.map +1 -1
  9. package/dist/core/common/services/brevo.service.d.ts +1 -0
  10. package/dist/core/common/services/brevo.service.js +19 -18
  11. package/dist/core/common/services/brevo.service.js.map +1 -1
  12. package/dist/core/common/services/crud.service.js +1 -1
  13. package/dist/core/common/services/crud.service.js.map +1 -1
  14. package/dist/core/modules/file/core-file-info.model.js +41 -23
  15. package/dist/core/modules/file/core-file-info.model.js.map +1 -1
  16. package/dist/core/modules/file/core-file.controller.d.ts +2 -1
  17. package/dist/core/modules/file/core-file.controller.js +1 -1
  18. package/dist/core/modules/file/core-file.controller.js.map +1 -1
  19. package/dist/core/modules/file/core-file.service.d.ts +7 -7
  20. package/dist/core/modules/file/core-file.service.js +28 -51
  21. package/dist/core/modules/file/core-file.service.js.map +1 -1
  22. package/dist/core/modules/file/interfaces/file-upload.interface.d.ts +1 -1
  23. package/dist/core/modules/user/core-user.model.js +95 -54
  24. package/dist/core/modules/user/core-user.model.js.map +1 -1
  25. package/dist/core/modules/user/core-user.service.js +2 -3
  26. package/dist/core/modules/user/core-user.service.js.map +1 -1
  27. package/dist/index.d.ts +2 -0
  28. package/dist/index.js +1 -0
  29. package/dist/index.js.map +1 -1
  30. package/dist/main.js +22 -0
  31. package/dist/main.js.map +1 -1
  32. package/dist/server/common/models/persistence.model.js +13 -11
  33. package/dist/server/common/models/persistence.model.js.map +1 -1
  34. package/dist/server/modules/auth/auth.model.js +6 -2
  35. package/dist/server/modules/auth/auth.model.js.map +1 -1
  36. package/dist/server/modules/file/file-info.model.d.ts +7 -3
  37. package/dist/server/modules/file/file.controller.d.ts +6 -4
  38. package/dist/server/modules/file/file.controller.js +15 -4
  39. package/dist/server/modules/file/file.controller.js.map +1 -1
  40. package/dist/server/modules/file/file.resolver.js +1 -1
  41. package/dist/server/modules/file/file.resolver.js.map +1 -1
  42. package/dist/server/modules/file/multer-config.service.d.ts +0 -2
  43. package/dist/server/modules/file/multer-config.service.js +3 -22
  44. package/dist/server/modules/file/multer-config.service.js.map +1 -1
  45. package/dist/server/modules/user/user.controller.d.ts +19 -0
  46. package/dist/server/modules/user/user.controller.js +256 -0
  47. package/dist/server/modules/user/user.controller.js.map +1 -0
  48. package/dist/server/modules/user/user.model.d.ts +7 -3
  49. package/dist/server/modules/user/user.model.js +37 -24
  50. package/dist/server/modules/user/user.model.js.map +1 -1
  51. package/dist/server/modules/user/user.module.js +2 -1
  52. package/dist/server/modules/user/user.module.js.map +1 -1
  53. package/dist/tsconfig.build.tsbuildinfo +1 -1
  54. package/dist/types/graphql-upload.d.ts +25 -0
  55. package/package.json +41 -44
  56. package/src/core/common/decorators/unified-field.decorator.ts +49 -10
  57. package/src/core/common/helpers/gridfs.helper.ts +227 -0
  58. package/src/core/common/models/core-persistence.model.ts +3 -3
  59. package/src/core/common/services/brevo.service.ts +20 -18
  60. package/src/core/common/services/crud.service.ts +3 -3
  61. package/src/core/modules/file/core-file-info.model.ts +40 -22
  62. package/src/core/modules/file/core-file.controller.ts +3 -2
  63. package/src/core/modules/file/core-file.service.ts +49 -60
  64. package/src/core/modules/file/interfaces/file-upload.interface.ts +2 -1
  65. package/src/core/modules/user/core-user.model.ts +120 -78
  66. package/src/core/modules/user/core-user.service.ts +3 -3
  67. package/src/index.ts +1 -0
  68. package/src/main.ts +25 -0
  69. package/src/server/common/models/persistence.model.ts +15 -13
  70. package/src/server/modules/auth/auth.model.ts +7 -3
  71. package/src/server/modules/file/file.controller.ts +25 -7
  72. package/src/server/modules/file/file.resolver.ts +1 -2
  73. package/src/server/modules/file/multer-config.service.ts +6 -21
  74. package/src/server/modules/user/user.controller.ts +242 -0
  75. package/src/server/modules/user/user.model.ts +39 -26
  76. package/src/server/modules/user/user.module.ts +2 -1
  77. package/src/types/graphql-upload.d.ts +25 -0
package/src/index.ts CHANGED
@@ -34,6 +34,7 @@ export * from './core/common/helpers/decorator.helper';
34
34
  export * from './core/common/helpers/file.helper';
35
35
  export * from './core/common/helpers/filter.helper';
36
36
  export * from './core/common/helpers/graphql.helper';
37
+ export * from './core/common/helpers/gridfs.helper';
37
38
  export * from './core/common/helpers/input.helper';
38
39
  export * from './core/common/helpers/model.helper';
39
40
  export * from './core/common/helpers/scim.helper';
package/src/main.ts CHANGED
@@ -1,11 +1,19 @@
1
1
  import { NestFactory } from '@nestjs/core';
2
2
  import { NestExpressApplication } from '@nestjs/platform-express';
3
+ import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
3
4
  import { exec } from 'child_process';
4
5
  import compression = require('compression');
5
6
  import cookieParser = require('cookie-parser');
6
7
 
7
8
  import envConfig from './config.env';
9
+ import { FilterArgs } from './core/common/args/filter.args';
8
10
  import { HttpExceptionLogFilter } from './core/common/filters/http-exception-log.filter';
11
+ import { CorePersistenceModel } from './core/common/models/core-persistence.model';
12
+ import { CoreAuthModel } from './core/modules/auth/core-auth.model';
13
+ import { CoreUserModel } from './core/modules/user/core-user.model';
14
+ import { PersistenceModel } from './server/common/models/persistence.model';
15
+ import { Auth } from './server/modules/auth/auth.model';
16
+ import { User } from './server/modules/user/user.model';
9
17
  import { ServerModule } from './server/server.module';
10
18
 
11
19
  /**
@@ -54,6 +62,23 @@ async function bootstrap() {
54
62
  // Enable cors to allow requests from other domains
55
63
  server.enableCors();
56
64
 
65
+ // Swagger documentation
66
+ const config = new DocumentBuilder()
67
+ .setTitle('Nest Server API')
68
+ .setDescription('API lenne.Tech Nest Server')
69
+ .setVersion('1.0.0')
70
+ .addBearerAuth()
71
+ .build();
72
+ const documentFactory = () =>
73
+ SwaggerModule.createDocument(server, config, {
74
+ autoTagControllers: true,
75
+ deepScanRoutes: true,
76
+ extraModels: [CoreUserModel, CoreAuthModel, Auth, User, PersistenceModel, CorePersistenceModel, FilterArgs],
77
+ });
78
+ SwaggerModule.setup('swagger', server, documentFactory, {
79
+ jsonDocumentUrl: '/api-docs-json',
80
+ });
81
+
57
82
  // Start server on configured port
58
83
  await server.listen(envConfig.port, envConfig.hostname);
59
84
  console.debug(`Server startet at ${await server.getUrl()}`);
@@ -1,10 +1,10 @@
1
- import { Field, ObjectType } from '@nestjs/graphql';
2
- import { Prop } from '@nestjs/mongoose';
3
- import { ApiExtraModels, ApiProperty } from '@nestjs/swagger';
1
+ import { ObjectType } from '@nestjs/graphql';
2
+ import { ApiExtraModels } from '@nestjs/swagger';
4
3
  import { Types } from 'mongoose';
5
4
  import mongoose = require('mongoose');
6
5
 
7
6
  import { Restricted } from '../../../core/common/decorators/restricted.decorator';
7
+ import { UnifiedField } from '../../../core/common/decorators/unified-field.decorator';
8
8
  import { RoleEnum } from '../../../core/common/enums/role.enum';
9
9
  import { CorePersistenceModel } from '../../../core/common/models/core-persistence.model';
10
10
  import { User } from '../../modules/user/user.model';
@@ -30,13 +30,14 @@ export abstract class PersistenceModel extends CorePersistenceModel {
30
30
  *
31
31
  * Not set when created by system
32
32
  */
33
- @ApiProperty({ type: String })
34
- @Field(() => User, {
33
+ @UnifiedField({
35
34
  description: 'ID of the user who created the object',
36
- nullable: true,
35
+ isOptional: true,
36
+ mongoose: { ref: 'User', type: mongoose.Schema.Types.ObjectId },
37
+ roles: RoleEnum.ADMIN,
38
+ swaggerApiOptions: { type: String },
39
+ type: () => User,
37
40
  })
38
- @Prop({ ref: 'User', type: mongoose.Schema.Types.ObjectId })
39
- @Restricted(RoleEnum.ADMIN)
40
41
  createdBy?: string | Types.ObjectId = undefined;
41
42
 
42
43
  /**
@@ -44,13 +45,14 @@ export abstract class PersistenceModel extends CorePersistenceModel {
44
45
  *
45
46
  * Not set when updated by system
46
47
  */
47
- @ApiProperty({ type: User })
48
- @Field(() => User, {
48
+ @UnifiedField({
49
49
  description: 'ID of the user who updated the object',
50
- nullable: true,
50
+ isOptional: true,
51
+ mongoose: { ref: 'User', type: mongoose.Schema.Types.ObjectId },
52
+ roles: RoleEnum.ADMIN,
53
+ swaggerApiOptions: { type: User },
54
+ type: () => User,
51
55
  })
52
- @Prop({ ref: 'User', type: mongoose.Schema.Types.ObjectId })
53
- @Restricted(RoleEnum.ADMIN)
54
56
  updatedBy?: string | Types.ObjectId = undefined;
55
57
 
56
58
  // ===================================================================================================================
@@ -1,6 +1,7 @@
1
- import { Field, ObjectType } from '@nestjs/graphql';
1
+ import { ObjectType } from '@nestjs/graphql';
2
2
 
3
3
  import { Restricted } from '../../../core/common/decorators/restricted.decorator';
4
+ import { UnifiedField } from '../../../core/common/decorators/unified-field.decorator';
4
5
  import { RoleEnum } from '../../../core/common/enums/role.enum';
5
6
  import { mapClasses } from '../../../core/common/helpers/model.helper';
6
7
  import { CoreAuthModel } from '../../../core/modules/auth/core-auth.model';
@@ -19,8 +20,11 @@ export class Auth extends CoreAuthModel {
19
20
  /**
20
21
  * Signed-in user
21
22
  */
22
- @Field(() => User, { description: 'User who signed in' })
23
- @Restricted(RoleEnum.S_EVERYONE)
23
+ @UnifiedField({
24
+ description: 'User who signed in',
25
+ roles: RoleEnum.S_EVERYONE,
26
+ type: () => User,
27
+ })
24
28
  override user: User = undefined;
25
29
 
26
30
  // ===================================================================================================================
@@ -11,13 +11,17 @@ import {
11
11
  UseInterceptors,
12
12
  } from '@nestjs/common';
13
13
  import { FileInterceptor } from '@nestjs/platform-express';
14
+ import { Response } from 'express';
15
+ import { Readable } from 'stream';
14
16
 
15
17
  import { Roles } from '../../../core/common/decorators/roles.decorator';
16
18
  import { RoleEnum } from '../../../core/common/enums/role.enum';
19
+ import { CoreFileInfo } from '../../../core/modules/file/core-file-info.model';
20
+ import { FileUpload } from '../../../core/modules/file/interfaces/file-upload.interface';
17
21
  import { FileService } from './file.service';
18
22
 
19
23
  /**
20
- * File controller for
24
+ * File controller
21
25
  */
22
26
  @Controller('files')
23
27
  @Roles(RoleEnum.ADMIN)
@@ -28,13 +32,26 @@ export class FileController {
28
32
  constructor(private readonly fileService: FileService) {}
29
33
 
30
34
  /**
31
- * Upload file
35
+ * Upload file via HTTP
32
36
  */
33
37
  @Post('upload')
34
38
  @Roles(RoleEnum.ADMIN)
35
39
  @UseInterceptors(FileInterceptor('file'))
36
- uploadFile(@UploadedFile() file: Express.Multer.File): any {
37
- return file;
40
+ async uploadFile(@UploadedFile() file: Express.Multer.File): Promise<any> {
41
+ if (!file) {
42
+ throw new BadRequestException('No file provided');
43
+ }
44
+
45
+ // Convert Multer file to FileUpload interface
46
+ const fileUpload: FileUpload = {
47
+ capacitor: null, // Not used when creating from buffer
48
+ createReadStream: () => Readable.from(file.buffer),
49
+ filename: file.originalname,
50
+ mimetype: file.mimetype,
51
+ };
52
+
53
+ // Save to GridFS using FileService
54
+ return await this.fileService.createFile(fileUpload);
38
55
  }
39
56
 
40
57
  /**
@@ -42,23 +59,24 @@ export class FileController {
42
59
  */
43
60
  @Get(':id')
44
61
  @Roles(RoleEnum.ADMIN)
45
- async getFile(@Param('id') id: string, @Res() res) {
62
+ async getFile(@Param('id') id: string, @Res() res: Response) {
46
63
  if (!id) {
47
64
  throw new BadRequestException('Missing ID');
48
65
  }
49
66
 
50
- let file;
67
+ let file: CoreFileInfo | null;
51
68
  try {
52
69
  file = await this.fileService.getFileInfo(id);
53
70
  } catch (e) {
54
71
  console.error(e);
72
+ file = null;
55
73
  }
56
74
 
57
75
  if (!file) {
58
76
  throw new NotFoundException('File not found');
59
77
  }
60
78
  const filestream = await this.fileService.getFileStream(id);
61
- res.header('Content-Type', file.contentType);
79
+ res.header('Content-Type', file.contentType || 'application/octet-stream');
62
80
  res.header('Content-Disposition', `attachment; filename=${file.filename}`);
63
81
  res.header('Cache-Control', 'max-age=31536000');
64
82
  return filestream.pipe(res);
@@ -64,8 +64,7 @@ export class FileResolver {
64
64
  // Save files in filesystem
65
65
  const promises: Promise<any>[] = [];
66
66
  for (const file of files) {
67
- // eslint-disable-next-line unused-imports/no-unused-vars
68
- const { createReadStream, encoding, filename, mimetype } = await file;
67
+ const { createReadStream, filename } = await file;
69
68
  await fs.promises.mkdir('./uploads', { recursive: true });
70
69
  promises.push(
71
70
  new Promise((resolve, reject) =>
@@ -1,31 +1,16 @@
1
- import { GridFsStorage } from '@lenne.tech/multer-gridfs-storage';
2
1
  import { Injectable } from '@nestjs/common';
3
2
  import { MulterModuleOptions, MulterOptionsFactory } from '@nestjs/platform-express';
3
+ import { memoryStorage } from 'multer';
4
4
 
5
- import envConfig from '../../../config.env';
6
-
5
+ /**
6
+ * Multer configuration service using MemoryStorage
7
+ * Files are stored in memory as Buffer objects and then manually saved to GridFS
8
+ */
7
9
  @Injectable()
8
10
  export class GridFsMulterConfigService implements MulterOptionsFactory {
9
- gridFsStorage: any;
10
-
11
- constructor() {
12
- this.gridFsStorage = new GridFsStorage({
13
- file: (req, file) => {
14
- return new Promise((resolve) => {
15
- const filename = file.originalname.trim();
16
- const fileInfo = {
17
- filename,
18
- };
19
- resolve(fileInfo);
20
- });
21
- },
22
- url: envConfig.mongoose.uri,
23
- });
24
- }
25
-
26
11
  createMulterOptions(): MulterModuleOptions {
27
12
  return {
28
- storage: this.gridFsStorage,
13
+ storage: memoryStorage(),
29
14
  };
30
15
  }
31
16
  }
@@ -0,0 +1,242 @@
1
+ import { Body, Controller, Delete, Get, Param, Patch, Post, Query, UseGuards } from '@nestjs/common';
2
+ import {
3
+ ApiBearerAuth,
4
+ ApiBody,
5
+ ApiCreatedResponse,
6
+ ApiOkResponse,
7
+ ApiOperation,
8
+ ApiParam,
9
+ ApiQuery,
10
+ ApiTags,
11
+ } from '@nestjs/swagger';
12
+
13
+ import { ApiCommonErrorResponses } from '../../../core/common/decorators/common-error.decorator';
14
+ import { CurrentUser } from '../../../core/common/decorators/current-user.decorator';
15
+ import { Roles } from '../../../core/common/decorators/roles.decorator';
16
+ import { RoleEnum } from '../../../core/common/enums/role.enum';
17
+ import { ServiceOptions } from '../../../core/common/interfaces/service-options.interface';
18
+ import { AuthGuardStrategy } from '../../../core/modules/auth/auth-guard-strategy.enum';
19
+ import { AuthGuard } from '../../../core/modules/auth/guards/auth.guard';
20
+ import { UserCreateInput } from './inputs/user-create.input';
21
+ import { UserInput } from './inputs/user.input';
22
+ import { FindAndCountUsersResult } from './outputs/find-and-count-users-result.output';
23
+ import { User } from './user.model';
24
+ import { UserService } from './user.service';
25
+
26
+ /**
27
+ * Controller to handle user REST API endpoints
28
+ */
29
+ @ApiCommonErrorResponses()
30
+ @ApiTags('users')
31
+ @Controller('users')
32
+ @Roles(RoleEnum.ADMIN)
33
+ export class UserController {
34
+ /**
35
+ * Import services
36
+ */
37
+ constructor(protected readonly userService: UserService) {}
38
+
39
+ // ===========================================================================
40
+ // GET Endpoints (Queries)
41
+ // ===========================================================================
42
+
43
+ /**
44
+ * Get users (via filter)
45
+ */
46
+ @ApiBearerAuth()
47
+ @ApiOkResponse({ description: 'Users found successfully', type: [User] })
48
+ @ApiOperation({ description: 'Find users (via filter)', summary: 'Get all users' })
49
+ @Get()
50
+ @Roles(RoleEnum.ADMIN)
51
+ @UseGuards(AuthGuard(AuthGuardStrategy.JWT))
52
+ async findUsers(@CurrentUser() currentUser: User): Promise<User[]> {
53
+ const serviceOptions: ServiceOptions = {
54
+ currentUser,
55
+ };
56
+ return await this.userService.find({}, serviceOptions);
57
+ }
58
+
59
+ /**
60
+ * Get users and total count (via filter)
61
+ */
62
+ @ApiBearerAuth()
63
+ @ApiOkResponse({ description: 'Users and count found successfully', type: FindAndCountUsersResult })
64
+ @ApiOperation({
65
+ description: 'Find users and total count (via filter)',
66
+ summary: 'Get users with count',
67
+ })
68
+ @Get('count')
69
+ @Roles(RoleEnum.ADMIN)
70
+ @UseGuards(AuthGuard(AuthGuardStrategy.JWT))
71
+ async findAndCountUsers(@CurrentUser() currentUser: User): Promise<FindAndCountUsersResult> {
72
+ const serviceOptions: ServiceOptions = {
73
+ currentUser,
74
+ };
75
+ return await this.userService.findAndCount({}, serviceOptions);
76
+ }
77
+
78
+ /**
79
+ * Get verified state of user with token
80
+ */
81
+ @ApiOkResponse({ description: 'Verified state retrieved successfully', type: Boolean })
82
+ @ApiOperation({
83
+ description: 'Get verified state of user with token',
84
+ summary: 'Check if user is verified',
85
+ })
86
+ @ApiQuery({ description: 'Verification token', name: 'token', type: String })
87
+ @Get('verified-state')
88
+ @Roles(RoleEnum.S_EVERYONE)
89
+ async getVerifiedState(@Query('token') token: string): Promise<boolean> {
90
+ return await this.userService.getVerifiedState(token);
91
+ }
92
+
93
+ /**
94
+ * Get user via ID
95
+ */
96
+ @ApiBearerAuth()
97
+ @ApiOkResponse({ description: 'User found successfully', type: User })
98
+ @ApiOperation({ description: 'Get user with specified ID', summary: 'Get user by ID' })
99
+ @ApiParam({ description: 'User ID', name: 'id', type: String })
100
+ @Get(':id')
101
+ @Roles(RoleEnum.S_USER)
102
+ @UseGuards(AuthGuard(AuthGuardStrategy.JWT))
103
+ async getUser(@CurrentUser() currentUser: User, @Param('id') id: string): Promise<User> {
104
+ const serviceOptions: ServiceOptions = {
105
+ currentUser,
106
+ roles: [RoleEnum.ADMIN, RoleEnum.S_CREATOR, RoleEnum.S_SELF],
107
+ };
108
+ return await this.userService.get(id, serviceOptions);
109
+ }
110
+
111
+ // ===========================================================================
112
+ // POST Endpoints (Mutations)
113
+ // ===========================================================================
114
+
115
+ /**
116
+ * Create new user
117
+ */
118
+ @ApiBearerAuth()
119
+ @ApiBody({ type: UserCreateInput })
120
+ @ApiCreatedResponse({ description: 'User created successfully', type: User })
121
+ @ApiOperation({ description: 'Create a new user', summary: 'Create user' })
122
+ @Post()
123
+ @Roles(RoleEnum.ADMIN)
124
+ @UseGuards(AuthGuard(AuthGuardStrategy.JWT))
125
+ async createUser(@CurrentUser() currentUser: User, @Body() input: UserCreateInput): Promise<User> {
126
+ const serviceOptions: ServiceOptions = {
127
+ currentUser,
128
+ inputType: UserCreateInput,
129
+ };
130
+ return await this.userService.create(input, serviceOptions);
131
+ }
132
+
133
+ /**
134
+ * Request new password for user with email
135
+ */
136
+ @ApiBody({
137
+ description: 'User email',
138
+ schema: {
139
+ properties: {
140
+ email: { type: 'string' },
141
+ },
142
+ type: 'object',
143
+ },
144
+ })
145
+ @ApiOkResponse({ description: 'Password reset email sent successfully', type: Boolean })
146
+ @ApiOperation({
147
+ description: 'Request new password for user with email',
148
+ summary: 'Request password reset',
149
+ })
150
+ @Post('password/reset-request')
151
+ @Roles(RoleEnum.S_EVERYONE)
152
+ async requestPasswordResetMail(@Body('email') email: string): Promise<boolean> {
153
+ return !!(await this.userService.sendPasswordResetMail(email));
154
+ }
155
+
156
+ /**
157
+ * Set new password for user with token
158
+ */
159
+ @ApiBody({
160
+ description: 'Password reset data',
161
+ schema: {
162
+ properties: {
163
+ password: { type: 'string' },
164
+ token: { type: 'string' },
165
+ },
166
+ type: 'object',
167
+ },
168
+ })
169
+ @ApiOkResponse({ description: 'Password reset successfully', type: Boolean })
170
+ @ApiOperation({ description: 'Set new password for user with token', summary: 'Reset password' })
171
+ @Post('password/reset')
172
+ @Roles(RoleEnum.S_EVERYONE)
173
+ async resetPassword(@Body('token') token: string, @Body('password') password: string): Promise<boolean> {
174
+ return !!(await this.userService.resetPassword(token, password));
175
+ }
176
+
177
+ /**
178
+ * Verify user with email
179
+ */
180
+ @ApiBody({
181
+ description: 'Verification token',
182
+ schema: {
183
+ properties: {
184
+ token: { type: 'string' },
185
+ },
186
+ type: 'object',
187
+ },
188
+ })
189
+ @ApiOkResponse({ description: 'User verified successfully', type: Boolean })
190
+ @ApiOperation({ description: 'Verify user with email', summary: 'Verify user' })
191
+ @Post('verify')
192
+ @Roles(RoleEnum.S_EVERYONE)
193
+ async verifyUser(@Body('token') token: string): Promise<boolean> {
194
+ return !!(await this.userService.verify(token));
195
+ }
196
+
197
+ // ===========================================================================
198
+ // PATCH Endpoints (Updates)
199
+ // ===========================================================================
200
+
201
+ /**
202
+ * Update existing user
203
+ */
204
+ @ApiBearerAuth()
205
+ @ApiBody({ type: UserInput })
206
+ @ApiOkResponse({ description: 'User updated successfully', type: User })
207
+ @ApiOperation({ description: 'Update existing user', summary: 'Update user' })
208
+ @ApiParam({ description: 'User ID', name: 'id', type: String })
209
+ @Patch(':id')
210
+ @Roles(RoleEnum.S_USER)
211
+ @UseGuards(AuthGuard(AuthGuardStrategy.JWT))
212
+ async updateUser(@CurrentUser() currentUser: User, @Param('id') id: string, @Body() input: UserInput): Promise<User> {
213
+ const serviceOptions: ServiceOptions = {
214
+ currentUser,
215
+ inputType: UserInput,
216
+ roles: [RoleEnum.ADMIN, RoleEnum.S_CREATOR, RoleEnum.S_SELF],
217
+ };
218
+ return await this.userService.update(id, input, serviceOptions);
219
+ }
220
+
221
+ // ===========================================================================
222
+ // DELETE Endpoints
223
+ // ===========================================================================
224
+
225
+ /**
226
+ * Delete existing user
227
+ */
228
+ @ApiBearerAuth()
229
+ @ApiOkResponse({ description: 'User deleted successfully', type: User })
230
+ @ApiOperation({ description: 'Delete existing user', summary: 'Delete user' })
231
+ @ApiParam({ description: 'User ID', name: 'id', type: String })
232
+ @Delete(':id')
233
+ @Roles(RoleEnum.S_USER)
234
+ @UseGuards(AuthGuard(AuthGuardStrategy.JWT))
235
+ async deleteUser(@CurrentUser() currentUser: User, @Param('id') id: string): Promise<User> {
236
+ const serviceOptions: ServiceOptions = {
237
+ currentUser,
238
+ roles: [RoleEnum.ADMIN, RoleEnum.S_CREATOR, RoleEnum.S_SELF],
239
+ };
240
+ return await this.userService.delete(id, serviceOptions);
241
+ }
242
+ }
@@ -1,10 +1,11 @@
1
- import { Field, ObjectType } from '@nestjs/graphql';
1
+ import { ObjectType } from '@nestjs/graphql';
2
2
  import { Schema as MongooseSchema, Prop, SchemaFactory } from '@nestjs/mongoose';
3
- import { IsEmail, IsOptional } from 'class-validator';
3
+ import { IsEmail } from 'class-validator';
4
4
  import { Document, Schema } from 'mongoose';
5
5
 
6
6
  import { Restricted } from '../../../core/common/decorators/restricted.decorator';
7
7
  import { Translatable } from '../../../core/common/decorators/translatable.decorator';
8
+ import { UnifiedField } from '../../../core/common/decorators/unified-field.decorator';
8
9
  import { RoleEnum } from '../../../core/common/enums/role.enum';
9
10
  import { CoreUserModel } from '../../../core/modules/user/core-user.model';
10
11
  import { PersistenceModel } from '../../common/models/persistence.model';
@@ -25,9 +26,12 @@ export class User extends CoreUserModel implements PersistenceModel {
25
26
  /**
26
27
  * URL to avatar file of the user
27
28
  */
28
- @Field({ description: 'URL to avatar file of the user', nullable: true })
29
- @Prop()
30
- @Restricted(RoleEnum.S_EVERYONE)
29
+ @UnifiedField({
30
+ description: 'URL to avatar file of the user',
31
+ isOptional: true,
32
+ mongoose: true,
33
+ roles: RoleEnum.S_EVERYONE,
34
+ })
31
35
  avatar: string = undefined;
32
36
 
33
37
  /**
@@ -35,39 +39,47 @@ export class User extends CoreUserModel implements PersistenceModel {
35
39
  *
36
40
  * Not set when created by system
37
41
  */
38
- @Field(() => String, {
42
+ @UnifiedField({
39
43
  description: 'ID of the user who created the object',
40
- nullable: true,
44
+ isOptional: true,
45
+ mongoose: { ref: 'User', type: Schema.Types.ObjectId },
46
+ roles: RoleEnum.S_EVERYONE,
47
+ type: () => String,
41
48
  })
42
- @Prop({ ref: 'User', type: Schema.Types.ObjectId })
43
- @Restricted(RoleEnum.S_EVERYONE)
44
49
  createdBy: string = undefined;
45
50
 
46
51
  /**
47
52
  * E-Mail address of the user
48
53
  */
49
- @Field({ description: 'Email of the user', nullable: true })
50
- @IsEmail()
51
- @Prop({ lowercase: true, trim: true, unique: true })
52
- @Restricted(RoleEnum.S_EVERYONE)
54
+ @UnifiedField({
55
+ description: 'Email of the user',
56
+ isOptional: true,
57
+ mongoose: { lowercase: true, trim: true, unique: true },
58
+ roles: RoleEnum.S_EVERYONE,
59
+ validator: () => [IsEmail()],
60
+ })
53
61
  override email: string = undefined;
54
62
 
55
63
  /**
56
64
  * Roles of the user
57
65
  */
58
- @Field(() => [String], { description: 'Roles of the user', nullable: true })
59
- @IsOptional()
60
- @Prop([String])
61
- @Restricted(RoleEnum.S_EVERYONE)
66
+ @UnifiedField({
67
+ description: 'Roles of the user',
68
+ isArray: true,
69
+ isOptional: true,
70
+ mongoose: [String],
71
+ roles: RoleEnum.S_EVERYONE,
72
+ type: () => String,
73
+ })
62
74
  override roles: string[] = undefined;
63
75
 
64
- @Field(() => String, {
76
+ @Translatable()
77
+ @UnifiedField({
65
78
  description: 'Job title of user',
66
- nullable: true,
79
+ isOptional: true,
80
+ mongoose: true,
81
+ roles: RoleEnum.S_EVERYONE,
67
82
  })
68
- @Prop()
69
- @Restricted(RoleEnum.S_EVERYONE)
70
- @Translatable()
71
83
  jobTitle?: string = undefined;
72
84
 
73
85
  /**
@@ -75,12 +87,13 @@ export class User extends CoreUserModel implements PersistenceModel {
75
87
  *
76
88
  * Not set when updated by system
77
89
  */
78
- @Field(() => String, {
90
+ @UnifiedField({
79
91
  description: 'ID of the user who last updated the object',
80
- nullable: true,
92
+ isOptional: true,
93
+ mongoose: { ref: 'User', type: Schema.Types.ObjectId },
94
+ roles: RoleEnum.S_USER,
95
+ type: () => String,
81
96
  })
82
- @Prop({ ref: 'User', type: Schema.Types.ObjectId })
83
- @Restricted(RoleEnum.S_USER)
84
97
  updatedBy: string = undefined;
85
98
 
86
99
  @Prop({ default: {}, type: Schema.Types.Mixed })
@@ -4,6 +4,7 @@ import { PubSub } from 'graphql-subscriptions';
4
4
 
5
5
  import { ConfigService } from '../../../core/common/services/config.service';
6
6
  import { AvatarController } from './avatar.controller';
7
+ import { UserController } from './user.controller';
7
8
  import { User, UserSchema } from './user.model';
8
9
  import { UserResolver } from './user.resolver';
9
10
  import { UserService } from './user.service';
@@ -12,7 +13,7 @@ import { UserService } from './user.service';
12
13
  * User module
13
14
  */
14
15
  @Module({
15
- controllers: [AvatarController],
16
+ controllers: [AvatarController, UserController],
16
17
  exports: [MongooseModule, UserResolver, UserService, 'USER_CLASS'],
17
18
  imports: [MongooseModule.forFeature([{ name: User.name, schema: UserSchema }])],
18
19
  providers: [
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Type definitions for graphql-upload
3
+ * graphql-upload uses deep imports, so we declare types for the specific modules
4
+ */
5
+
6
+ declare module 'graphql-upload/graphqlUploadExpress.js' {
7
+ import { RequestHandler } from 'express';
8
+
9
+ interface GraphQLUploadOptions {
10
+ maxFiles?: number;
11
+ maxFileSize?: number;
12
+ }
13
+
14
+ function graphqlUploadExpress(options?: GraphQLUploadOptions): RequestHandler;
15
+
16
+ export = graphqlUploadExpress;
17
+ }
18
+
19
+ declare module 'graphql-upload/GraphQLUpload.js' {
20
+ import { GraphQLScalarType } from 'graphql';
21
+
22
+ const GraphQLUpload: GraphQLScalarType;
23
+
24
+ export = GraphQLUpload;
25
+ }