@lenne.tech/nest-server 11.0.1 → 11.1.1

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 (121) hide show
  1. package/dist/core/common/args/filter.args.js +6 -8
  2. package/dist/core/common/args/filter.args.js.map +1 -1
  3. package/dist/core/common/args/pagination.args.js +16 -16
  4. package/dist/core/common/args/pagination.args.js.map +1 -1
  5. package/dist/core/common/decorators/restricted.decorator.js +1 -1
  6. package/dist/core/common/decorators/restricted.decorator.js.map +1 -1
  7. package/dist/core/common/decorators/unified-field.decorator.d.ts +25 -0
  8. package/dist/core/common/decorators/unified-field.decorator.js +167 -0
  9. package/dist/core/common/decorators/unified-field.decorator.js.map +1 -0
  10. package/dist/core/common/helpers/config.helper.js +1 -1
  11. package/dist/core/common/helpers/config.helper.js.map +1 -1
  12. package/dist/core/common/helpers/db.helper.js +1 -1
  13. package/dist/core/common/helpers/db.helper.js.map +1 -1
  14. package/dist/core/common/helpers/graphql.helper.js.map +1 -1
  15. package/dist/core/common/helpers/input.helper.js +2 -2
  16. package/dist/core/common/helpers/input.helper.js.map +1 -1
  17. package/dist/core/common/helpers/service.helper.js +33 -12
  18. package/dist/core/common/helpers/service.helper.js.map +1 -1
  19. package/dist/core/common/inputs/combined-filter.input.js +8 -4
  20. package/dist/core/common/inputs/combined-filter.input.js.map +1 -1
  21. package/dist/core/common/inputs/filter.input.js +10 -7
  22. package/dist/core/common/inputs/filter.input.js.map +1 -1
  23. package/dist/core/common/inputs/single-filter.input.js +26 -18
  24. package/dist/core/common/inputs/single-filter.input.js.map +1 -1
  25. package/dist/core/common/inputs/sort.input.js +10 -4
  26. package/dist/core/common/inputs/sort.input.js.map +1 -1
  27. package/dist/core/common/models/core-persistence.model.d.ts +2 -0
  28. package/dist/core/common/models/core-persistence.model.js +40 -11
  29. package/dist/core/common/models/core-persistence.model.js.map +1 -1
  30. package/dist/core/common/pipes/map-and-validate.pipe.js +12 -3
  31. package/dist/core/common/pipes/map-and-validate.pipe.js.map +1 -1
  32. package/dist/core/common/services/brevo.service.js +1 -1
  33. package/dist/core/common/services/brevo.service.js.map +1 -1
  34. package/dist/core/common/services/config.service.js +1 -1
  35. package/dist/core/common/services/config.service.js.map +1 -1
  36. package/dist/core/common/services/email.service.js +1 -1
  37. package/dist/core/common/services/email.service.js.map +1 -1
  38. package/dist/core/common/services/model-doc.service.js +1 -1
  39. package/dist/core/common/services/model-doc.service.js.map +1 -1
  40. package/dist/core/common/services/module.service.js +1 -1
  41. package/dist/core/common/services/module.service.js.map +1 -1
  42. package/dist/core/common/services/template.service.js +2 -2
  43. package/dist/core/common/services/template.service.js.map +1 -1
  44. package/dist/core/modules/auth/core-auth.model.js +10 -11
  45. package/dist/core/modules/auth/core-auth.model.js.map +1 -1
  46. package/dist/core/modules/auth/guards/auth.guard.js +1 -1
  47. package/dist/core/modules/auth/guards/auth.guard.js.map +1 -1
  48. package/dist/core/modules/auth/inputs/core-auth-sign-in.input.js +25 -21
  49. package/dist/core/modules/auth/inputs/core-auth-sign-in.input.js.map +1 -1
  50. package/dist/core/modules/auth/services/core-auth.service.js +1 -1
  51. package/dist/core/modules/auth/services/core-auth.service.js.map +1 -1
  52. package/dist/core/modules/health-check/core-health-check-result.model.js +18 -11
  53. package/dist/core/modules/health-check/core-health-check-result.model.js.map +1 -1
  54. package/dist/core/modules/user/core-user.service.js +2 -2
  55. package/dist/core/modules/user/core-user.service.js.map +1 -1
  56. package/dist/core/modules/user/inputs/core-user-create.input.js +6 -3
  57. package/dist/core/modules/user/inputs/core-user-create.input.js.map +1 -1
  58. package/dist/core/modules/user/inputs/core-user.input.js +32 -18
  59. package/dist/core/modules/user/inputs/core-user.input.js.map +1 -1
  60. package/dist/core.module.js +1 -1
  61. package/dist/core.module.js.map +1 -1
  62. package/dist/index.d.ts +1 -0
  63. package/dist/index.js +1 -0
  64. package/dist/index.js.map +1 -1
  65. package/dist/main.js +2 -2
  66. package/dist/main.js.map +1 -1
  67. package/dist/server/common/models/persistence.model.js +1 -1
  68. package/dist/server/common/models/persistence.model.js.map +1 -1
  69. package/dist/server/modules/auth/inputs/auth-sign-up.input.js +11 -4
  70. package/dist/server/modules/auth/inputs/auth-sign-up.input.js.map +1 -1
  71. package/dist/server/modules/file/file.resolver.js +2 -2
  72. package/dist/server/modules/file/file.resolver.js.map +1 -1
  73. package/dist/server/modules/user/outputs/find-and-count-users-result.output.js +11 -4
  74. package/dist/server/modules/user/outputs/find-and-count-users-result.output.js.map +1 -1
  75. package/dist/server/modules/user/user.model.js +2 -0
  76. package/dist/server/modules/user/user.model.js.map +1 -1
  77. package/dist/server/modules/user/user.service.js +1 -1
  78. package/dist/server/modules/user/user.service.js.map +1 -1
  79. package/dist/test/test.helper.js +1 -1
  80. package/dist/test/test.helper.js.map +1 -1
  81. package/dist/tsconfig.build.tsbuildinfo +1 -1
  82. package/package.json +25 -25
  83. package/src/core/common/args/filter.args.ts +7 -10
  84. package/src/core/common/args/pagination.args.ts +17 -17
  85. package/src/core/common/decorators/restricted.decorator.ts +1 -2
  86. package/src/core/common/decorators/unified-field.decorator.ts +269 -0
  87. package/src/core/common/helpers/config.helper.ts +1 -4
  88. package/src/core/common/helpers/db.helper.ts +1 -2
  89. package/src/core/common/helpers/graphql.helper.ts +0 -1
  90. package/src/core/common/helpers/input.helper.ts +2 -3
  91. package/src/core/common/helpers/service.helper.ts +39 -14
  92. package/src/core/common/inputs/combined-filter.input.ts +9 -5
  93. package/src/core/common/inputs/filter.input.ts +14 -11
  94. package/src/core/common/inputs/single-filter.input.ts +28 -19
  95. package/src/core/common/inputs/sort.input.ts +11 -5
  96. package/src/core/common/models/core-persistence.model.ts +38 -12
  97. package/src/core/common/pipes/map-and-validate.pipe.ts +15 -4
  98. package/src/core/common/services/brevo.service.ts +1 -2
  99. package/src/core/common/services/config.service.ts +1 -2
  100. package/src/core/common/services/email.service.ts +1 -2
  101. package/src/core/common/services/model-doc.service.ts +1 -2
  102. package/src/core/common/services/module.service.ts +1 -2
  103. package/src/core/common/services/template.service.ts +2 -3
  104. package/src/core/modules/auth/core-auth.model.ts +11 -12
  105. package/src/core/modules/auth/guards/auth.guard.ts +1 -2
  106. package/src/core/modules/auth/inputs/core-auth-sign-in.input.ts +27 -23
  107. package/src/core/modules/auth/services/core-auth.service.ts +1 -2
  108. package/src/core/modules/health-check/core-health-check-result.model.ts +19 -12
  109. package/src/core/modules/user/core-user.service.ts +2 -3
  110. package/src/core/modules/user/inputs/core-user-create.input.ts +7 -4
  111. package/src/core/modules/user/inputs/core-user.input.ts +34 -20
  112. package/src/core.module.ts +1 -2
  113. package/src/index.ts +1 -0
  114. package/src/main.ts +2 -3
  115. package/src/server/common/models/persistence.model.ts +1 -2
  116. package/src/server/modules/auth/inputs/auth-sign-up.input.ts +12 -5
  117. package/src/server/modules/file/file.resolver.ts +2 -3
  118. package/src/server/modules/user/outputs/find-and-count-users-result.output.ts +12 -5
  119. package/src/server/modules/user/user.model.ts +2 -0
  120. package/src/server/modules/user/user.service.ts +1 -2
  121. package/src/test/test.helper.ts +2 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lenne.tech/nest-server",
