@lenne.tech/nest-server 11.7.3 → 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 +35 -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/modules/file/core-file.controller.d.ts +1 -0
- package/dist/core/modules/file/core-file.controller.js +22 -0
- package/dist/core/modules/file/core-file.controller.js.map +1 -1
- package/dist/core/modules/tus/core-tus.controller.d.ts +9 -0
- package/dist/core/modules/tus/core-tus.controller.js +85 -0
- package/dist/core/modules/tus/core-tus.controller.js.map +1 -0
- package/dist/core/modules/tus/core-tus.service.d.ts +30 -0
- package/dist/core/modules/tus/core-tus.service.js +284 -0
- package/dist/core/modules/tus/core-tus.service.js.map +1 -0
- package/dist/core/modules/tus/index.d.ts +4 -0
- package/dist/core/modules/tus/index.js +21 -0
- package/dist/core/modules/tus/index.js.map +1 -0
- package/dist/core/modules/tus/interfaces/tus-config.interface.d.ts +10 -0
- package/dist/core/modules/tus/interfaces/tus-config.interface.js +59 -0
- package/dist/core/modules/tus/interfaces/tus-config.interface.js.map +1 -0
- package/dist/core/modules/tus/tus.module.d.ts +21 -0
- package/dist/core/modules/tus/tus.module.js +99 -0
- package/dist/core/modules/tus/tus.module.js.map +1 -0
- package/dist/core.module.js +8 -0
- package/dist/core.module.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -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/modules/file/file.controller.d.ts +5 -7
- package/dist/server/modules/file/file.controller.js +3 -31
- package/dist/server/modules/file/file.controller.js.map +1 -1
- package/dist/server/server.module.js +10 -1
- package/dist/server/server.module.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +5 -2
- package/src/config.env.ts +4 -0
- package/src/core/common/interfaces/server-options.interface.ts +243 -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/modules/file/README.md +165 -0
- package/src/core/modules/file/core-file.controller.ts +27 -1
- package/src/core/modules/tus/INTEGRATION-CHECKLIST.md +176 -0
- package/src/core/modules/tus/README.md +439 -0
- package/src/core/modules/tus/core-tus.controller.ts +88 -0
- package/src/core/modules/tus/core-tus.service.ts +424 -0
- package/src/core/modules/tus/index.ts +5 -0
- package/src/core/modules/tus/interfaces/tus-config.interface.ts +107 -0
- package/src/core/modules/tus/tus.module.ts +187 -0
- package/src/core.module.ts +16 -0
- package/src/index.ts +12 -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/modules/file/file.controller.ts +14 -34
- package/src/server/server.module.ts +15 -1
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { DynamicModule, Global, Logger, Module, OnModuleInit, Type } from '@nestjs/common';
|
|
2
|
+
import { getConnectionToken } from '@nestjs/mongoose';
|
|
3
|
+
import { Connection } from 'mongoose';
|
|
4
|
+
|
|
5
|
+
import { ITusConfig } from '../../common/interfaces/server-options.interface';
|
|
6
|
+
import { CoreTusController } from './core-tus.controller';
|
|
7
|
+
import { CoreTusService } from './core-tus.service';
|
|
8
|
+
import { normalizeTusConfig } from './interfaces/tus-config.interface';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Token for injecting the TUS configuration
|
|
12
|
+
*/
|
|
13
|
+
export const TUS_CONFIG = 'TUS_CONFIG';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Options for TusModule.forRoot()
|
|
17
|
+
*/
|
|
18
|
+
export interface TusModuleOptions {
|
|
19
|
+
/**
|
|
20
|
+
* TUS configuration.
|
|
21
|
+
* Accepts:
|
|
22
|
+
* - `true` or `undefined`: Enable with defaults (enabled by default)
|
|
23
|
+
* - `false`: Disable TUS uploads
|
|
24
|
+
* - `{ ... }`: Enable with custom configuration
|
|
25
|
+
*/
|
|
26
|
+
config?: boolean | ITusConfig;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Custom controller class to use instead of CoreTusController.
|
|
30
|
+
* The class must extend CoreTusController.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```typescript
|
|
34
|
+
* @Controller('tus')
|
|
35
|
+
* @Roles(RoleEnum.S_USER) // Require authentication
|
|
36
|
+
* export class TusController extends CoreTusController {
|
|
37
|
+
* override async handleTus(...) {
|
|
38
|
+
* // Custom logic
|
|
39
|
+
* return super.handleTus(...);
|
|
40
|
+
* }
|
|
41
|
+
* }
|
|
42
|
+
*
|
|
43
|
+
* TusModule.forRoot({
|
|
44
|
+
* controller: TusController,
|
|
45
|
+
* })
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
controller?: Type<CoreTusController>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* TUS Module for resumable file uploads
|
|
53
|
+
*
|
|
54
|
+
* This module provides integration with the tus.io protocol via @tus/server.
|
|
55
|
+
* It is enabled by default with sensible defaults - no configuration required.
|
|
56
|
+
*
|
|
57
|
+
* Features:
|
|
58
|
+
* - Resumable uploads via tus.io protocol
|
|
59
|
+
* - Automatic migration to GridFS after upload completion
|
|
60
|
+
* - Configurable extensions (creation, termination, expiration, etc.)
|
|
61
|
+
* - Module Inheritance Pattern for customization
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```typescript
|
|
65
|
+
* // Default usage - enabled with all defaults
|
|
66
|
+
* @Module({
|
|
67
|
+
* imports: [
|
|
68
|
+
* CoreModule.forRoot(envConfig),
|
|
69
|
+
* TusModule.forRoot(), // No config needed
|
|
70
|
+
* ],
|
|
71
|
+
* })
|
|
72
|
+
* export class AppModule {}
|
|
73
|
+
*
|
|
74
|
+
* // Custom configuration
|
|
75
|
+
* TusModule.forRoot({
|
|
76
|
+
* config: {
|
|
77
|
+
* maxSize: 100 * 1024 * 1024, // 100 MB
|
|
78
|
+
* path: '/uploads',
|
|
79
|
+
* },
|
|
80
|
+
* })
|
|
81
|
+
*
|
|
82
|
+
* // Disable TUS
|
|
83
|
+
* TusModule.forRoot({ config: false })
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
86
|
+
@Global()
|
|
87
|
+
@Module({})
|
|
88
|
+
export class TusModule implements OnModuleInit {
|
|
89
|
+
private static logger = new Logger(TusModule.name);
|
|
90
|
+
private static tusEnabled = false;
|
|
91
|
+
private static currentConfig: ITusConfig | null = null;
|
|
92
|
+
private static customController: null | Type<CoreTusController> = null;
|
|
93
|
+
|
|
94
|
+
constructor(private readonly tusService?: CoreTusService) {}
|
|
95
|
+
|
|
96
|
+
async onModuleInit(): Promise<void> {
|
|
97
|
+
if (TusModule.tusEnabled && this.tusService?.isEnabled()) {
|
|
98
|
+
TusModule.logger.log('TusModule ready');
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Gets the controller class to use (custom or default)
|
|
104
|
+
*/
|
|
105
|
+
private static getControllerClass(): Type<CoreTusController> {
|
|
106
|
+
return this.customController || CoreTusController;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Creates a dynamic module for TUS uploads
|
|
111
|
+
*
|
|
112
|
+
* @param options - Configuration options (optional)
|
|
113
|
+
* @returns Dynamic module configuration
|
|
114
|
+
*/
|
|
115
|
+
static forRoot(options: TusModuleOptions = {}): DynamicModule {
|
|
116
|
+
const { config: rawConfig, controller } = options;
|
|
117
|
+
|
|
118
|
+
// Normalize config: undefined/true → enabled with defaults, false → disabled
|
|
119
|
+
const config = normalizeTusConfig(rawConfig);
|
|
120
|
+
|
|
121
|
+
// Store config for service configuration
|
|
122
|
+
this.currentConfig = config;
|
|
123
|
+
// Store custom controller if provided
|
|
124
|
+
this.customController = controller || null;
|
|
125
|
+
|
|
126
|
+
// If TUS is disabled, return minimal module
|
|
127
|
+
if (config === null) {
|
|
128
|
+
this.logger.debug('TUS uploads disabled');
|
|
129
|
+
this.tusEnabled = false;
|
|
130
|
+
return {
|
|
131
|
+
exports: [TUS_CONFIG, CoreTusService],
|
|
132
|
+
module: TusModule,
|
|
133
|
+
providers: [
|
|
134
|
+
{
|
|
135
|
+
provide: TUS_CONFIG,
|
|
136
|
+
useValue: null,
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
inject: [getConnectionToken()],
|
|
140
|
+
provide: CoreTusService,
|
|
141
|
+
useFactory: (connection: Connection) => {
|
|
142
|
+
const service = new CoreTusService(connection);
|
|
143
|
+
service.configure(false);
|
|
144
|
+
return service;
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
],
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Enable TUS
|
|
152
|
+
this.tusEnabled = true;
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
controllers: [this.getControllerClass()],
|
|
156
|
+
exports: [TUS_CONFIG, CoreTusService],
|
|
157
|
+
module: TusModule,
|
|
158
|
+
providers: [
|
|
159
|
+
{
|
|
160
|
+
provide: TUS_CONFIG,
|
|
161
|
+
useValue: config,
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
inject: [getConnectionToken(), TUS_CONFIG],
|
|
165
|
+
provide: CoreTusService,
|
|
166
|
+
useFactory: async (connection: Connection, tusConfig: ITusConfig) => {
|
|
167
|
+
const service = new CoreTusService(connection);
|
|
168
|
+
service.configure(tusConfig);
|
|
169
|
+
// Manually call onModuleInit since useFactory bypasses lifecycle hooks
|
|
170
|
+
await service.onModuleInit();
|
|
171
|
+
return service;
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
],
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Resets the static state of TusModule
|
|
180
|
+
* Useful for testing
|
|
181
|
+
*/
|
|
182
|
+
static reset(): void {
|
|
183
|
+
this.tusEnabled = false;
|
|
184
|
+
this.currentConfig = null;
|
|
185
|
+
this.customController = null;
|
|
186
|
+
}
|
|
187
|
+
}
|
package/src/core.module.ts
CHANGED
|
@@ -22,6 +22,7 @@ import { TemplateService } from './core/common/services/template.service';
|
|
|
22
22
|
import { BetterAuthUserMapper } from './core/modules/better-auth/better-auth-user.mapper';
|
|
23
23
|
import { BetterAuthModule } from './core/modules/better-auth/better-auth.module';
|
|
24
24
|
import { BetterAuthService } from './core/modules/better-auth/better-auth.service';
|
|
25
|
+
import { ErrorCodeModule } from './core/modules/error-code/error-code.module';
|
|
25
26
|
import { CoreHealthCheckModule } from './core/modules/health-check/core-health-check.module';
|
|
26
27
|
|
|
27
28
|
/**
|
|
@@ -226,6 +227,21 @@ export class CoreModule implements NestModule {
|
|
|
226
227
|
Object.assign({ driver: ApolloDriver }, config.graphQl.driver, config.graphQl.options),
|
|
227
228
|
),
|
|
228
229
|
];
|
|
230
|
+
|
|
231
|
+
// Add ErrorCodeModule based on configuration
|
|
232
|
+
// autoRegister defaults to true (backward compatible)
|
|
233
|
+
const errorCodeConfig = config.errorCode;
|
|
234
|
+
const isErrorCodeAutoRegister = errorCodeConfig?.autoRegister !== false;
|
|
235
|
+
|
|
236
|
+
if (isErrorCodeAutoRegister) {
|
|
237
|
+
// Always use forRoot() - it registers the controller and handles configuration
|
|
238
|
+
imports.push(
|
|
239
|
+
ErrorCodeModule.forRoot({
|
|
240
|
+
additionalErrorRegistry: errorCodeConfig?.additionalErrorRegistry,
|
|
241
|
+
}),
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
|
|
229
245
|
if (config.healthCheck) {
|
|
230
246
|
imports.push(CoreHealthCheckModule);
|
|
231
247
|
}
|
package/src/index.ts
CHANGED
|
@@ -131,6 +131,12 @@ export * from './core/modules/auth/tokens.decorator';
|
|
|
131
131
|
|
|
132
132
|
export * from './core/modules/better-auth';
|
|
133
133
|
|
|
134
|
+
// =====================================================================================================================
|
|
135
|
+
// Core - Modules - ErrorCode
|
|
136
|
+
// =====================================================================================================================
|
|
137
|
+
|
|
138
|
+
export * from './core/modules/error-code';
|
|
139
|
+
|
|
134
140
|
// =====================================================================================================================
|
|
135
141
|
// Core - Modules - File
|
|
136
142
|
// =====================================================================================================================
|
|
@@ -158,6 +164,12 @@ export * from './core/modules/health-check/core-health-check.service';
|
|
|
158
164
|
|
|
159
165
|
export * from './core/modules/migrate';
|
|
160
166
|
|
|
167
|
+
// =====================================================================================================================
|
|
168
|
+
// Core - Modules - Tus
|
|
169
|
+
// =====================================================================================================================
|
|
170
|
+
|
|
171
|
+
export * from './core/modules/tus';
|
|
172
|
+
|
|
161
173
|
// =====================================================================================================================
|
|
162
174
|
// Core - Modules - User
|
|
163
175
|
// =====================================================================================================================
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# Error Code Module - Reference Implementation
|
|
2
|
+
|
|
3
|
+
This directory contains the reference implementation for extending the ErrorCodeModule.
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
Demonstrates **Scenario C: Custom Service + Controller via forRoot()** where a project:
|
|
8
|
+
1. Defines its own error codes with a unique prefix (`SRV_*`)
|
|
9
|
+
2. Creates a custom service extending `CoreErrorCodeService`
|
|
10
|
+
3. Creates a **standalone** controller (not extending CoreErrorCodeController - see below)
|
|
11
|
+
4. Uses `ErrorCodeModule.forRoot({ service, controller })` from Core
|
|
12
|
+
|
|
13
|
+
**No custom module needed!** The Core ErrorCodeModule handles everything.
|
|
14
|
+
|
|
15
|
+
## Files
|
|
16
|
+
|
|
17
|
+
| File | Description |
|
|
18
|
+
|------|-------------|
|
|
19
|
+
| `error-codes.ts` | Server-specific error definitions (`SRV_*` prefix) |
|
|
20
|
+
| `error-code.service.ts` | Custom service registering `ServerErrors` |
|
|
21
|
+
| `error-code.controller.ts` | Custom controller with `/codes` endpoint |
|
|
22
|
+
| `index.ts` | Module exports |
|
|
23
|
+
|
|
24
|
+
## Architecture
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
┌──────────────────────────────────────────────────────────────┐
|
|
28
|
+
│ Core ErrorCodeModule.forRoot() │
|
|
29
|
+
│ (@Global) │
|
|
30
|
+
├──────────────────────────────────────────────────────────────┤
|
|
31
|
+
│ ┌─────────────────────┐ ┌─────────────────────────────┐ │
|
|
32
|
+
│ │ ErrorCodeService │ │ ErrorCodeController │ │
|
|
33
|
+
│ │ extends Core... │ │ (standalone - see below) │ │
|
|
34
|
+
│ │ │ │ │ │
|
|
35
|
+
│ │ - LTNS_* (core) │ │ GET /api/i18n/errors/codes│ │
|
|
36
|
+
│ │ - SRV_* (server) │ │ GET /api/i18n/errors/de │ │
|
|
37
|
+
│ │ │ │ GET /api/i18n/errors/en │ │
|
|
38
|
+
│ └─────────────────────┘ └─────────────────────────────┘ │
|
|
39
|
+
└──────────────────────────────────────────────────────────────┘
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Usage
|
|
43
|
+
|
|
44
|
+
### In ServerModule
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import { ErrorCodeModule } from '@lenne.tech/nest-server';
|
|
48
|
+
import { ErrorCodeController } from './modules/error-code/error-code.controller';
|
|
49
|
+
import { ErrorCodeService } from './modules/error-code/error-code.service';
|
|
50
|
+
|
|
51
|
+
@Module({
|
|
52
|
+
imports: [
|
|
53
|
+
CoreModule.forRoot({
|
|
54
|
+
// ...config
|
|
55
|
+
errorCode: {
|
|
56
|
+
autoRegister: false, // Required! Prevents CoreModule auto-registration
|
|
57
|
+
},
|
|
58
|
+
}),
|
|
59
|
+
// Use Core ErrorCodeModule with custom service and controller
|
|
60
|
+
ErrorCodeModule.forRoot({
|
|
61
|
+
controller: ErrorCodeController,
|
|
62
|
+
service: ErrorCodeService,
|
|
63
|
+
}),
|
|
64
|
+
],
|
|
65
|
+
})
|
|
66
|
+
export class ServerModule {}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### In Code
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
import { ErrorCode } from './modules/error-code/error-codes';
|
|
73
|
+
import { Errors } from '@lenne.tech/nest-server';
|
|
74
|
+
|
|
75
|
+
// Access error codes
|
|
76
|
+
console.log(ErrorCode.DEMO_ERROR); // '#SRV_0001: Demo error for testing'
|
|
77
|
+
|
|
78
|
+
// Use factory functions
|
|
79
|
+
throw new BadRequestException(Errors.userNotFound({ email: 'test@example.com' }));
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## When to Use This Pattern
|
|
83
|
+
|
|
84
|
+
Use Scenario C when you need:
|
|
85
|
+
- Custom REST endpoints (like `/codes`)
|
|
86
|
+
- Different route paths
|
|
87
|
+
- Complex controller logic
|
|
88
|
+
|
|
89
|
+
For simpler cases, see:
|
|
90
|
+
- **Scenario A**: `additionalErrorRegistry` in config (simplest)
|
|
91
|
+
- **Scenario B**: Custom service via inheritance
|
|
92
|
+
|
|
93
|
+
See `src/core/modules/error-code/INTEGRATION-CHECKLIST.md` for all scenarios.
|
|
94
|
+
|
|
95
|
+
## Important Notes
|
|
96
|
+
|
|
97
|
+
### Why Standalone Controller Instead of Extending?
|
|
98
|
+
|
|
99
|
+
The controller is **standalone** (does not extend `CoreErrorCodeController`) because:
|
|
100
|
+
|
|
101
|
+
**NestJS registers routes from parent classes first**, regardless of method declaration
|
|
102
|
+
order in child classes. This causes parameterized routes (`:locale`) to intercept
|
|
103
|
+
static routes (`/codes`), even if you re-declare the methods.
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
// DOES NOT WORK - parent route registered first!
|
|
107
|
+
@Controller('api/i18n/errors')
|
|
108
|
+
export class ErrorCodeController extends CoreErrorCodeController {
|
|
109
|
+
@Get('codes') // Registered AFTER parent's :locale
|
|
110
|
+
getAllCodes(): string[] { }
|
|
111
|
+
|
|
112
|
+
@Get(':locale') // Parent already registered this
|
|
113
|
+
override getTranslations() { }
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// WORKS - standalone ensures correct order
|
|
117
|
+
@Controller('api/i18n/errors')
|
|
118
|
+
export class ErrorCodeController {
|
|
119
|
+
@Get('codes') // Registered first
|
|
120
|
+
getAllCodes(): string[] { }
|
|
121
|
+
|
|
122
|
+
@Get(':locale') // Registered second
|
|
123
|
+
getTranslations() { }
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**Key insight:** NestJS Controller inheritance does NOT work like Service inheritance for route ordering.
|
|
128
|
+
|
|
129
|
+
### Why is `autoRegister: false` required?
|
|
130
|
+
|
|
131
|
+
NestJS `@Global()` modules use "first wins" for provider registration. Without `autoRegister: false`, CoreModule registers its ErrorCodeModule first, and your custom service is ignored.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { Controller, Get, NotFoundException, Param } from '@nestjs/common';
|
|
2
|
+
|
|
3
|
+
import { Roles } from '../../../core/common/decorators/roles.decorator';
|
|
4
|
+
import { RoleEnum } from '../../../core/common/enums/role.enum';
|
|
5
|
+
import { IErrorTranslationResponse, SupportedLocale } from '../../../core/modules/error-code/interfaces/error-code.interfaces';
|
|
6
|
+
import { ErrorCodeService } from './error-code.service';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Server Error Code Controller
|
|
10
|
+
*
|
|
11
|
+
* Project-specific Error Code controller that provides error translation endpoints.
|
|
12
|
+
* This is a standalone controller (not extending CoreErrorCodeController) to ensure
|
|
13
|
+
* correct route registration order.
|
|
14
|
+
*
|
|
15
|
+
* Endpoints:
|
|
16
|
+
* - GET /api/i18n/errors/codes - Get all available error codes (custom)
|
|
17
|
+
* - GET /api/i18n/errors/:locale - Get translations for a locale
|
|
18
|
+
*
|
|
19
|
+
* **WHY standalone instead of extending CoreErrorCodeController?**
|
|
20
|
+
* NestJS registers routes from parent classes first, regardless of method declaration
|
|
21
|
+
* order in child classes. This causes parameterized routes (`:locale`) to intercept
|
|
22
|
+
* static routes (`/codes`). A standalone controller ensures predictable route ordering.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```typescript
|
|
26
|
+
* // In your module
|
|
27
|
+
* ErrorCodeModule.forRoot({
|
|
28
|
+
* controller: ErrorCodeController,
|
|
29
|
+
* service: ErrorCodeService,
|
|
30
|
+
* })
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
@Controller('api/i18n/errors')
|
|
34
|
+
export class ErrorCodeController {
|
|
35
|
+
constructor(protected readonly errorCodeService: ErrorCodeService) {}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Get all available error codes
|
|
39
|
+
*
|
|
40
|
+
* Returns a list of all registered error codes from all registries.
|
|
41
|
+
* This endpoint must be defined BEFORE the :locale endpoint to prevent
|
|
42
|
+
* "codes" being interpreted as a locale parameter.
|
|
43
|
+
*
|
|
44
|
+
* @returns Array of error codes
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* Response:
|
|
48
|
+
* ```json
|
|
49
|
+
* ["LTNS_0001", "LTNS_0002", "SRV_0001", "SRV_0002"]
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
@Get('codes')
|
|
53
|
+
@Roles(RoleEnum.S_EVERYONE)
|
|
54
|
+
getAllCodes(): string[] {
|
|
55
|
+
return this.errorCodeService.getErrorCodes();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Get error translations for a specific locale
|
|
60
|
+
*
|
|
61
|
+
* Returns all error codes with their translations in Nuxt i18n compatible format.
|
|
62
|
+
*
|
|
63
|
+
* @param locale - Locale code (e.g., 'de', 'en')
|
|
64
|
+
* @returns Translations object
|
|
65
|
+
* @throws NotFoundException if locale is not supported
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* Response:
|
|
69
|
+
* ```json
|
|
70
|
+
* {
|
|
71
|
+
* "errors": {
|
|
72
|
+
* "LTNS_0001": "Benutzer mit E-Mail {email} wurde nicht gefunden.",
|
|
73
|
+
* "LTNS_0002": "Das eingegebene Passwort ist ungültig.",
|
|
74
|
+
* "SRV_0001": "Dies ist ein Demo-Fehler zu Testzwecken."
|
|
75
|
+
* }
|
|
76
|
+
* }
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
@Get(':locale')
|
|
80
|
+
@Roles(RoleEnum.S_EVERYONE)
|
|
81
|
+
getTranslations(@Param('locale') locale: string): IErrorTranslationResponse {
|
|
82
|
+
if (!this.errorCodeService.isLocaleSupported(locale)) {
|
|
83
|
+
throw new NotFoundException(
|
|
84
|
+
`Locale "${locale}" is not supported. ` +
|
|
85
|
+
`Supported locales: ${this.errorCodeService.getSupportedLocales().join(', ')}`,
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return this.errorCodeService.getTranslations(locale as SupportedLocale);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Injectable } from '@nestjs/common';
|
|
2
|
+
|
|
3
|
+
import { CoreErrorCodeService } from '../../../core/modules/error-code/core-error-code.service';
|
|
4
|
+
import { ServerErrors } from './error-codes';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Server Error Code Service
|
|
8
|
+
*
|
|
9
|
+
* Extends CoreErrorCodeService with project-specific error codes.
|
|
10
|
+
* This service is automatically registered when using ErrorCodeModule.forRoot()
|
|
11
|
+
* with the service option.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* // In your module
|
|
16
|
+
* ErrorCodeModule.forRoot({
|
|
17
|
+
* service: ErrorCodeService,
|
|
18
|
+
* })
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* // Extend with additional locales
|
|
24
|
+
* @Injectable()
|
|
25
|
+
* export class ErrorCodeService extends CoreErrorCodeService {
|
|
26
|
+
* protected override supportedLocales = ['de', 'en', 'fr', 'es'] as const;
|
|
27
|
+
*
|
|
28
|
+
* constructor() {
|
|
29
|
+
* super();
|
|
30
|
+
* this.registerErrorRegistry(ProjectErrors);
|
|
31
|
+
* }
|
|
32
|
+
* }
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
@Injectable()
|
|
36
|
+
export class ErrorCodeService extends CoreErrorCodeService {
|
|
37
|
+
constructor() {
|
|
38
|
+
super();
|
|
39
|
+
// Register project-specific errors
|
|
40
|
+
this.registerErrorRegistry(ServerErrors);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { IErrorRegistry, mergeErrorCodes } from '../../../core/modules/error-code/error-codes';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Server-specific Error Registry
|
|
5
|
+
*
|
|
6
|
+
* Project-specific error codes that extend the core LTNS_* errors.
|
|
7
|
+
* Use a unique prefix (e.g., SRV_ for Server) to avoid conflicts.
|
|
8
|
+
*
|
|
9
|
+
* Error code ranges for this project:
|
|
10
|
+
* - SRV_0001-SRV_0099: Business logic errors
|
|
11
|
+
* - SRV_0100-SRV_0199: Integration errors
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* import { UnprocessableEntityException } from '@nestjs/common';
|
|
16
|
+
* import { ErrorCode } from './error-codes';
|
|
17
|
+
*
|
|
18
|
+
* throw new UnprocessableEntityException(ErrorCode.DEMO_ERROR);
|
|
19
|
+
* // Response: { statusCode: 422, message: "#SRV_0001: Demo error for testing" }
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export const ServerErrors = {
|
|
23
|
+
// =====================================================
|
|
24
|
+
// Business Logic Errors (SRV_0001-SRV_0099)
|
|
25
|
+
// =====================================================
|
|
26
|
+
|
|
27
|
+
DEMO_ERROR: {
|
|
28
|
+
code: 'SRV_0001',
|
|
29
|
+
message: 'Demo error for testing',
|
|
30
|
+
translations: {
|
|
31
|
+
de: 'Dies ist ein Demo-Fehler zu Testzwecken.',
|
|
32
|
+
en: 'This is a demo error for testing purposes.',
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
FEATURE_NOT_AVAILABLE: {
|
|
37
|
+
code: 'SRV_0002',
|
|
38
|
+
message: 'Feature not available in this environment',
|
|
39
|
+
translations: {
|
|
40
|
+
de: 'Diese Funktion ist in dieser Umgebung nicht verfügbar.',
|
|
41
|
+
en: 'This feature is not available in this environment.',
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
} as const satisfies IErrorRegistry;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Merged ErrorCode object for use in this project
|
|
48
|
+
*
|
|
49
|
+
* Contains both core LTNS_* errors and project-specific SRV_* errors.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```typescript
|
|
53
|
+
* // Core error
|
|
54
|
+
* throw new UnauthorizedException(ErrorCode.UNAUTHORIZED);
|
|
55
|
+
*
|
|
56
|
+
* // Project error
|
|
57
|
+
* throw new UnprocessableEntityException(ErrorCode.DEMO_ERROR);
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export const ErrorCode = mergeErrorCodes(ServerErrors);
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Type for all available error code keys
|
|
64
|
+
*/
|
|
65
|
+
export type ServerErrorCodeKey = keyof typeof ErrorCode;
|
|
@@ -3,33 +3,41 @@ import {
|
|
|
3
3
|
Controller,
|
|
4
4
|
Delete,
|
|
5
5
|
Get,
|
|
6
|
-
NotFoundException,
|
|
7
6
|
Param,
|
|
8
7
|
Post,
|
|
9
|
-
Res,
|
|
10
8
|
UploadedFile,
|
|
11
9
|
UseInterceptors,
|
|
12
10
|
} from '@nestjs/common';
|
|
13
11
|
import { FileInterceptor } from '@nestjs/platform-express';
|
|
14
|
-
import { Response } from 'express';
|
|
15
12
|
import { Readable } from 'stream';
|
|
16
13
|
|
|
17
14
|
import { Roles } from '../../../core/common/decorators/roles.decorator';
|
|
18
15
|
import { RoleEnum } from '../../../core/common/enums/role.enum';
|
|
19
|
-
import {
|
|
16
|
+
import { CoreFileController } from '../../../core/modules/file/core-file.controller';
|
|
20
17
|
import { FileUpload } from '../../../core/modules/file/interfaces/file-upload.interface';
|
|
21
18
|
import { FileService } from './file.service';
|
|
22
19
|
|
|
23
20
|
/**
|
|
24
21
|
* File controller
|
|
22
|
+
*
|
|
23
|
+
* Extends CoreFileController to provide public download endpoints:
|
|
24
|
+
* - GET /files/id/:id - Download file by ID (public)
|
|
25
|
+
* - GET /files/:filename - Download file by filename (public)
|
|
26
|
+
*
|
|
27
|
+
* Adds admin-only endpoints:
|
|
28
|
+
* - POST /files/upload - Upload file (admin)
|
|
29
|
+
* - GET /files/info/:id - Get file info (admin)
|
|
30
|
+
* - DELETE /files/:id - Delete file (admin)
|
|
25
31
|
*/
|
|
26
32
|
@Controller('files')
|
|
27
33
|
@Roles(RoleEnum.ADMIN)
|
|
28
|
-
export class FileController {
|
|
34
|
+
export class FileController extends CoreFileController {
|
|
29
35
|
/**
|
|
30
36
|
* Import services
|
|
31
37
|
*/
|
|
32
|
-
constructor(
|
|
38
|
+
constructor(protected override readonly fileService: FileService) {
|
|
39
|
+
super(fileService);
|
|
40
|
+
}
|
|
33
41
|
|
|
34
42
|
/**
|
|
35
43
|
* Upload file via HTTP
|
|
@@ -54,34 +62,6 @@ export class FileController {
|
|
|
54
62
|
return await this.fileService.createFile(fileUpload);
|
|
55
63
|
}
|
|
56
64
|
|
|
57
|
-
/**
|
|
58
|
-
* Download file
|
|
59
|
-
*/
|
|
60
|
-
@Get(':id')
|
|
61
|
-
@Roles(RoleEnum.ADMIN)
|
|
62
|
-
async getFile(@Param('id') id: string, @Res() res: Response) {
|
|
63
|
-
if (!id) {
|
|
64
|
-
throw new BadRequestException('Missing ID');
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
let file: CoreFileInfo | null;
|
|
68
|
-
try {
|
|
69
|
-
file = await this.fileService.getFileInfo(id);
|
|
70
|
-
} catch (e) {
|
|
71
|
-
console.error(e);
|
|
72
|
-
file = null;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (!file) {
|
|
76
|
-
throw new NotFoundException('File not found');
|
|
77
|
-
}
|
|
78
|
-
const filestream = await this.fileService.getFileStream(id);
|
|
79
|
-
res.header('Content-Type', file.contentType || 'application/octet-stream');
|
|
80
|
-
res.header('Content-Disposition', `attachment; filename=${file.filename}`);
|
|
81
|
-
res.header('Cache-Control', 'max-age=31536000');
|
|
82
|
-
return filestream.pipe(res);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
65
|
/**
|
|
86
66
|
* Get file information
|
|
87
67
|
*/
|