@lenne.tech/nest-server 11.1.5 → 11.1.7

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 (36) hide show
  1. package/dist/core/common/decorators/translatable.decorator.d.ts +1 -0
  2. package/dist/core/common/decorators/translatable.decorator.js +17 -0
  3. package/dist/core/common/decorators/translatable.decorator.js.map +1 -1
  4. package/dist/core/common/decorators/unified-field.decorator.d.ts +1 -1
  5. package/dist/core/common/decorators/unified-field.decorator.js +9 -1
  6. package/dist/core/common/decorators/unified-field.decorator.js.map +1 -1
  7. package/dist/core/modules/user/inputs/core-user.input.js +1 -1
  8. package/dist/core/modules/user/inputs/core-user.input.js.map +1 -1
  9. package/dist/server/modules/user/inputs/user-create.input.d.ts +1 -0
  10. package/dist/server/modules/user/inputs/user-create.input.js +17 -0
  11. package/dist/server/modules/user/inputs/user-create.input.js.map +1 -1
  12. package/dist/server/modules/user/inputs/user.input.d.ts +1 -0
  13. package/dist/server/modules/user/inputs/user.input.js +17 -0
  14. package/dist/server/modules/user/inputs/user.input.js.map +1 -1
  15. package/dist/server/modules/user/outputs/find-and-count-users-result.output.js +1 -1
  16. package/dist/server/modules/user/outputs/find-and-count-users-result.output.js.map +1 -1
  17. package/dist/server/modules/user/user.model.d.ts +2 -0
  18. package/dist/server/modules/user/user.model.js +18 -0
  19. package/dist/server/modules/user/user.model.js.map +1 -1
  20. package/dist/server/modules/user/user.service.d.ts +1 -0
  21. package/dist/server/modules/user/user.service.js +8 -0
  22. package/dist/server/modules/user/user.service.js.map +1 -1
  23. package/dist/test/test.helper.d.ts +1 -0
  24. package/dist/test/test.helper.js +5 -1
  25. package/dist/test/test.helper.js.map +1 -1
  26. package/dist/tsconfig.build.tsbuildinfo +1 -1
  27. package/package.json +1 -1
  28. package/src/core/common/decorators/translatable.decorator.ts +25 -0
  29. package/src/core/common/decorators/unified-field.decorator.ts +16 -16
  30. package/src/core/modules/user/inputs/core-user.input.ts +1 -1
  31. package/src/server/modules/user/inputs/user-create.input.ts +9 -1
  32. package/src/server/modules/user/inputs/user.input.ts +9 -1
  33. package/src/server/modules/user/outputs/find-and-count-users-result.output.ts +1 -1
  34. package/src/server/modules/user/user.model.ts +14 -0
  35. package/src/server/modules/user/user.service.ts +9 -0
  36. package/src/test/test.helper.ts +11 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lenne.tech/nest-server",
3
- "version": "11.1.5",
3
+ "version": "11.1.7",
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",
@@ -22,3 +22,28 @@ export function Translatable(): PropertyDecorator {
22
22
  Reflect.defineMetadata(TRANSLATABLE_KEY, [...existingProperties, propertyKey], target.constructor);
23
23
  };
24
24
  }
25
+
26
+ export function updateLanguage<T extends Record<string, any>, K extends readonly (keyof T)[]>(
27
+ language: string,
28
+ input: any,
29
+ oldValue: T,
30
+ translatableFields: string[],
31
+ ): T {
32
+ const changedFields: Partial<Pick<T, K[number]>> = {};
33
+
34
+ for (const key of translatableFields) {
35
+ const k = key as keyof T;
36
+
37
+ if (input[k] !== oldValue[k] && input[k] !== undefined) {
38
+ changedFields[k] = input[k];
39
+ input[k] = oldValue[k] as T[typeof k];
40
+ }
41
+ }
42
+
43
+ input._translations = input._translations ?? {};
44
+ input._translations[language] = {
45
+ ...(input._translations[language] ?? {}),
46
+ ...changedFields,
47
+ };
48
+ return input;
49
+ }
@@ -22,17 +22,6 @@ import { RoleEnum } from '../enums/role.enum';
22
22
  import { Restricted, RestrictedType } from './restricted.decorator';
23
23
 