3
- "version": "11.0.1",
3
+ "version": "11.1.1",
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,8 +22,8 @@
22
22
  "docs:ci": "ts-node ./scripts/init-server.ts && npm run docs:bootstrap && compodoc -p tsconfig.json",
23
23
  "format": "prettier --write 'src/**/*.ts'",
24
24
  "format:staged": "pretty-quick --staged",
25
- "lint": "eslint '{src,apps,libs,test}/**/*.{ts,js}' --cache",
26
- "lint:fix": "eslint '{src,apps,libs,test}/**/*.{ts,js}' --fix --cache",
25
+ "lint": "eslint '{src,apps,libs,tests}/**/*.{ts,js}' --cache",
26
+ "lint:fix": "eslint '{src,apps,libs,tests}/**/*.{ts,js}' --fix --cache",
27
27
  "prestart:prod": "npm run build",
28
28
  "reinit": "rimraf package-lock.json && rimraf node_modules && npm i && npm run lint && npm run test:e2e && npm run test:ci && npm run build",
29
29
  "reinit:clean": "rimraf package-lock.json && rimraf node_modules && npm cache clean --force && npm i && npm run test:e2e && npm run build",
@@ -70,26 +70,26 @@
70
70
  "@lenne.tech/mongoose-gridfs": "1.4.2",
