@lenne.tech/nest-server 11.8.0 → 11.9.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.
- package/dist/config.env.js +3 -0
- package/dist/config.env.js.map +1 -1
- package/dist/core/common/interfaces/server-options.interface.d.ts +13 -0
- package/dist/core/modules/auth/guards/roles.guard.js +4 -3
- package/dist/core/modules/auth/guards/roles.guard.js.map +1 -1
- package/dist/core/modules/auth/services/core-auth.service.js +5 -4
- package/dist/core/modules/auth/services/core-auth.service.js.map +1 -1
- package/dist/core/modules/error-code/core-error-code.controller.d.ts +7 -0
- package/dist/core/modules/error-code/core-error-code.controller.js +45 -0
- package/dist/core/modules/error-code/core-error-code.controller.js.map +1 -0
- package/dist/core/modules/error-code/core-error-code.service.d.ts +16 -0
- package/dist/core/modules/error-code/core-error-code.service.js +65 -0
- package/dist/core/modules/error-code/core-error-code.service.js.map +1 -0
- package/dist/core/modules/error-code/error-code.module.d.ts +7 -0
- package/dist/core/modules/error-code/error-code.module.js +64 -0
- package/dist/core/modules/error-code/error-code.module.js.map +1 -0
- package/dist/core/modules/error-code/error-codes.d.ts +219 -0
- package/dist/core/modules/error-code/error-codes.js +204 -0
- package/dist/core/modules/error-code/error-codes.js.map +1 -0
- package/dist/core/modules/error-code/index.d.ts +5 -0
- package/dist/core/modules/error-code/index.js +22 -0
- package/dist/core/modules/error-code/index.js.map +1 -0
- package/dist/core/modules/error-code/interfaces/error-code.interfaces.d.ts +12 -0
- package/dist/core/modules/error-code/interfaces/error-code.interfaces.js +3 -0
- package/dist/core/modules/error-code/interfaces/error-code.interfaces.js.map +1 -0
- package/dist/core.module.js +8 -0
- package/dist/core.module.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/server/modules/error-code/error-code.controller.d.ts +8 -0
- package/dist/server/modules/error-code/error-code.controller.js +55 -0
- package/dist/server/modules/error-code/error-code.controller.js.map +1 -0
- package/dist/server/modules/error-code/error-code.service.d.ts +4 -0
- package/dist/server/modules/error-code/error-code.service.js +27 -0
- package/dist/server/modules/error-code/error-code.service.js.map +1 -0
- package/dist/server/modules/error-code/error-codes.d.ts +45 -0
- package/dist/server/modules/error-code/error-codes.js +24 -0
- package/dist/server/modules/error-code/error-codes.js.map +1 -0
- package/dist/server/modules/error-code/index.d.ts +3 -0
- package/dist/server/modules/error-code/index.js +20 -0
- package/dist/server/modules/error-code/index.js.map +1 -0
- package/dist/server/server.module.js +7 -0
- package/dist/server/server.module.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/config.env.ts +4 -0
- package/src/core/common/interfaces/server-options.interface.ts +89 -0
- package/src/core/modules/auth/guards/roles.guard.ts +5 -4
- package/src/core/modules/auth/services/core-auth.service.ts +5 -4
- package/src/core/modules/error-code/INTEGRATION-CHECKLIST.md +288 -0
- package/src/core/modules/error-code/core-error-code.controller.ts +54 -0
- package/src/core/modules/error-code/core-error-code.service.ts +135 -0
- package/src/core/modules/error-code/error-code.module.ts +119 -0
- package/src/core/modules/error-code/error-codes.ts +405 -0
- package/src/core/modules/error-code/index.ts +14 -0
- package/src/core/modules/error-code/interfaces/error-code.interfaces.ts +99 -0
- package/src/core.module.ts +16 -0
- package/src/index.ts +6 -0
- package/src/server/modules/error-code/README.md +131 -0
- package/src/server/modules/error-code/error-code.controller.ts +91 -0
- package/src/server/modules/error-code/error-code.service.ts +42 -0
- package/src/server/modules/error-code/error-codes.ts +65 -0
- package/src/server/modules/error-code/index.ts +8 -0
- package/src/server/server.module.ts +10 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lenne.tech/nest-server",
|
|
3
|
-
"version": "11.
|
|
3
|
+
"version": "11.9.0",
|
|
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",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"build:pack": "npm pack && echo 'use file:/ROOT_PATH_TO_TGZ_FILE to integrate the package'",
|
|
22
22
|
"build:dev": "npm run build && yalc push --private",
|
|
23
23
|
"docs": "npm run docs:ci && open http://127.0.0.1:8080/ && open ./public/index.html && compodoc -p tsconfig.json -s ",
|
|
24
|
-
"docs:bootstrap": "node extras/update-spectaql-version.mjs &&
|
|
24
|
+
"docs:bootstrap": "node extras/update-spectaql-version.mjs && node scripts/run-spectaql.mjs",
|
|
25
25
|
"docs:ci": "ts-node ./scripts/init-server.ts && npm run docs:bootstrap && compodoc -p tsconfig.json",
|
|
26
26
|
"format": "prettier --write 'src/**/*.ts'",
|
|
27
27
|
"format:staged": "pretty-quick --staged",
|
package/src/config.env.ts
CHANGED
|
@@ -262,6 +262,10 @@ const config: { [env: string]: IServerOptions } = {
|
|
|
262
262
|
verificationLink: 'http://localhost:4200/user/verification',
|
|
263
263
|
},
|
|
264
264
|
env: 'local',
|
|
265
|
+
// Disable auto-registration to allow Server ErrorCodeModule with SRV_* codes
|
|
266
|
+
errorCode: {
|
|
267
|
+
autoRegister: false,
|
|
268
|
+
},
|
|
265
269
|
execAfterInit: 'npm run docs:bootstrap',
|
|
266
270
|
filter: {
|
|
267
271
|
maxLimit: null,
|
|
@@ -589,6 +589,70 @@ export interface IBetterAuthUserField {
|
|
|
589
589
|
type: BetterAuthFieldType;
|
|
590
590
|
}
|
|
591
591
|
|
|
592
|
+
/**
|
|
593
|
+
* Interface for Error Code module configuration
|
|
594
|
+
*
|
|
595
|
+
* Controls how the ErrorCodeModule is registered and configured.
|
|
596
|
+
*
|
|
597
|
+
* @since 11.9.0
|
|
598
|
+
*/
|
|
599
|
+
export interface IErrorCode {
|
|
600
|
+
/**
|
|
601
|
+
* Additional error registry to merge with core LTNS_* errors
|
|
602
|
+
*
|
|
603
|
+
* Use this to add project-specific error codes with a custom prefix.
|
|
604
|
+
*
|
|
605
|
+
* @example
|
|
606
|
+
* ```typescript
|
|
607
|
+
* const ProjectErrors = {
|
|
608
|
+
* ORDER_NOT_FOUND: {
|
|
609
|
+
* code: 'PROJ_0001',
|
|
610
|
+
* message: 'Order not found',
|
|
611
|
+
* translations: { de: 'Bestellung nicht gefunden.', en: 'Order not found.' }
|
|
612
|
+
* }
|
|
613
|
+
* } as const satisfies IErrorRegistry;
|
|
614
|
+
*
|
|
615
|
+
* errorCode: {
|
|
616
|
+
* additionalErrorRegistry: ProjectErrors,
|
|
617
|
+
* }
|
|
618
|
+
* ```
|
|
619
|
+
*/
|
|
620
|
+
additionalErrorRegistry?: Record<
|
|
621
|
+
string,
|
|
622
|
+
{
|
|
623
|
+
code: string;
|
|
624
|
+
message: string;
|
|
625
|
+
translations: { [locale: string]: string; de: string; en: string };
|
|
626
|
+
}
|
|
627
|
+
>;
|
|
628
|
+
|
|
629
|
+
/**
|
|
630
|
+
* Automatically register the ErrorCodeModule in CoreModule
|
|
631
|
+
*
|
|
632
|
+
* Set to `false` to disable auto-registration and provide your own
|
|
633
|
+
* ErrorCodeModule with custom controller and/or service.
|
|
634
|
+
*
|
|
635
|
+
* @default true
|
|
636
|
+
*
|
|
637
|
+
* @example
|
|
638
|
+
* ```typescript
|
|
639
|
+
* // In config.env.ts - disable auto-registration
|
|
640
|
+
* errorCode: {
|
|
641
|
+
* autoRegister: false,
|
|
642
|
+
* }
|
|
643
|
+
*
|
|
644
|
+
* // In server.module.ts - import your custom module
|
|
645
|
+
* @Module({
|
|
646
|
+
* imports: [
|
|
647
|
+
* CoreModule.forRoot(...),
|
|
648
|
+
* ErrorCodeModule.forRoot(), // Your custom module
|
|
649
|
+
* ],
|
|
650
|
+
* })
|
|
651
|
+
* ```
|
|
652
|
+
*/
|
|
653
|
+
autoRegister?: boolean;
|
|
654
|
+
}
|
|
655
|
+
|
|
592
656
|
/**
|
|
593
657
|
* Interface for JWT configuration (main and refresh)
|
|
594
658
|
*/
|
|
@@ -766,6 +830,31 @@ export interface IServerOptions {
|
|
|
766
830
|
*/
|
|
767
831
|
env?: string;
|
|
768
832
|
|
|
833
|
+
/**
|
|
834
|
+
* Configuration for the error code module
|
|
835
|
+
*
|
|
836
|
+
* Controls how error codes and translations are handled.
|
|
837
|
+
*
|
|
838
|
+
* @since 11.9.0
|
|
839
|
+
*
|
|
840
|
+
* @example
|
|
841
|
+
* ```typescript
|
|
842
|
+
* // Default: auto-register with core errors only
|
|
843
|
+
* errorCode: undefined
|
|
844
|
+
*
|
|
845
|
+
* // Add project-specific error codes
|
|
846
|
+
* errorCode: {
|
|
847
|
+
* additionalErrorRegistry: ProjectErrors,
|
|
848
|
+
* }
|
|
849
|
+
*
|
|
850
|
+
* // Disable auto-registration to provide your own module
|
|
851
|
+
* errorCode: {
|
|
852
|
+
* autoRegister: false,
|
|
853
|
+
* }
|
|
854
|
+
* ```
|
|
855
|
+
*/
|
|
856
|
+
errorCode?: IErrorCode;
|
|
857
|
+
|
|
769
858
|
/**
|
|
770
859
|
* Exec a command after server is initialized
|
|
771
860
|
* e.g. 'npm run docs:bootstrap'
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ExecutionContext, Injectable, Logger, Optional, UnauthorizedException } from '@nestjs/common';
|
|
1
|
+
import { ExecutionContext, ForbiddenException, Injectable, Logger, Optional, UnauthorizedException } from '@nestjs/common';
|
|
2
2
|
import { ModuleRef, Reflector } from '@nestjs/core';
|
|
3
3
|
import { GqlExecutionContext } from '@nestjs/graphql';
|
|
4
4
|
import { getConnectionToken } from '@nestjs/mongoose';
|
|
@@ -7,6 +7,7 @@ import { firstValueFrom, isObservable } from 'rxjs';
|
|
|
7
7
|
|
|
8
8
|
import { RoleEnum } from '../../../common/enums/role.enum';
|
|
9
9
|
import { BetterAuthService } from '../../better-auth/better-auth.service';
|
|
10
|
+
import { ErrorCode } from '../../error-code';
|
|
10
11
|
import { AuthGuardStrategy } from '../auth-guard-strategy.enum';
|
|
11
12
|
import { ExpiredTokenException } from '../exceptions/expired-token.exception';
|
|
12
13
|
import { InvalidTokenException } from '../exceptions/invalid-token.exception';
|
|
@@ -331,7 +332,7 @@ export class RolesGuard extends AuthGuard(AuthGuardStrategy.JWT) {
|
|
|
331
332
|
|
|
332
333
|
// Check if locked
|
|
333
334
|
if (roles && roles.includes(RoleEnum.S_NO_ONE)) {
|
|
334
|
-
throw new UnauthorizedException(
|
|
335
|
+
throw new UnauthorizedException(ErrorCode.UNAUTHORIZED);
|
|
335
336
|
}
|
|
336
337
|
|
|
337
338
|
// Check roles
|
|
@@ -354,11 +355,11 @@ export class RolesGuard extends AuthGuard(AuthGuardStrategy.JWT) {
|
|
|
354
355
|
if (info?.name === 'TokenExpiredError') {
|
|
355
356
|
throw new ExpiredTokenException();
|
|
356
357
|
}
|
|
357
|
-
throw new UnauthorizedException(
|
|
358
|
+
throw new UnauthorizedException(ErrorCode.UNAUTHORIZED);
|
|
358
359
|
}
|
|
359
360
|
|
|
360
361
|
// Requester is not authorized
|
|
361
|
-
throw new
|
|
362
|
+
throw new ForbiddenException(ErrorCode.ACCESS_DENIED);
|
|
362
363
|
}
|
|
363
364
|
|
|
364
365
|
// Everything is ok
|
|
@@ -17,6 +17,7 @@ import { ServiceOptions } from '../../../common/interfaces/service-options.inter
|
|
|
17
17
|
import { ConfigService } from '../../../common/services/config.service';
|
|
18
18
|
import { BetterAuthUserMapper } from '../../better-auth/better-auth-user.mapper';
|
|
19
19
|
import { BetterAuthService } from '../../better-auth/better-auth.service';
|
|
20
|
+
import { ErrorCode } from '../../error-code';
|
|
20
21
|
import { CoreAuthModel } from '../core-auth.model';
|
|
21
22
|
import { CoreAuthSignInInput } from '../inputs/core-auth-sign-in.input';
|
|
22
23
|
import { CoreAuthSignUpInput } from '../inputs/core-auth-sign-up.input';
|
|
@@ -83,13 +84,13 @@ export class CoreAuthService {
|
|
|
83
84
|
// Check authentication
|
|
84
85
|
const user = serviceOptions.currentUser;
|
|
85
86
|
if (!user || !tokenOrRefreshToken) {
|
|
86
|
-
throw new UnauthorizedException(
|
|
87
|
+
throw new UnauthorizedException(ErrorCode.INVALID_TOKEN);
|
|
87
88
|
}
|
|
88
89
|
|
|
89
90
|
// Check authorization
|
|
90
91
|
const deviceId = this.decodeJwt(tokenOrRefreshToken)?.deviceId;
|
|
91
92
|
if (!deviceId || !user.refreshTokens[deviceId]) {
|
|
92
|
-
throw new UnauthorizedException(
|
|
93
|
+
throw new UnauthorizedException(ErrorCode.INVALID_TOKEN);
|
|
93
94
|
}
|
|
94
95
|
|
|
95
96
|
// Logout from all devices
|
|
@@ -374,7 +375,7 @@ export class CoreAuthService {
|
|
|
374
375
|
if (currentRefreshToken) {
|
|
375
376
|
deviceId = this.decodeJwt(currentRefreshToken)?.deviceId;
|
|
376
377
|
if (!deviceId || !user.refreshTokens?.[deviceId]) {
|
|
377
|
-
throw new UnauthorizedException(
|
|
378
|
+
throw new UnauthorizedException(ErrorCode.INVALID_TOKEN);
|
|
378
379
|
}
|
|
379
380
|
if (!this.configService.getFastButReadOnly('jwt.refresh.renewal')) {
|
|
380
381
|
// Return currentToken
|
|
@@ -398,7 +399,7 @@ export class CoreAuthService {
|
|
|
398
399
|
// Set new token
|
|
399
400
|
const payload = this.decodeJwt(newRefreshToken);
|
|
400
401
|
if (!payload) {
|
|
401
|
-
throw new UnauthorizedException(
|
|
402
|
+
throw new UnauthorizedException(ErrorCode.INVALID_TOKEN);
|
|
402
403
|
}
|
|
403
404
|
if (!deviceId) {
|
|
404
405
|
deviceId = payload.deviceId;
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
# ErrorCode Integration Checklist
|
|
2
|
+
|
|
3
|
+
**For integrating custom error codes into projects using `@lenne.tech/nest-server`.**
|
|
4
|
+
|
|
5
|
+
> **Estimated time:** 5-10 minutes
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Choose Your Scenario
|
|
10
|
+
|
|
11
|
+
| Scenario | Use When | Configuration | Complexity |
|
|
12
|
+
|----------|----------|---------------|------------|
|
|
13
|
+
| **A. additionalErrorRegistry** | Simple error code addition | Config in `config.env.ts` | Minimal |
|
|
14
|
+
| **B. Custom Service** | Need custom locales or logic | Service inheritance | Low |
|
|
15
|
+
| **C. Custom Controller** | Need custom controller/routes | Service + Controller | Medium |
|
|
16
|
+
|
|
17
|
+
**Recommendation:** Start with **Scenario A**. Only use B or C if you need customization beyond adding error codes.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Reference Implementation
|
|
22
|
+
|
|
23
|
+
All files are available as reference in the package:
|
|
24
|
+
|
|
25
|
+
**Local (in your node_modules):**
|
|
26
|
+
```
|
|
27
|
+
node_modules/@lenne.tech/nest-server/src/server/modules/error-code/
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**GitHub:**
|
|
31
|
+
https://github.com/lenneTech/nest-server/tree/develop/src/server/modules/error-code
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Scenario A: additionalErrorRegistry (Simplest)
|
|
36
|
+
|
|
37
|
+
Use this when you just want to add project-specific error codes.
|
|
38
|
+
|
|
39
|
+
### 1. Define Error Codes
|
|
40
|
+
|
|
41
|
+
**Create:** `src/server/common/errors/project-errors.ts`
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
import { IErrorRegistry, mergeErrorCodes } from '@lenne.tech/nest-server';
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Project-specific error codes
|
|
48
|
+
*
|
|
49
|
+
* Format: PREFIX_XXXX (e.g., PROJ_0001, APP_0001)
|
|
50
|
+
* Use a unique prefix to avoid collisions with LTNS_* core errors.
|
|
51
|
+
*/
|
|
52
|
+
export const ProjectErrors = {
|
|
53
|
+
ORDER_NOT_FOUND: {
|
|
54
|
+
code: 'PROJ_0001',
|
|
55
|
+
message: 'Order not found',
|
|
56
|
+
translations: {
|
|
57
|
+
de: 'Bestellung mit ID {orderId} wurde nicht gefunden.',
|
|
58
|
+
en: 'Order with ID {orderId} was not found.',
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
PAYMENT_FAILED: {
|
|
62
|
+
code: 'PROJ_0002',
|
|
63
|
+
message: 'Payment processing failed',
|
|
64
|
+
translations: {
|
|
65
|
+
de: 'Die Zahlung konnte nicht verarbeitet werden: {reason}',
|
|
66
|
+
en: 'Payment processing failed: {reason}',
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
} as const satisfies IErrorRegistry;
|
|
70
|
+
|
|
71
|
+
// Merged error codes for type-safe factory functions
|
|
72
|
+
export const ErrorCode = mergeErrorCodes(ProjectErrors);
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 2. Add to config.env.ts
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
import { ProjectErrors } from './server/common/errors/project-errors';
|
|
79
|
+
|
|
80
|
+
const config = {
|
|
81
|
+
// ... other config ...
|
|
82
|
+
errorCode: {
|
|
83
|
+
additionalErrorRegistry: ProjectErrors,
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**Done!** Your project errors are now available via `/api/i18n/errors/:locale`.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Scenario B: Custom Service (For Custom Locales)
|
|
93
|
+
|
|
94
|
+
Use this when you need:
|
|
95
|
+
- Additional locales (e.g., French, Spanish)
|
|
96
|
+
- Custom logic in the service
|
|
97
|
+
|
|
98
|
+
### 1. Define Error Codes
|
|
99
|
+
|
|
100
|
+
Same as Scenario A, Step 1.
|
|
101
|
+
|
|
102
|
+
### 2. Create Custom Service
|
|
103
|
+
|
|
104
|
+
**Create:** `src/server/modules/error-code/error-code.service.ts`
|
|
105
|
+
**Copy from:** `node_modules/@lenne.tech/nest-server/src/server/modules/error-code/error-code.service.ts`
|
|
106
|
+
|
|
107
|
+
**Optional customization - add locales:**
|
|
108
|
+
```typescript
|
|
109
|
+
@Injectable()
|
|
110
|
+
export class ErrorCodeService extends CoreErrorCodeService {
|
|
111
|
+
// Override to add more locales
|
|
112
|
+
protected override supportedLocales = ['de', 'en', 'fr', 'es'] as const;
|
|
113
|
+
|
|
114
|
+
constructor() {
|
|
115
|
+
super();
|
|
116
|
+
this.registerErrorRegistry(ProjectErrors);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### 3. Disable Auto-Registration
|
|
122
|
+
|
|
123
|
+
**Update:** `src/config.env.ts`
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
const config = {
|
|
127
|
+
// ... other config ...
|
|
128
|
+
errorCode: {
|
|
129
|
+
autoRegister: false, // Required! Prevents CoreModule from registering its own
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
**WHY is `autoRegister: false` required?**
|
|
135
|
+
NestJS @Global() modules use "first wins" for provider registration. Without this, CoreModule's ErrorCodeModule loads first and your custom service is ignored.
|
|
136
|
+
|
|
137
|
+
### 4. Register in ServerModule
|
|
138
|
+
|
|
139
|
+
**Update:** `src/server/server.module.ts`
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
import { ErrorCodeModule as CoreErrorCodeModule } from '@lenne.tech/nest-server';
|
|
143
|
+
import { ErrorCodeService } from './modules/error-code/error-code.service';
|
|
144
|
+
|
|
145
|
+
@Module({
|
|
146
|
+
imports: [
|
|
147
|
+
CoreModule.forRoot(...),
|
|
148
|
+
// Register with custom service
|
|
149
|
+
CoreErrorCodeModule.forRoot({
|
|
150
|
+
service: ErrorCodeService,
|
|
151
|
+
}),
|
|
152
|
+
// ... other modules
|
|
153
|
+
],
|
|
154
|
+
})
|
|
155
|
+
export class ServerModule {}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Scenario C: Custom Controller (For Custom Routes)
|
|
161
|
+
|
|
162
|
+
Use this when you need:
|
|
163
|
+
- Custom controller endpoints (e.g., `/codes` listing)
|
|
164
|
+
- Different route paths
|
|
165
|
+
- Additional REST endpoints
|
|
166
|
+
|
|
167
|
+
**No custom module needed!** Use Core `ErrorCodeModule.forRoot()` with your custom controller and service.
|
|
168
|
+
|
|
169
|
+
### 1. Create Files
|
|
170
|
+
|
|
171
|
+
**Copy from:** `node_modules/@lenne.tech/nest-server/src/server/modules/error-code/`
|
|
172
|
+
|
|
173
|
+
Files needed:
|
|
174
|
+
- `error-codes.ts` - Your error definitions
|
|
175
|
+
- `error-code.service.ts` - Service extending CoreErrorCodeService
|
|
176
|
+
- `error-code.controller.ts` - Controller (**standalone**, not extending)
|
|
177
|
+
- `index.ts` - Exports
|
|
178
|
+
|
|
179
|
+
**No `error-code.module.ts` needed!**
|
|
180
|
+
|
|
181
|
+
### 2. Disable Auto-Registration
|
|
182
|
+
|
|
183
|
+
Same as Scenario B, Step 3.
|
|
184
|
+
|
|
185
|
+
### 3. Register via Core ErrorCodeModule
|
|
186
|
+
|
|
187
|
+
**Update:** `src/server/server.module.ts`
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
import { ErrorCodeModule } from '@lenne.tech/nest-server';
|
|
191
|
+
import { ErrorCodeController } from './modules/error-code/error-code.controller';
|
|
192
|
+
import { ErrorCodeService } from './modules/error-code/error-code.service';
|
|
193
|
+
|
|
194
|
+
@Module({
|
|
195
|
+
imports: [
|
|
196
|
+
CoreModule.forRoot(...),
|
|
197
|
+
// Use Core ErrorCodeModule with custom service and controller
|
|
198
|
+
ErrorCodeModule.forRoot({
|
|
199
|
+
controller: ErrorCodeController,
|
|
200
|
+
service: ErrorCodeService,
|
|
201
|
+
}),
|
|
202
|
+
// ... other modules
|
|
203
|
+
],
|
|
204
|
+
})
|
|
205
|
+
export class ServerModule {}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
**WHY standalone controller instead of extending?**
|
|
209
|
+
NestJS registers routes from parent classes first, regardless of method order in child classes. This causes `:locale` to intercept `/codes`. A standalone controller ensures correct route order: static routes (`/codes`) first, then parameterized routes (`:locale`).
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## Verification Checklist
|
|
214
|
+
|
|
215
|
+
After integration, verify:
|
|
216
|
+
|
|
217
|
+
- [ ] `npm run build` succeeds without errors
|
|
218
|
+
- [ ] `npm test` passes
|
|
219
|
+
- [ ] `GET /api/i18n/errors/de` returns your project error codes
|
|
220
|
+
- [ ] `GET /api/i18n/errors/en` returns English translations
|
|
221
|
+
- [ ] Error codes follow format `PREFIX_XXXX` (e.g., `PROJ_0001`)
|
|
222
|
+
- [ ] Translations include placeholders where needed (`{param}`)
|
|
223
|
+
|
|
224
|
+
### For Scenario C only:
|
|
225
|
+
- [ ] `GET /api/i18n/errors/codes` returns all error codes (if implemented)
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## Common Mistakes
|
|
230
|
+
|
|
231
|
+
| Mistake | Symptom | Fix |
|
|
232
|
+
|---------|---------|-----|
|
|
233
|
+
| Forgot `autoRegister: false` | Project errors not appearing | Add `errorCode: { autoRegister: false }` to config |
|
|
234
|
+
| Wrong error code format | Validation errors | Use `PREFIX_XXXX` format (4 digits) |
|
|
235
|
+
| Missing translations | Runtime errors | Ensure all locales have translations |
|
|
236
|
+
| Controller extends CoreErrorCodeController | `/codes` returns 404 | Use standalone controller |
|
|
237
|
+
| Duplicate error codes | Unpredictable behavior | Ensure unique codes across all registries |
|
|
238
|
+
| Forgot to import module | No error translations | Import ErrorCodeModule in ServerModule |
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## Using Error Codes in Code
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
import { ErrorCode, Errors } from '@lenne.tech/nest-server';
|
|
246
|
+
|
|
247
|
+
// Type-safe error code access
|
|
248
|
+
const code = ErrorCode.userNotFound; // Returns '#LTNS_0001: User not found'
|
|
249
|
+
|
|
250
|
+
// Factory functions with parameters
|
|
251
|
+
throw new BadRequestException(Errors.userNotFound({ email: 'test@example.com' }));
|
|
252
|
+
// Throws: '#LTNS_0001: User with email test@example.com was not found.'
|
|
253
|
+
|
|
254
|
+
// Project-specific errors (after registration)
|
|
255
|
+
import { ErrorCode as ProjectErrorCode } from './common/errors/project-errors';
|
|
256
|
+
|
|
257
|
+
const orderCode = ProjectErrorCode.ORDER_NOT_FOUND; // '#PROJ_0001: Order not found'
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## API Reference
|
|
263
|
+
|
|
264
|
+
### REST Endpoints
|
|
265
|
+
|
|
266
|
+
| Endpoint | Method | Description |
|
|
267
|
+
|----------|--------|-------------|
|
|
268
|
+
| `/api/i18n/errors/:locale` | GET | Get translations for locale (de, en, ...) |
|
|
269
|
+
| `/api/i18n/errors/codes` | GET | Get all error codes (Scenario C only) |
|
|
270
|
+
|
|
271
|
+
### Response Format (Nuxt i18n compatible)
|
|
272
|
+
|
|
273
|
+
```json
|
|
274
|
+
{
|
|
275
|
+
"errors": {
|
|
276
|
+
"LTNS_0001": "Benutzer mit E-Mail {email} wurde nicht gefunden.",
|
|
277
|
+
"PROJ_0001": "Bestellung mit ID {orderId} wurde nicht gefunden."
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## Detailed Documentation
|
|
285
|
+
|
|
286
|
+
For complete API reference and advanced topics:
|
|
287
|
+
- **Core Error Codes:** `node_modules/@lenne.tech/nest-server/src/core/modules/error-code/error-codes.ts`
|
|
288
|
+
- **Interfaces:** `node_modules/@lenne.tech/nest-server/src/core/modules/error-code/interfaces/error-code.interfaces.ts`
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Controller, Get, NotFoundException, Param } from '@nestjs/common';
|
|
2
|
+
|
|
3
|
+
import { Roles } from '../../common/decorators/roles.decorator';
|
|
4
|
+
import { RoleEnum } from '../../common/enums/role.enum';
|
|
5
|
+
import { CoreErrorCodeService } from './core-error-code.service';
|
|
6
|
+
import { IErrorTranslationResponse, SupportedLocale } from './interfaces/error-code.interfaces';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Core Error Code Controller
|
|
10
|
+
*
|
|
11
|
+
* Provides REST endpoints for error translations.
|
|
12
|
+
* This controller is publicly accessible (no authentication required).
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* GET /api/i18n/errors/de - Get German translations
|
|
16
|
+
* GET /api/i18n/errors/en - Get English translations
|
|
17
|
+
*/
|
|
18
|
+
@Controller('api/i18n/errors')
|
|
19
|
+
export class CoreErrorCodeController {
|
|
20
|
+
constructor(protected readonly errorCodeService: CoreErrorCodeService) {}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Get error translations for a specific locale
|
|
24
|
+
*
|
|
25
|
+
* Returns all error codes with their translations in Nuxt i18n compatible format.
|
|
26
|
+
*
|
|
27
|
+
* @param locale - Locale code (e.g., 'de', 'en')
|
|
28
|
+
* @returns Translations object
|
|
29
|
+
* @throws NotFoundException if locale is not supported
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* Response:
|
|
33
|
+
* ```json
|
|
34
|
+
* {
|
|
35
|
+
* "errors": {
|
|
36
|
+
* "LTNS_0001": "Benutzer mit E-Mail {email} wurde nicht gefunden.",
|
|
37
|
+
* "LTNS_0002": "Das eingegebene Passwort ist ungültig."
|
|
38
|
+
* }
|
|
39
|
+
* }
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
@Get(':locale')
|
|
43
|
+
@Roles(RoleEnum.S_EVERYONE)
|
|
44
|
+
getTranslations(@Param('locale') locale: string): IErrorTranslationResponse {
|
|
45
|
+
if (!this.errorCodeService.isLocaleSupported(locale)) {
|
|
46
|
+
throw new NotFoundException(
|
|
47
|
+
`Locale "${locale}" is not supported. ` +
|
|
48
|
+
`Supported locales: ${this.errorCodeService.getSupportedLocales().join(', ')}`,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return this.errorCodeService.getTranslations(locale as SupportedLocale);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { Injectable } from '@nestjs/common';
|
|
2
|
+
|
|
3
|
+
import { getAllErrorDefinitions, IErrorRegistry } from './error-codes';
|
|
4
|
+
import { SupportedLocale } from './interfaces/error-code.interfaces';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Core Error Code Service
|
|
8
|
+
*
|
|
9
|
+
* Serves error code translations from the structured ErrorRegistry.
|
|
10
|
+
* Translations are defined in error-codes.ts as Single Source of Truth.
|
|
11
|
+
*
|
|
12
|
+
* Projects can extend this service to add custom error registries.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* // In consuming project:
|
|
17
|
+
* import { CoreErrorCodeService, IErrorRegistry } from '@lenne.tech/nest-server';
|
|
18
|
+
*
|
|
19
|
+
* const ProjectErrors = {
|
|
20
|
+
* ORDER_NOT_FOUND: {
|
|
21
|
+
* code: 'PROJ_0001',
|
|
22
|
+
* message: 'Order not found',
|
|
23
|
+
* translations: { de: 'Bestellung nicht gefunden.', en: 'Order not found.' }
|
|
24
|
+
* }
|
|
25
|
+
* } as const satisfies IErrorRegistry;
|
|
26
|
+
*
|
|
27
|
+
* @Injectable()
|
|
28
|
+
* export class ErrorCodeService extends CoreErrorCodeService {
|
|
29
|
+
* constructor() {
|
|
30
|
+
* super();
|
|
31
|
+
* this.registerErrorRegistry(ProjectErrors);
|
|
32
|
+
* }
|
|
33
|
+
* }
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
@Injectable()
|
|
37
|
+
export class CoreErrorCodeService {
|
|
38
|
+
/**
|
|
39
|
+
* Supported locales
|
|
40
|
+
*/
|
|
41
|
+
protected supportedLocales: SupportedLocale[] = ['de', 'en'];
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Cached translations per locale
|
|
45
|
+
*/
|
|
46
|
+
protected translations: Map<SupportedLocale, Record<string, string>> = new Map();
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Registered error registries
|
|
50
|
+
*/
|
|
51
|
+
protected registries: IErrorRegistry[] = [];
|
|
52
|
+
|
|
53
|
+
constructor() {
|
|
54
|
+
// Initialize with core errors
|
|
55
|
+
this.registerErrorRegistry(getAllErrorDefinitions());
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Register an error registry and generate translations
|
|
60
|
+
*
|
|
61
|
+
* @param registry - Error registry to register
|
|
62
|
+
*/
|
|
63
|
+
registerErrorRegistry(registry: IErrorRegistry): void {
|
|
64
|
+
this.registries.push(registry);
|
|
65
|
+
this.generateTranslationsFromRegistry(registry);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Generate translations from error registry
|
|
70
|
+
*
|
|
71
|
+
* @param registry - Error registry to extract translations from
|
|
72
|
+
*/
|
|
73
|
+
protected generateTranslationsFromRegistry(registry: IErrorRegistry): void {
|
|
74
|
+
for (const [, definition] of Object.entries(registry)) {
|
|
75
|
+
const { code, translations: defTranslations } = definition;
|
|
76
|
+
|
|
77
|
+
for (const locale of this.supportedLocales) {
|
|
78
|
+
const translation = defTranslations[locale];
|
|
79
|
+
if (translation) {
|
|
80
|
+
const existing = this.translations.get(locale) || {};
|
|
81
|
+
this.translations.set(locale, { ...existing, [code]: translation });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Check if a locale is supported
|
|
89
|
+
*
|
|
90
|
+
* @param locale - Locale to check
|
|
91
|
+
* @returns True if locale is supported
|
|
92
|
+
*/
|
|
93
|
+
isLocaleSupported(locale: string): locale is SupportedLocale {
|
|
94
|
+
return this.supportedLocales.includes(locale as SupportedLocale);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Get supported locales
|
|
99
|
+
*
|
|
100
|
+
* @returns Array of supported locales
|
|
101
|
+
*/
|
|
102
|
+
getSupportedLocales(): SupportedLocale[] {
|
|
103
|
+
return [...this.supportedLocales];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Get all translations for a locale
|
|
108
|
+
*
|
|
109
|
+
* @param locale - Locale code (e.g., 'de', 'en')
|
|
110
|
+
* @returns Translations object wrapped in { errors: ... } for Nuxt i18n compatibility
|
|
111
|
+
* @throws Error if locale is not supported
|
|
112
|
+
*/
|
|
113
|
+
getTranslations(locale: SupportedLocale): { errors: Record<string, string> } {
|
|
114
|
+
if (!this.isLocaleSupported(locale)) {
|
|
115
|
+
throw new Error(`Locale "${locale}" is not supported. Supported: ${this.supportedLocales.join(', ')}`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return { errors: this.translations.get(locale) || {} };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Get all error codes
|
|
123
|
+
*
|
|
124
|
+
* @returns Array of error codes from all registries
|
|
125
|
+
*/
|
|
126
|
+
getErrorCodes(): string[] {
|
|
127
|
+
const codes = new Set<string>();
|
|
128
|
+
for (const translations of this.translations.values()) {
|
|
129
|
+
for (const code of Object.keys(translations)) {
|
|
130
|
+
codes.add(code);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return Array.from(codes).sort();
|
|
134
|
+
}
|
|
135
|
+
}
|