24
24
  export interface UnifiedFieldOptions {
25
- /**
26
- * Indicates whether the property is an array.
27
- *
28
- * This value is typically determined automatically based on the property type or metadata.
29
- *
30
- * However, cases involving complex or dynamic type definitions (e.g., union types, generics, or factory functions)
31
- * may result in inaccurate detection.
32
- *
33
- * When in doubt, explicitly set this property to ensure correct behavior.
34
- */
35
- array?: boolean;
36
25
  /** Description used for both Swagger & Gql */
37
26
  description?: string;
38
27
  /** Enum for class-validator */
@@ -41,10 +30,12 @@ export interface UnifiedFieldOptions {
41
30
  example?: any;
42
31
  /** Options for graphql */
43
32
  gqlOptions?: FieldOptions;
44
- /** Type if Swagger & Gql types arent compatible */
33
+ /** Type if Swagger & Gql types aren't compatible */
45
34
  gqlType?: GraphQLScalarType | (new (...args: any[]) => any) | Record<number | string, number | string>;
46
35
  /** If the property is Any (skips all Validation) */
47
36
  isAny?: boolean;
37
+ /** Indicates whether the property is an array. */
38
+ isArray?: boolean;
48
39
  /** Default: false */
49
40
  isOptional?: boolean;
50
41
  /** Restricted roles */
@@ -75,9 +66,9 @@ export function UnifiedField(opts: UnifiedFieldOptions = {}): PropertyDecorator
75
66
  return (target: any, propertyKey: string | symbol) => {
76
67
  const metadataType = Reflect.getMetadata('design:type', target, propertyKey);
77
68
  const userType = opts.type;
78
- const isArrayField = opts.array === true || metadataType === Array;
69
+ const isArrayField = opts.isArray === true || metadataType === Array;
79
70
 
80
- // Throwing because metatype only returns the generic array, but not the type of the array items
71
+ // Throwing because meta type only returns the generic array, but not the type of the array items
81
72
  if (metadataType === Array && !userType) {
82
73
  throw new Error(`Array field '${String(propertyKey)}' of '${String(target)}' must have an explicit type`);
83
74
  }
@@ -129,12 +120,21 @@ export function UnifiedField(opts: UnifiedFieldOptions = {}): PropertyDecorator
129
120
  swaggerOpts.required = true;
130
121
  }
131
122
 
132
- // Description
123
+ // Set type for swagger
124
+ if (baseType) {
125
+ if (opts.enum) {
126
+ swaggerOpts.type = () => String;
127
+ } else {
128
+ swaggerOpts.type = () => resolvedTypeFn();
129
+ }
130
+ }
131
+
132
+ // Set description
133
133
  const defaultDesc = opts.description ?? `${String(propertyKey)} of ${target.constructor.name}`;
134
134
  gqlOpts.description = gqlOpts.description ?? defaultDesc;
135
135
  swaggerOpts.description = swaggerOpts.description ?? defaultDesc;
136
136
 
137
- // Swagger example
137
+ // Set swagger example
138
138
  if (opts.example !== undefined) {
139
139
  swaggerOpts.example = swaggerOpts.example ?? opts.example;
140
140
  }
@@ -53,7 +53,7 @@ export abstract class CoreUserInput extends CoreInput {
53
53
  */
54
54
  @Restricted({ processType: ProcessType.INPUT, roles: RoleEnum.ADMIN })
55
55
  @UnifiedField({
56
- array: true,
56
+ isArray: true,
57
57
  isOptional: true,
58
58
  type: String,
59
59
  })
@@ -1,4 +1,5 @@
1
- import { InputType } from '@nestjs/graphql';
1
+ import { Field, InputType } from '@nestjs/graphql';
2
+ import { IsOptional } from 'class-validator';
2
3
 
3
4
  import { Restricted } from '../../../../core/common/decorators/restricted.decorator';
4
5
  import { RoleEnum } from '../../../../core/common/enums/role.enum';
@@ -11,4 +12,11 @@ import { CoreUserCreateInput } from '../../../../core/modules/user/inputs/core-u
11
12
  @Restricted(RoleEnum.ADMIN)
12
13
  export class UserCreateInput extends CoreUserCreateInput {
13
14
  // Extend UserCreateInput here
15
+ @Field(() => String, {
16
+ description: 'Job Title of the user',
17
+ nullable: true,
18
+ })
19
+ @IsOptional()
20
+ @Restricted(RoleEnum.ADMIN)
21
+ jobTitle?: string = undefined;
14
22
  }
@@ -1,4 +1,5 @@
1
- import { InputType } from '@nestjs/graphql';
1
+ import { Field, InputType } from '@nestjs/graphql';
2
+ import { IsOptional } from 'class-validator';
2
3
 
3
4
  import { Restricted } from '../../../../core/common/decorators/restricted.decorator';
4
5
  import { RoleEnum } from '../../../../core/common/enums/role.enum';
@@ -11,4 +12,11 @@ import { CoreUserInput } from '../../../../core/modules/user/inputs/core-user.in
11
12
  @Restricted(RoleEnum.ADMIN)
12
13
  export class UserInput extends CoreUserInput {
13
14
  // Extend UserInput here
15
+ @Field(() => String, {
16
+ description: 'Job Title of the user',
17
+ nullable: true,
18
+ })
19
+ @IsOptional()
20
+ @Restricted(RoleEnum.ADMIN)
21
+ jobTitle?: string = undefined;
14
22
  }
@@ -9,8 +9,8 @@ import { User } from '../user.model';
9
9
  @Restricted(RoleEnum.ADMIN)
10
10
  export class FindAndCountUsersResult {
11
11
  @UnifiedField({
12
- array: true,
13
12
  description: 'Found users',
13
+ isArray: true,
14
14
  isOptional: true,
15
15
  type: () => User,
16
16
  })
@@ -4,6 +4,7 @@ import { IsEmail, IsOptional } from 'class-validator';
4
4
  import { Document, Schema } from 'mongoose';
5
5
 
6
6
  import { Restricted } from '../../../core/common/decorators/restricted.decorator';
7
+ import { Translatable } from '../../../core/common/decorators/translatable.decorator';
7
8
  import { RoleEnum } from '../../../core/common/enums/role.enum';
8
9
  import { CoreUserModel } from '../../../core/modules/user/core-user.model';
9
10
  import { PersistenceModel } from '../../common/models/persistence.model';
@@ -60,6 +61,15 @@ export class User extends CoreUserModel implements PersistenceModel {
60
61
  @Restricted(RoleEnum.S_EVERYONE)
61
62
  override roles: string[] = undefined;
62
63
 
64
+ @Field(() => String, {
65
+ description: 'Job title of user',
66
+ nullable: true,
67
+ })
68
+ @Prop()
69
+ @Restricted(RoleEnum.S_EVERYONE)
70
+ @Translatable()
71
+ jobTitle?: string = undefined;
72
+
63
73
  /**
64
74
  * ID of the user who updated the object
65
75
  *
@@ -73,6 +83,10 @@ export class User extends CoreUserModel implements PersistenceModel {
73
83
  @Restricted(RoleEnum.S_USER)
74
84
  updatedBy: string = undefined;
75
85
 
86
+ @Prop({ default: {}, type: Schema.Types.Mixed })
87
+ @Restricted(RoleEnum.S_EVERYONE)
88
+ _translations?: Record<string, Partial<User>> = undefined;
89
+
76
90
  // ===================================================================================================================
77
91
  // Methods
78
92
  // ===================================================================================================================
@@ -4,6 +4,7 @@ import fs = require('fs');
4
4
  import { PubSub } from 'graphql-subscriptions';
5
5
  import { Model } from 'mongoose';
6
6
 
7
+ import { getTranslatablePropertyKeys, updateLanguage } from '../../../core/common/decorators/translatable.decorator';
7
8
  import { ServiceOptions } from '../../../core/common/interfaces/service-options.interface';
8
9
  import { ConfigService } from '../../../core/common/services/config.service';
9
10
  import { EmailService } from '../../../core/common/services/email.service';
@@ -62,6 +63,14 @@ export class UserService extends CoreUserService<User, UserInput, UserCreateInpu
62
63
  return user;
63
64
  }
64
65
 
66
+ override async update(id: string, input: UserInput, serviceOptions?: ServiceOptions): Promise<User> {
67
+ const dbObject = await super.get(id, serviceOptions);
68
+ if (serviceOptions.language && serviceOptions.language !== 'de') {
69
+ input = updateLanguage(serviceOptions.language, input, dbObject as UserInput, getTranslatablePropertyKeys(User));
70
+ }
71
+ return super.update(id, input, serviceOptions);
72
+ }
73
+
65
74
  /**
66
75
  * Request password reset mail
67
76
  */
@@ -83,6 +83,11 @@ export interface TestGraphQLOptions {
83
83
  */
84
84
  countOfSubscriptionMessages?: number;
85
85
 
86
+ /**
87
+ * Language selected by user
88
+ */
89
+ language?: string;
90
+
86
91
  /**
87
92
  * Output information in the console
88
93
  */
@@ -225,6 +230,7 @@ export class TestHelper {
225
230
  const config = {
226
231
  convertEnums: true,
227
232
  countOfSubscriptionMessages: 1,
233
+ language: null,
228
234
  log: false,
229
235
  logError: false,
230
236
  prepareArguments: true,
@@ -234,7 +240,7 @@ export class TestHelper {
234
240
  };
235
241
 
236
242
  // Init vars
237
- const { log, logError, statusCode, token, variables } = config;
243
+ const { language, log, logError, statusCode, token, variables } = config;
238
244
 
239
245
  // Init
240
246
  let query = '';
@@ -337,6 +343,10 @@ export class TestHelper {
337
343
  requestConfig.headers = { authorization: `Bearer ${token}` };
338
344
  }
339
345
 
346
+ if (language) {
347
+ requestConfig.headers = { ...requestConfig.headers, 'accept-language': language };
348
+ }
349
+
340
350
  // Get response
341
351
  const response = await this.getResponse(token, requestConfig, statusCode, log, logError, variables);
342
352