71
71
  "@lenne.tech/multer-gridfs-storage": "5.0.6",
72
72
  "@nestjs/apollo": "13.1.0",
73
- "@nestjs/common": "11.0.17",
74
- "@nestjs/core": "11.0.17",
73
+ "@nestjs/common": "11.1.0",
74
+ "@nestjs/core": "11.1.0",
75
75
  "@nestjs/graphql": "13.1.0",
76
76
  "@nestjs/jwt": "11.0.0",
77
77
  "@nestjs/mongoose": "11.0.3",
78
78
  "@nestjs/passport": "11.0.5",
79
- "@nestjs/platform-express": "11.0.17",
80
- "@nestjs/schedule": "5.0.1",
81
- "@nestjs/swagger": "11.1.3",
79
+ "@nestjs/platform-express": "11.1.0",
80
+ "@nestjs/schedule": "6.0.0",
81
+ "@nestjs/swagger": "11.2.0",
82
82
  "@nestjs/terminus": "11.0.0",
83
83
  "apollo-server-core": "3.13.0",
84
84
  "apollo-server-express": "3.13.0",
85
85
  "bcrypt": "5.1.1",
86
86
  "class-transformer": "0.5.1",
87
- "class-validator": "0.14.1",
87
+ "class-validator": "0.14.2",
88
88
  "compression": "1.8.0",
89
89
  "cookie-parser": "1.4.7",
90
90
  "dotenv": "16.5.0",
91
91
  "ejs": "3.1.10",
92
- "graphql": "16.10.0",
92
+ "graphql": "16.11.0",
93
93
  "graphql-query-complexity": "1.1.0",
94
94
  "graphql-subscriptions": "3.0.0",
95
95
  "graphql-upload": "15.0.2",
@@ -97,12 +97,12 @@
97
97
  "json-to-graphql-query": "2.3.0",
98
98
  "light-my-request": "6.6.0",
99
99
  "lodash": "4.17.21",
100
- "mongodb": "6.15.0",
101
- "mongoose": "7.8.6",
100
+ "mongodb": "6.16.0",
101
+ "mongoose": "7.8.7",
102
102
  "multer": "1.4.5-lts.2",
103
103
  "node-mailjet": "6.0.8",
104
- "nodemailer": "6.10.1",
105
- "nodemon": "3.1.9",
104
+ "nodemailer": "7.0.3",
105
+ "nodemon": "3.1.10",
106
106
  "passport": "0.7.0",
107
107
  "passport-jwt": "4.0.1",
108
108
  "reflect-metadata": "0.2.2",
@@ -114,13 +114,13 @@
114
114
  "devDependencies": {
115
115
  "@babel/plugin-proposal-private-methods": "7.18.6",
116
116
  "@compodoc/compodoc": "1.1.26",
117
- "@lenne.tech/eslint-config-ts": "1.0.8",
118
- "@nestjs/cli": "11.0.6",
117
+ "@lenne.tech/eslint-config-ts": "2.0.1",
118
+ "@nestjs/cli": "11.0.7",
119
119
  "@nestjs/schematics": "11.0.5",
120
- "@nestjs/testing": "11.0.17",
121
- "@swc/cli": "0.6.0",
122
- "@swc/core": "1.11.21",
123
- "@swc/jest": "0.2.37",
120
+ "@nestjs/testing": "11.1.0",
121
+ "@swc/cli": "0.7.5",
122
+ "@swc/core": "1.11.24",
123
+ "@swc/jest": "0.2.38",
124
124
  "@types/compression": "1.7.5",
125
125
  "@types/cookie-parser": "1.4.8",
126
126
  "@types/ejs": "3.1.5",
@@ -128,15 +128,15 @@
128
128
  "@types/jest": "29.5.14",
129
129
  "@types/lodash": "4.17.16",
130
130
  "@types/multer": "1.4.12",
131
- "@types/node": "22.14.1",
131
+ "@types/node": "22.15.17",
132
132
  "@types/nodemailer": "6.4.17",
133
133
  "@types/passport": "1.0.17",
134
134
  "@types/supertest": "6.0.3",
135
- "@typescript-eslint/eslint-plugin": "8.30.1",
136
- "@typescript-eslint/parser": "8.30.1",
135
+ "@typescript-eslint/eslint-plugin": "8.32.0",
136
+ "@typescript-eslint/parser": "8.32.0",
137
137
  "coffeescript": "2.7.0",
138
- "eslint": "9.24.0",
139
- "eslint-config-prettier": "10.1.2",
138
+ "eslint": "9.26.0",
139
+ "eslint-config-prettier": "10.1.5",
140
140
  "eslint-plugin-unused-imports": "4.1.4",
141
141
  "find-file-up": "2.0.1",
142
142
  "grunt": "1.6.1",
@@ -1,6 +1,6 @@
1
- import { ArgsType, Field } from '@nestjs/graphql';
2
- import { IsOptional } from 'class-validator';
1
+ import { ArgsType } from '@nestjs/graphql';
3
2
 
3
+ import { UnifiedField } from '../decorators/unified-field.decorator';
4
4
  import { FilterInput } from '../inputs/filter.input';
5
5
  import { PaginationArgs } from './pagination.args';
6
6
 
@@ -9,20 +9,17 @@ export class FilterArgs extends PaginationArgs {
9
9
  /**
10
10
  * Filtering
11
11
  */
12
- @Field(() => FilterInput, {
13
- description: 'Input for filtering',
14
- nullable: true,
12
+ @UnifiedField({
13
+ isOptional: true,
14
+ type: FilterInput,
15
15
  })
16
- @IsOptional()
17
16
  filter?: FilterInput = undefined;
18
17
 
19
18
  /**
20
19
  * Get a specific number of random samples from filter results
21
20
  */
22
- @Field(() => Number, {
23
- description:
24
- 'Request only a specified number of samples from the filter results; if not specified, all results are returned.',
25
- nullable: true,
21
+ @UnifiedField({
22
+ isOptional: true,
26
23
  })
27
24
  samples?: number = undefined;
28
25
 
@@ -1,6 +1,6 @@
1
- import { ArgsType, Field, Int } from '@nestjs/graphql';
2
- import { IsOptional } from 'class-validator';
1
+ import { ArgsType, Int } from '@nestjs/graphql';
3
2
 
3
+ import { UnifiedField } from '../decorators/unified-field.decorator';
4
4
  import { maps } from '../helpers/model.helper';
5
5
  import { CoreInput } from '../inputs/core-input.input';
6
6
  import { SortInput } from '../inputs/sort.input';
@@ -10,51 +10,51 @@ export class PaginationArgs extends CoreInput {
10
10
  /**
11
11
  * Limit for pagination
12
12
  */
13
- @Field(() => Int, {
13
+ @UnifiedField({
14
14
  description: 'Limit specifies the maximum number of elements found that are to be returned',
15
- nullable: true,
15
+ isOptional: true,
16
+ type: Int,
16
17
  })
17
- @IsOptional()
18
18
  limit?: number = undefined;
19
19
 
20
20
  /**
21
21
  * Alias for skip
22
22
  */
23
- @Field(() => Int, {
23
+ @UnifiedField({
24
24
  description: 'Alias for skip',
25
- nullable: true,
25
+ isOptional: true,
26
+ type: Int,
26
27
  })
27
- @IsOptional()
28
28
  offset?: number = undefined;
29
29
 
30
30
  /**
31
31
  * Skip for pagination
32
32
  */
33
- @Field(() => Int, {
33
+ @UnifiedField({
34
34
  description: 'Skip specifies how many found elements should be skipped on return',
35
- nullable: true,
35
+ isOptional: true,
36
+ type: Int,
36
37
  })
37
- @IsOptional()
38
38
  skip?: number = undefined;
39
39
 
40
40
  /**
41
41
  * Sorting for pagination
42
42
  */
43
- @Field(() => [SortInput], {
43
+ @UnifiedField({
44
44
  description: 'Sorting the returned elements',
45
- nullable: true,
45
+ isOptional: true,
46
+ type: () => SortInput,
46
47
  })
47
- @IsOptional()
48
48
  sort?: SortInput[] = undefined;
49
49
 
50
50
  /**
51
51
  * Alias for limit
52
52
  */
53
- @Field(() => Int, {
53
+ @UnifiedField({
54
54
  description: 'Alias for limit',
55
- nullable: true,
55
+ isOptional: true,
56
+ type: () => Int,
56
57
  })
57
- @IsOptional()
58
58
  take?: number = undefined;
59
59
 
60
60
  // ===================================================================================================================
@@ -1,13 +1,12 @@
1
1
  import { UnauthorizedException } from '@nestjs/common';
2
2
  import 'reflect-metadata';
3
+ import _ = require('lodash');
3
4
 
4
5
  import { ProcessType } from '../enums/process-type.enum';
5
6
  import { RoleEnum } from '../enums/role.enum';
6
7
  import { equalIds, getIncludedIds } from '../helpers/db.helper';
7
8
  import { RequireAtLeastOne } from '../types/required-at-least-one.type';
8
9
 
9
- import _ = require('lodash');
10
-
11
10
  /**
12
11
  * Restricted meta key
13
12
  */
@@ -0,0 +1,269 @@
1
+ import { Field, FieldOptions } from '@nestjs/graphql';
2
+ import { ApiProperty, ApiPropertyOptions } from '@nestjs/swagger';
3
+ import { EnumAllowedTypes } from '@nestjs/swagger/dist/interfaces/schema-object-metadata.interface';
4
+ import { Type } from 'class-transformer';
5
+ import {
6
+ IsArray,
7
+ IsBoolean,
8
+ IsDate,
9
+ IsEnum,
10
+ IsNotEmpty,
11
+ IsNumber,
12
+ IsObject,
13
+ IsOptional,
14
+ IsString,
15
+ ValidateIf,
16
+ ValidateNested,
17
+ ValidationOptions,
18
+ } from 'class-validator';
19
+ import { GraphQLScalarType } from 'graphql';
20
+
21
+ import { RoleEnum } from '../enums/role.enum';
22
+ import { Restricted, RestrictedType } from './restricted.decorator';
23
+
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
+ /** Description used for both Swagger & Gql */
37
+ description?: string;
38
+ /** Enum for class-validator */
39
+ enum?: { enum: EnumAllowedTypes; options?: ValidationOptions };
40
+ /** Example value for swagger api documentation */
41
+ example?: any;
42
+ /** Options for graphql */
43
+ gqlOptions?: FieldOptions;
44
+ /** Default: false */
45
+ isOptional?: boolean;
46
+ /** Restricted roles */
47
+ roles?: RestrictedType | RoleEnum | RoleEnum[];
48
+ /** Options for swagger api documentation */
49
+ swaggerApiOptions?: ApiPropertyOptions;
50
+ /** Type of the field, if not specified, it will be determined automatically.
51
+ *
52
+ * Required if the field is an array (inferred automatically or via the array flag).
53
+ *
54
+ * Enums should be defined via the enum option.
55
+ *
56
+ * Supports:
57
+ * - A factory function that returns the type: `() => MyType`
58
+ * - A GraphQL scalar: `GraphQLScalarType`
59
+ * - A class constructor: `MyClass`
60
+ * */
61
+ type?: (() => any) | GraphQLScalarType | (new (...args: any[]) => any) | Record<number | string, number | string>; // Enums;
62
+ /** Condition for validation */
63
+ validateIf?: (obj: any, val: any) => boolean;
64
+ /** Validation options for class-validator */
65
+ validationOptions?: ValidationOptions;
66
+ /** Custom validators, when using this option, all built-in validators are ignored */
67
+ validator?: (opts: ValidationOptions) => PropertyDecorator[];
68
+ }
69
+
70
+ export function UnifiedField(opts: UnifiedFieldOptions = {}): PropertyDecorator {
71
+ return (target: any, propertyKey: string | symbol) => {
72
+ const metadataType = Reflect.getMetadata('design:type', target, propertyKey);
73
+ const userType = opts.type;
74
+ const isArrayField = opts.array === true || metadataType === Array;
75
+
76
+ // Throwing because metatype only returns the generic array, but not the type of the array items
77
+ if (metadataType === Array && !userType) {
78
+ throw new Error(`Array field '${String(propertyKey)}' of '${String(target)}' must have an explicit type`);
79
+ }
80
+
81
+ if (opts.enum && userType) {
82
+ throw new Error(`Can't set both enum and type of ${String(propertyKey)} in ${target.constructor.name}`);
83
+ }
84
+
85
+ const resolvedTypeFn = (): any => {
86
+ if (opts.enum?.enum) {
87
+ return opts.enum.enum; // Ensure enums are handled directly
88
+ }
89
+ if (userType) {
90
+ if (userType instanceof GraphQLScalarType) { // Case if it's a scalar
91
+ return userType;
92
+ }
93
+ if (
94
+ typeof userType === 'function'
95
+ && userType.prototype
96
+ && userType.prototype.constructor === userType
97
+ ) { // Case if it's a function
98
+ return userType;
99
+ }
100
+ try { // case if its a factory
101
+ return (userType as () => any)();
102
+ } catch {
103
+ return userType;
104
+ }
105
+ }
106
+ return metadataType;
107
+ };
108
+
109
+ const baseType = resolvedTypeFn();
110
+
111
+ // Prepare merged options
112
+ const gqlOpts: FieldOptions = { ...opts.gqlOptions };
113
+ const swaggerOpts: ApiPropertyOptions = { ...opts.swaggerApiOptions };
114
+ const valOpts: ValidationOptions = { ...opts.validationOptions };
115
+
116
+ // Optionality
117
+ if (opts.isOptional) {
118
+ gqlOpts.nullable = true;
119
+ swaggerOpts.nullable = true;
120
+ } else {
121
+ gqlOpts.nullable = false;
122
+ swaggerOpts.nullable = false;
123
+ }
124
+
125
+ // Description
126
+ const defaultDesc = opts.description ?? `${String(propertyKey)} of ${target.constructor.name}`;
127
+ gqlOpts.description = gqlOpts.description ?? defaultDesc;
128
+ swaggerOpts.description = swaggerOpts.description ?? defaultDesc;
129
+
130
+ // Swagger example
131
+ if (opts.example !== undefined) {
132
+ swaggerOpts.example = swaggerOpts.example ?? opts.example;
133
+ }
134
+
135
+ if (opts.enum && opts.enum.enum) {
136
+ swaggerOpts.enum = opts.enum.enum;
137
+ }
138
+ // Array handling
139
+ if (isArrayField) {
140
+ swaggerOpts.isArray = true;
141
+ IsArray(valOpts)(target, propertyKey);
142
+ valOpts.each = true;
143
+ }
144
+
145
+ if (opts.isOptional) {
146
+ IsOptional(valOpts)(target, propertyKey);
147
+ }
148
+
149
+ // Type function for gql
150
+ const gqlTypeFn = isArrayField
151
+ ? () => [opts.enum?.enum || resolvedTypeFn()]
152
+ : () => opts.enum?.enum || resolvedTypeFn();
153
+
154
+ // Gql decorator
155
+ Field(gqlTypeFn, gqlOpts)(target, propertyKey);
156
+
157
+ // Trims keys with 'undefined' properties.
158
+ function trimUndefined<T>(obj: T): Partial<T> {
159
+ if (typeof obj !== 'object' || obj === null) {
160
+ return obj;
161
+ }
162
+ const result: any = Array.isArray(obj) ? [] : {};
163
+ for (const key in obj) {
164
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
165
+ const value = (obj as any)[key];
166
+
167
+ if (typeof value === 'object' && value !== null) {
168
+ const cleaned = trimUndefined(value);
169
+ if (Array.isArray(cleaned) ? cleaned.length > 0 : Object.keys(cleaned).length > 0) {
170
+ result[key] = cleaned;
171
+ }
172
+ } else if (value !== undefined) {
173
+ result[key] = value;
174
+ }
175
+ }
176
+ }
177
+
178
+ return result;
179
+ }
180
+
181
+ ApiProperty(trimUndefined({
182
+ deprecated: swaggerOpts.deprecated,
183
+ description: swaggerOpts.description,
184
+ enum: swaggerOpts.enum,
185
+ example: swaggerOpts.example,
186
+ examples: swaggerOpts.examples,
187
+ isArray: swaggerOpts.isArray,
188
+ nullable: swaggerOpts.nullable,
189
+ pattern: swaggerOpts.pattern,
190
+ type: () => resolvedTypeFn(),
191
+ }))(target, propertyKey);
192
+
193
+ // Conditional validation
194
+ if (opts.validateIf) {
195
+ ValidateIf(opts.validateIf)(target, propertyKey);
196
+ }
197
+
198
+ // isOptional validation
199
+ if (opts.isOptional) {
200
+ IsOptional()(target, propertyKey);
201
+ } else {
202
+ IsNotEmpty()(target, propertyKey);
203
+ }
204
+
205
+ // Custom or builtin validator
206
+ if (opts.validator) {
207
+ opts.validator(valOpts).forEach(d => d(target, propertyKey));
208
+ } else {
209
+ const validator = getBuiltInValidator(baseType, valOpts, isArrayField, target);
210
+ if (validator) {
211
+ validator(target, propertyKey);
212
+ }
213
+ }
214
+
215
+ // Enum validation
216
+ if (opts.enum) {
217
+ IsEnum(opts.enum.enum, opts.enum.options)(target, propertyKey);
218
+ }
219
+
220
+ // Check if it's a primitive, if not apply transform
221
+ if (!isPrimitive(baseType) && !opts.enum && !isGraphQLScalar(baseType)) {
222
+ Type(() => baseType)(target, propertyKey);
223
+ ValidateNested({ each: isArrayField })(target, propertyKey);
224
+ }
225
+
226
+ // Roles
227
+ if (opts.roles) {
228
+ const rolesArr = Array.isArray(opts.roles) ? opts.roles : [opts.roles];
229
+ Restricted(...rolesArr)(target, propertyKey);
230
+ }
231
+ };
232
+ }
233
+
234
+ function getBuiltInValidator(
235
+ type: any,
236
+ opts: ValidationOptions,
237
+ each: boolean,
238
+ target: any,
239
+ ): ((t: any, k: string | symbol) => void) | null {
240
+ const map = new Map<any, PropertyDecorator>([
241
+ [Boolean, IsBoolean(opts)],
242
+ [Date, IsDate(opts)],
243
+ [Number, IsNumber({}, opts)],
244
+ [Object, IsObject(opts)],
245
+ [String, IsString(opts)],
246
+ ]);
247
+ const decorator = map.get(type);
248
+ if (!decorator) {
249
+ return null;
250
+ }
251
+ if (each) {
252
+ return (t, k) => decorator(target, k);
253
+ }
254
+ return decorator;
255
+ }
256
+
257
+ function isGraphQLScalar(type: any): boolean {
258
+ // CustomScalar check (The CustomScalar interface implements these functions below)
259
+ return type
260
+ && typeof type === 'function'
261
+ && typeof type.prototype?.serialize === 'function'
262
+ && typeof type.prototype?.parseValue === 'function'
263
+ && typeof type.prototype?.parseLiteral === 'function'
264
+ || type instanceof GraphQLScalarType;
265
+ }
266
+
267
+ function isPrimitive(fn: any): boolean {
268
+ return [Boolean, Date, Number, String].includes(fn);
269
+ }
@@ -1,9 +1,8 @@
1
1
  import * as dotenv from 'dotenv';
2
+ import _ = require('lodash');
2
3
  import * as process from 'node:process';
3
4
  import { join } from 'path';
4
5
 
5
- import _ = require('lodash');
6
-
7
6
  /**
8
7
  * Helper class for configurations
9
8
  * @deprecated use functions directly
@@ -28,8 +27,6 @@ export default class Config {
28
27
  }
29
28
  }
30
29
 
31
-
32
-
33
30
  /**
34
31
  * Get environment configuration (deeply merged into config object set via options)
35
32
  *
@@ -1,4 +1,5 @@
1
1
  import { FieldNode, GraphQLResolveInfo, SelectionNode } from 'graphql';
2
+ import _ = require('lodash');
2
3
  import { Document, Model, PopulateOptions, Query, Types } from 'mongoose';
3
4
 
4
5
  import { ResolveSelector } from '../interfaces/resolve-selector.interface';
@@ -8,8 +9,6 @@ import { IdsType } from '../types/ids.type';
8
9
  import { StringOrObjectId } from '../types/string-or-object-id.type';
9
10
  import { removePropertiesDeep } from './input.helper';
10
11
 
11
- import _ = require('lodash');
12
-
13
12
  // =====================================================================================================================
14
13
  // Export functions
15
14
  // =====================================================================================================================
@@ -12,7 +12,6 @@ import {
12
12
  ValueNode,
13
13
  VariableNode,
14
14
  } from 'graphql';
15
-
16
15
  import _ = require('lodash');
17
16
 
18
17
  /**
@@ -4,6 +4,8 @@ import { validate } from 'class-validator';
4
4
  import { ValidatorOptions } from 'class-validator/types/validation/ValidatorOptions';
5
5
  import { Kind } from 'graphql/index';
6
6
  import * as inspector from 'inspector';
7
+ import _ = require('lodash');
8
+ import rfdc = require('rfdc');
7
9
  import * as util from 'util';
8
10
 
9
11
  import { checkRestricted } from '../decorators/restricted.decorator';
@@ -12,9 +14,6 @@ import { RoleEnum } from '../enums/role.enum';
12
14
  import { merge } from './config.helper';
13
15
  import { equalIds } from './db.helper';
14
16
 
15
- import _ = require('lodash');
16
- import rfdc = require('rfdc');
17
-
18
17
  /**
19
18
  * Helper class for inputs
20
19
  * @deprecated use functions directly
@@ -1,9 +1,10 @@
1
1
  import { UnauthorizedException } from '@nestjs/common';
2
+ import bcrypt = require('bcrypt');
2
3
  import { plainToInstance } from 'class-transformer';
3
4
  import { sha256 } from 'js-sha256';
5
+ import _ = require('lodash');
4
6
  import { Types } from 'mongoose';
5
7
 
6
- import { getTranslatablePropertyKeys } from '../decorators/translatable.decorator';
7
8
  import { RoleEnum } from '../enums/role.enum';
8
9
  import { PrepareInputOptions } from '../interfaces/prepare-input-options.interface';
9
10
  import { PrepareOutputOptions } from '../interfaces/prepare-output-options.interface';
@@ -13,9 +14,6 @@ import { ConfigService } from '../services/config.service';
13
14
  import { getStringIds } from './db.helper';
14
15
  import { clone, processDeep } from './input.helper';
15
16
 
16
- import bcrypt = require('bcrypt');
17
- import _ = require('lodash');
18
-
19
17
  /**
20
18
  * Helper class for services
21
19
  * @deprecated use functions directly
@@ -277,16 +275,7 @@ export async function prepareOutput<T = { [key: string]: any; map: (...args: any
277
275
 
278
276
  // Add translated values of current selected language if _translations object exists
279
277
  if (config.targetModel && config.language && typeof output === 'object' && '_translations' in output) {
280
- const translation = output._translations?.[options.language];
281
-
282
- if (typeof translation === 'object') {
283
- const keys = getTranslatablePropertyKeys(config.targetModel);
284
- for (const key of keys) {
285
- if (translation[key] != null) {
286
- output[key] = translation[key];
287
- }
288
- }
289
- }
278
+ applyTranslationsRecursively(output, config.language);
290
279
  }
291
280
 
292
281
  // Return prepared output
@@ -347,3 +336,39 @@ export function prepareServiceOptions(
347
336
  // Return (cloned and) prepared service options
348
337
  return serviceOptions;
349
338
  }
339
+
340
+ function applyTranslationsRecursively(obj: any, language: string, visited: WeakSet<object> = new WeakSet()) {
341
+ if (typeof obj !== 'object' || obj === null) {
342
+ return;
343
+ }
344
+ if (visited.has(obj)) {
345
+ return;
346
+ } // Cycle detected -> cancel
347
+
348
+ visited.add(obj);
349
+
350
+ // If _translations exists
351
+ if ('_translations' in obj && typeof obj._translations === 'object') {
352
+ const translation = obj._translations?.[language];
353
+ if (typeof translation === 'object') {
354
+ for (const key in translation) {
355
+ if (translation[key] != null) {
356
+ obj[key] = translation[key];
357
+ }
358
+ }
359
+ }
360
+ }
361
+
362
+ // Recursive over all properties
363
+ for (const key of Object.keys(obj)) {
364
+ const value = obj[key];
365
+
366
+ if (Array.isArray(value)) {
367
+ for (const item of value) {
368
+ applyTranslationsRecursively(item, language, visited);
369
+ }
370
+ } else if (typeof value === 'object' && value !== null) {
371
+ applyTranslationsRecursively(value, language, visited);
372
+ }
373
+ }
374
+ }