@flusys/nestjs-shared 0.1.0-beta.1 → 0.1.0-beta.3
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/README.md +1399 -0
- package/cjs/classes/api-controller.class.js +473 -0
- package/cjs/classes/api-service.class.js +493 -0
- package/cjs/classes/hybrid-cache.class.js +96 -0
- package/cjs/classes/index.js +23 -0
- package/cjs/classes/request-scoped-api.service.js +110 -0
- package/cjs/classes/winston-logger-adapter.class.js +121 -0
- package/cjs/classes/winston.logger.class.js +251 -0
- package/cjs/constants/index.js +62 -0
- package/cjs/decorators/api-response.decorator.js +53 -0
- package/cjs/decorators/current-user.decorator.js +16 -0
- package/cjs/decorators/index.js +21 -0
- package/cjs/decorators/public.decorator.js +13 -0
- package/cjs/decorators/require-permission.decorator.js +32 -0
- package/cjs/dtos/delete.dto.js +82 -0
- package/cjs/dtos/filter-and-pagination.dto.js +174 -0
- package/cjs/dtos/identity-response.dto.js +112 -0
- package/cjs/dtos/index.js +22 -0
- package/cjs/dtos/pagination.dto.js +71 -0
- package/cjs/dtos/response-payload.dto.js +458 -0
- package/cjs/entities/identity.js +94 -0
- package/cjs/entities/index.js +19 -0
- package/cjs/entities/user-root.js +189 -0
- package/cjs/exceptions/index.js +18 -0
- package/cjs/exceptions/permission.exception.js +52 -0
- package/cjs/guards/index.js +19 -0
- package/cjs/guards/jwt-auth.guard.js +72 -0
- package/cjs/guards/permission.guard.js +266 -0
- package/cjs/index.js +28 -130
- package/cjs/interceptors/delete-empty-id-from-body.interceptor.js +40 -0
- package/cjs/interceptors/idempotency.interceptor.js +96 -0
- package/cjs/interceptors/index.js +25 -0
- package/cjs/interceptors/query-performance.interceptor.js +66 -0
- package/cjs/interceptors/response-meta.interceptor.js +47 -0
- package/cjs/interceptors/set-create-by-on-body.interceptor.js +40 -0
- package/cjs/interceptors/set-delete-by-on-body.interceptor.js +40 -0
- package/cjs/interceptors/set-update-by-on-body.interceptor.js +40 -0
- package/cjs/interceptors/slug.interceptor.js +54 -0
- package/cjs/interfaces/api.interface.js +4 -0
- package/cjs/interfaces/base-query.interface.js +6 -0
- package/cjs/interfaces/identity.interface.js +4 -0
- package/cjs/interfaces/index.js +23 -0
- package/cjs/interfaces/logged-user-info.interface.js +7 -0
- package/cjs/interfaces/logger.interface.js +7 -0
- package/cjs/interfaces/permission.interface.js +13 -0
- package/cjs/middlewares/index.js +18 -0
- package/cjs/middlewares/logger.middleware.js +250 -0
- package/cjs/modules/cache/cache.module.js +39 -0
- package/cjs/modules/datasource/datasource.module.js +120 -0
- package/cjs/modules/datasource/index.js +19 -0
- package/cjs/modules/datasource/multi-tenant-datasource.service.js +345 -0
- package/cjs/modules/index.js +21 -0
- package/cjs/modules/utils/utils.module.js +32 -0
- package/cjs/modules/utils/utils.service.js +255 -0
- package/cjs/utils/error-handler.util.js +67 -0
- package/cjs/utils/index.js +18 -0
- package/fesm/classes/api-controller.class.js +510 -0
- package/fesm/classes/api-service.class.js +487 -0
- package/fesm/classes/hybrid-cache.class.js +81 -0
- package/fesm/classes/index.js +6 -0
- package/fesm/classes/request-scoped-api.service.js +100 -0
- package/fesm/classes/winston-logger-adapter.class.js +124 -0
- package/fesm/classes/winston.logger.class.js +208 -0
- package/fesm/constants/index.js +29 -0
- package/fesm/decorators/api-response.decorator.js +60 -0
- package/fesm/decorators/current-user.decorator.js +18 -0
- package/fesm/decorators/index.js +4 -0
- package/fesm/decorators/public.decorator.js +20 -0
- package/fesm/decorators/require-permission.decorator.js +75 -0
- package/fesm/dtos/delete.dto.js +72 -0
- package/fesm/dtos/filter-and-pagination.dto.js +169 -0
- package/fesm/dtos/identity-response.dto.js +102 -0
- package/fesm/dtos/index.js +5 -0
- package/fesm/dtos/pagination.dto.js +61 -0
- package/fesm/dtos/response-payload.dto.js +436 -0
- package/fesm/entities/identity.js +84 -0
- package/fesm/entities/index.js +2 -0
- package/fesm/entities/user-root.js +179 -0
- package/fesm/exceptions/index.js +1 -0
- package/fesm/exceptions/permission.exception.js +37 -0
- package/fesm/guards/index.js +2 -0
- package/fesm/guards/jwt-auth.guard.js +62 -0
- package/fesm/guards/permission.guard.js +256 -0
- package/fesm/index.js +12 -131
- package/fesm/interceptors/delete-empty-id-from-body.interceptor.js +30 -0
- package/fesm/interceptors/idempotency.interceptor.js +86 -0
- package/fesm/interceptors/index.js +8 -0
- package/fesm/interceptors/query-performance.interceptor.js +56 -0
- package/fesm/interceptors/response-meta.interceptor.js +37 -0
- package/fesm/interceptors/set-create-by-on-body.interceptor.js +30 -0
- package/fesm/interceptors/set-delete-by-on-body.interceptor.js +30 -0
- package/fesm/interceptors/set-update-by-on-body.interceptor.js +30 -0
- package/fesm/interceptors/slug.interceptor.js +44 -0
- package/fesm/interfaces/api.interface.js +1 -0
- package/fesm/interfaces/base-query.interface.js +3 -0
- package/fesm/interfaces/identity.interface.js +1 -0
- package/fesm/interfaces/index.js +6 -0
- package/fesm/interfaces/logged-user-info.interface.js +4 -0
- package/fesm/interfaces/logger.interface.js +4 -0
- package/fesm/interfaces/permission.interface.js +15 -0
- package/fesm/middlewares/index.js +1 -0
- package/fesm/middlewares/logger.middleware.js +215 -0
- package/fesm/modules/cache/cache.module.js +29 -0
- package/fesm/modules/datasource/datasource.module.js +110 -0
- package/fesm/modules/datasource/index.js +2 -0
- package/fesm/modules/datasource/multi-tenant-datasource.service.js +335 -0
- package/fesm/modules/index.js +4 -0
- package/fesm/modules/utils/utils.module.js +22 -0
- package/fesm/modules/utils/utils.service.js +245 -0
- package/fesm/utils/error-handler.util.js +59 -0
- package/fesm/utils/index.js +1 -0
- package/package.json +32 -27
- package/cjs/classes-index.js +0 -131
- package/cjs/constants-index.js +0 -1
- package/cjs/decorators-index.js +0 -1
- package/cjs/dtos-index.js +0 -1
- package/cjs/entities-index.js +0 -1
- package/cjs/exceptions-index.js +0 -1
- package/cjs/guards-index.js +0 -2
- package/cjs/interceptors-index.js +0 -106
- package/cjs/interfaces-index.js +0 -1
- package/cjs/middlewares-index.js +0 -2
- package/cjs/modules-index.js +0 -114
- package/cjs/utils-index.js +0 -1
- package/fesm/classes-index.js +0 -131
- package/fesm/constants-index.js +0 -1
- package/fesm/decorators-index.js +0 -1
- package/fesm/dtos-index.js +0 -1
- package/fesm/entities-index.js +0 -1
- package/fesm/exceptions-index.js +0 -1
- package/fesm/guards-index.js +0 -2
- package/fesm/interceptors-index.js +0 -106
- package/fesm/interfaces-index.js +0 -0
- package/fesm/middlewares-index.js +0 -2
- package/fesm/modules-index.js +0 -114
- package/fesm/utils-index.js +0 -1
package/README.md
ADDED
|
@@ -0,0 +1,1399 @@
|
|
|
1
|
+
# Shared Package Guide
|
|
2
|
+
|
|
3
|
+
> **Package:** `@flusys/nestjs-shared`
|
|
4
|
+
> **Type:** Shared NestJS utilities, classes, decorators, guards, and modules
|
|
5
|
+
|
|
6
|
+
This comprehensive guide covers the shared package - the shared NestJS infrastructure layer.
|
|
7
|
+
|
|
8
|
+
## Table of Contents
|
|
9
|
+
|
|
10
|
+
- [Overview](#overview)
|
|
11
|
+
- [Installation](#installation)
|
|
12
|
+
- [Package Architecture](#package-architecture)
|
|
13
|
+
- [ApiService - Generic CRUD Service](#apiservice---generic-crud-service)
|
|
14
|
+
- [ApiController - Generic CRUD Controller](#apicontroller---generic-crud-controller)
|
|
15
|
+
- [Decorators](#decorators)
|
|
16
|
+
- [Guards](#guards)
|
|
17
|
+
- [Middleware](#middleware)
|
|
18
|
+
- [Interceptors](#interceptors)
|
|
19
|
+
- [Caching System](#caching-system)
|
|
20
|
+
- [Multi-Tenant DataSource](#multi-tenant-datasource)
|
|
21
|
+
- [DTOs](#dtos)
|
|
22
|
+
- [Base Entities](#base-entities)
|
|
23
|
+
- [Error Handling](#error-handling)
|
|
24
|
+
- [API Reference](#api-reference)
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Overview
|
|
29
|
+
|
|
30
|
+
`@flusys/nestjs-shared` provides shared utilities for building scalable NestJS applications:
|
|
31
|
+
|
|
32
|
+
- **Generic CRUD** - Standardized API controller and service patterns
|
|
33
|
+
- **Permission System** - Role and permission-based access control
|
|
34
|
+
- **Caching** - In-memory + Redis hybrid caching
|
|
35
|
+
- **Request Correlation** - AsyncLocalStorage-based request tracking (NEW)
|
|
36
|
+
- **Middleware** - Logging, correlation, and performance monitoring (NEW)
|
|
37
|
+
- **Interceptors** - Response metadata, idempotency, auto field setting
|
|
38
|
+
- **Multi-Tenancy** - Dynamic database connection management
|
|
39
|
+
- **Error Handling** - Centralized error handling utilities
|
|
40
|
+
|
|
41
|
+
### Package Hierarchy
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
@flusys/nestjs-core ← Pure TypeScript (foundation)
|
|
45
|
+
↓
|
|
46
|
+
@flusys/nestjs-shared ← Shared NestJS utilities (THIS PACKAGE)
|
|
47
|
+
↓
|
|
48
|
+
@flusys/nestjs-auth ← Uses common classes
|
|
49
|
+
↓
|
|
50
|
+
@flusys/nestjs-iam ← Uses common patterns
|
|
51
|
+
↓
|
|
52
|
+
@flusys/nestjs-storage ← Uses common patterns
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Installation
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
npm install @flusys/nestjs-shared @flusys/nestjs-core
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Package Architecture
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
nestjs-shared/
|
|
69
|
+
├── src/
|
|
70
|
+
│ ├── classes/ # Base classes
|
|
71
|
+
│ │ ├── api-service.class.ts # Generic CRUD service
|
|
72
|
+
│ │ ├── api-controller.class.ts # Generic CRUD controller factory
|
|
73
|
+
│ │ ├── request-scoped-api.service.ts # REQUEST-scoped service base
|
|
74
|
+
│ │ ├── hybrid-cache.class.ts # Two-tier caching
|
|
75
|
+
│ │ ├── winston.logger.class.ts # Winston logger config
|
|
76
|
+
│ │ ├── winston-logger-adapter.class.ts # Logger adapters
|
|
77
|
+
│ │ └── index.ts
|
|
78
|
+
│ │
|
|
79
|
+
│ ├── constants/ # Injection tokens & constants
|
|
80
|
+
│ │ └── index.ts
|
|
81
|
+
│ │
|
|
82
|
+
│ ├── decorators/ # Custom decorators
|
|
83
|
+
│ │ ├── api-response.decorator.ts # Swagger response decorator
|
|
84
|
+
│ │ ├── current-user.decorator.ts
|
|
85
|
+
│ │ ├── public.decorator.ts
|
|
86
|
+
│ │ ├── require-permission.decorator.ts
|
|
87
|
+
│ │ └── index.ts
|
|
88
|
+
│ │
|
|
89
|
+
│ ├── dtos/ # Shared DTOs
|
|
90
|
+
│ │ ├── delete.dto.ts
|
|
91
|
+
│ │ ├── filter-and-pagination.dto.ts
|
|
92
|
+
│ │ ├── identity-response.dto.ts
|
|
93
|
+
│ │ ├── pagination.dto.ts
|
|
94
|
+
│ │ ├── response-payload.dto.ts
|
|
95
|
+
│ │ └── index.ts
|
|
96
|
+
│ │
|
|
97
|
+
│ ├── entities/ # Base entities
|
|
98
|
+
│ │ ├── identity.ts
|
|
99
|
+
│ │ ├── user-root.ts
|
|
100
|
+
│ │ └── index.ts
|
|
101
|
+
│ │
|
|
102
|
+
│ ├── exceptions/ # Custom exceptions
|
|
103
|
+
│ │ ├── permission.exception.ts
|
|
104
|
+
│ │ └── index.ts
|
|
105
|
+
│ │
|
|
106
|
+
│ ├── guards/ # Authentication & authorization
|
|
107
|
+
│ │ ├── jwt-auth.guard.ts # JWT token validation
|
|
108
|
+
│ │ ├── permission.guard.ts # Permission checks
|
|
109
|
+
│ │ └── index.ts
|
|
110
|
+
│ │
|
|
111
|
+
│ ├── interceptors/ # Request/response interceptors
|
|
112
|
+
│ │ ├── delete-empty-id-from-body.interceptor.ts
|
|
113
|
+
│ │ ├── idempotency.interceptor.ts
|
|
114
|
+
│ │ ├── query-performance.interceptor.ts
|
|
115
|
+
│ │ ├── response-meta.interceptor.ts
|
|
116
|
+
│ │ ├── set-create-by-on-body.interceptor.ts
|
|
117
|
+
│ │ ├── set-delete-by-on-body.interceptor.ts
|
|
118
|
+
│ │ ├── set-update-by-on-body.interceptor.ts
|
|
119
|
+
│ │ ├── slug.interceptor.ts
|
|
120
|
+
│ │ └── index.ts
|
|
121
|
+
│ │
|
|
122
|
+
│ ├── interfaces/ # TypeScript interfaces
|
|
123
|
+
│ │ ├── api.interface.ts
|
|
124
|
+
│ │ ├── base-query.interface.ts
|
|
125
|
+
│ │ ├── identity.interface.ts
|
|
126
|
+
│ │ ├── logged-user-info.interface.ts
|
|
127
|
+
│ │ ├── logger.interface.ts
|
|
128
|
+
│ │ ├── permission.interface.ts
|
|
129
|
+
│ │ └── index.ts
|
|
130
|
+
│ │
|
|
131
|
+
│ ├── middlewares/ # Middleware
|
|
132
|
+
│ │ ├── logger.middleware.ts # Request logging & correlation
|
|
133
|
+
│ │ └── index.ts
|
|
134
|
+
│ │
|
|
135
|
+
│ ├── modules/ # NestJS modules
|
|
136
|
+
│ │ ├── cache/cache.module.ts
|
|
137
|
+
│ │ ├── datasource/
|
|
138
|
+
│ │ │ ├── datasource.module.ts
|
|
139
|
+
│ │ │ ├── multi-tenant-datasource.service.ts
|
|
140
|
+
│ │ │ └── index.ts
|
|
141
|
+
│ │ ├── utils/
|
|
142
|
+
│ │ │ ├── utils.module.ts
|
|
143
|
+
│ │ │ └── utils.service.ts
|
|
144
|
+
│ │ └── index.ts
|
|
145
|
+
│ │
|
|
146
|
+
│ └── utils/ # Utility functions
|
|
147
|
+
│ ├── error-handler.util.ts
|
|
148
|
+
│ └── index.ts
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## ApiService - Generic CRUD Service
|
|
154
|
+
|
|
155
|
+
The `ApiService` base class provides standardized CRUD operations with caching, pagination, and transaction support.
|
|
156
|
+
|
|
157
|
+
### Basic Usage
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
import { ApiService, HybridCache } from '@flusys/nestjs-shared/classes';
|
|
161
|
+
import { UtilsService } from '@flusys/nestjs-shared/modules';
|
|
162
|
+
import { Injectable, Inject } from '@nestjs/common';
|
|
163
|
+
import { InjectRepository } from '@nestjs/typeorm';
|
|
164
|
+
import { Repository } from 'typeorm';
|
|
165
|
+
import { User } from './user.entity';
|
|
166
|
+
import { CreateUserDto, UpdateUserDto } from './user.dto';
|
|
167
|
+
import { IUser } from './user.interface';
|
|
168
|
+
|
|
169
|
+
@Injectable()
|
|
170
|
+
export class UserService extends ApiService<
|
|
171
|
+
CreateUserDto, // Create DTO
|
|
172
|
+
UpdateUserDto, // Update DTO
|
|
173
|
+
IUser, // Interface
|
|
174
|
+
User, // Entity
|
|
175
|
+
Repository<User> // Repository type
|
|
176
|
+
> {
|
|
177
|
+
constructor(
|
|
178
|
+
@InjectRepository(User)
|
|
179
|
+
protected override repository: Repository<User>,
|
|
180
|
+
@Inject('CACHE_INSTANCE')
|
|
181
|
+
protected override cacheManager: HybridCache,
|
|
182
|
+
protected override utilsService: UtilsService,
|
|
183
|
+
) {
|
|
184
|
+
super(
|
|
185
|
+
'user', // Entity name (for query building)
|
|
186
|
+
repository,
|
|
187
|
+
cacheManager,
|
|
188
|
+
utilsService,
|
|
189
|
+
'UserService', // Service name (for logging)
|
|
190
|
+
true, // Enable caching
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Override to customize DTO to entity conversion
|
|
195
|
+
override async convertSingleDtoToEntity(
|
|
196
|
+
dto: CreateUserDto | UpdateUserDto,
|
|
197
|
+
user: ILoggedUserInfo,
|
|
198
|
+
): Promise<User> {
|
|
199
|
+
const entity = new User();
|
|
200
|
+
Object.assign(entity, dto);
|
|
201
|
+
return entity;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Override to customize query selection
|
|
205
|
+
override async getSelectQuery(
|
|
206
|
+
query: SelectQueryBuilder<User>,
|
|
207
|
+
user: ILoggedUserInfo,
|
|
208
|
+
select?: string[],
|
|
209
|
+
) {
|
|
210
|
+
if (!select?.length) {
|
|
211
|
+
select = ['id', 'name', 'email', 'createdAt'];
|
|
212
|
+
}
|
|
213
|
+
const selectFields = select.map(f => `${this.entityName}.${f}`);
|
|
214
|
+
query.select(selectFields);
|
|
215
|
+
return { query, isRaw: false };
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### ApiService Methods
|
|
221
|
+
|
|
222
|
+
| Method | Description |
|
|
223
|
+
|--------|-------------|
|
|
224
|
+
| `insert(dto, user)` | Create single entity |
|
|
225
|
+
| `insertMany(dtos, user)` | Create multiple entities |
|
|
226
|
+
| `getById(id, user, select?)` | Get entity by ID |
|
|
227
|
+
| `findById(id, user, select?)` | Find entity (returns null if not found) |
|
|
228
|
+
| `getAll(dto, user)` | Get paginated list |
|
|
229
|
+
| `update(dto, user)` | Update single entity |
|
|
230
|
+
| `updateMany(dtos, user)` | Update multiple entities |
|
|
231
|
+
| `delete(dto, user)` | Soft/permanent delete |
|
|
232
|
+
| `restore(dto, user)` | Restore soft-deleted |
|
|
233
|
+
|
|
234
|
+
### Customization Hooks
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
@Injectable()
|
|
238
|
+
export class UserService extends ApiService<...> {
|
|
239
|
+
// Convert DTO to entity
|
|
240
|
+
override async convertSingleDtoToEntity(dto, user): Promise<User> { }
|
|
241
|
+
|
|
242
|
+
// Customize SELECT query
|
|
243
|
+
override async getSelectQuery(query, user, select?): Promise<{ query, isRaw }> { }
|
|
244
|
+
|
|
245
|
+
// Add WHERE filters
|
|
246
|
+
override async getFilterQuery(query, filter, user): Promise<{ query, isRaw }> { }
|
|
247
|
+
|
|
248
|
+
// Add extra query conditions (e.g., company filtering)
|
|
249
|
+
override async getExtraManipulateQuery(query, dto, user): Promise<{ query, isRaw }> { }
|
|
250
|
+
|
|
251
|
+
// Before insert hook
|
|
252
|
+
override async beforeInsertOperation(entity, user, queryRunner): Promise<void> { }
|
|
253
|
+
|
|
254
|
+
// After insert hook
|
|
255
|
+
override async afterInsertOperation(entity, user, queryRunner): Promise<void> { }
|
|
256
|
+
|
|
257
|
+
// Before update hook
|
|
258
|
+
override async beforeUpdateOperation(oldEntity, newEntity, user, queryRunner): Promise<void> { }
|
|
259
|
+
|
|
260
|
+
// After update hook
|
|
261
|
+
override async afterUpdateOperation(entity, user, queryRunner): Promise<void> { }
|
|
262
|
+
|
|
263
|
+
// Before delete hook
|
|
264
|
+
override async beforeDeleteOperation(dto, user, queryRunner): Promise<void> { }
|
|
265
|
+
|
|
266
|
+
// After delete hook
|
|
267
|
+
override async afterDeleteOperation(dto, user, queryRunner): Promise<void> { }
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### Company/Branch Filtering Example
|
|
272
|
+
|
|
273
|
+
```typescript
|
|
274
|
+
override async getExtraManipulateQuery(
|
|
275
|
+
query: SelectQueryBuilder<User>,
|
|
276
|
+
dto: FilterAndPaginationDto,
|
|
277
|
+
user: ILoggedUserInfo,
|
|
278
|
+
) {
|
|
279
|
+
// Filter by user's company
|
|
280
|
+
if (user.companyId) {
|
|
281
|
+
query.andWhere(`${this.entityName}.companyId = :companyId`, {
|
|
282
|
+
companyId: user.companyId,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Filter by user's branch
|
|
287
|
+
if (user.branchId) {
|
|
288
|
+
query.andWhere(`${this.entityName}.branchId = :branchId`, {
|
|
289
|
+
branchId: user.branchId,
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return { query, isRaw: false };
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
---
|
|
298
|
+
|
|
299
|
+
## ApiController - Generic CRUD Controller
|
|
300
|
+
|
|
301
|
+
The `createApiController` factory creates standardized REST API controllers.
|
|
302
|
+
|
|
303
|
+
### Basic Usage
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
import { createApiController } from '@flusys/nestjs-shared/classes';
|
|
307
|
+
import { Controller } from '@nestjs/common';
|
|
308
|
+
import { ApiTags } from '@nestjs/swagger';
|
|
309
|
+
import { CreateUserDto, UpdateUserDto, UserResponseDto } from './user.dto';
|
|
310
|
+
import { IUser } from './user.interface';
|
|
311
|
+
import { UserService } from './user.service';
|
|
312
|
+
|
|
313
|
+
@ApiTags('Users')
|
|
314
|
+
@Controller('users')
|
|
315
|
+
export class UserController extends createApiController<
|
|
316
|
+
CreateUserDto,
|
|
317
|
+
UpdateUserDto,
|
|
318
|
+
UserResponseDto,
|
|
319
|
+
IUser,
|
|
320
|
+
UserService
|
|
321
|
+
>(CreateUserDto, UpdateUserDto, UserResponseDto) {
|
|
322
|
+
constructor(protected service: UserService) {
|
|
323
|
+
super(service);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### Generated Endpoints
|
|
329
|
+
|
|
330
|
+
| Endpoint | Method | Description |
|
|
331
|
+
|----------|--------|-------------|
|
|
332
|
+
| `/insert` | POST | Create entity |
|
|
333
|
+
| `/insert-many` | POST | Create multiple entities |
|
|
334
|
+
| `/get/:id` | POST | Get entity by ID |
|
|
335
|
+
| `/get-all` | POST | Get paginated list |
|
|
336
|
+
| `/update` | POST | Update entity |
|
|
337
|
+
| `/update-many` | POST | Update multiple entities |
|
|
338
|
+
| `/delete` | POST | Delete entity |
|
|
339
|
+
|
|
340
|
+
### Security Configuration
|
|
341
|
+
|
|
342
|
+
```typescript
|
|
343
|
+
// Global security (all endpoints)
|
|
344
|
+
@Controller('users')
|
|
345
|
+
export class UserController extends createApiController(
|
|
346
|
+
CreateUserDto,
|
|
347
|
+
UpdateUserDto,
|
|
348
|
+
UserResponseDto,
|
|
349
|
+
{
|
|
350
|
+
security: 'jwt', // All endpoints require JWT
|
|
351
|
+
},
|
|
352
|
+
) {}
|
|
353
|
+
|
|
354
|
+
// Per-endpoint security
|
|
355
|
+
@Controller('users')
|
|
356
|
+
export class UserController extends createApiController(
|
|
357
|
+
CreateUserDto,
|
|
358
|
+
UpdateUserDto,
|
|
359
|
+
UserResponseDto,
|
|
360
|
+
{
|
|
361
|
+
security: {
|
|
362
|
+
getAll: 'public', // No auth required
|
|
363
|
+
getById: 'jwt', // JWT required
|
|
364
|
+
insert: { level: 'permission', permissions: ['users.create'] },
|
|
365
|
+
update: { level: 'permission', permissions: ['users.update'] },
|
|
366
|
+
delete: { level: 'permission', permissions: ['users.delete'] },
|
|
367
|
+
},
|
|
368
|
+
},
|
|
369
|
+
) {}
|
|
370
|
+
|
|
371
|
+
// Permission combinations
|
|
372
|
+
{
|
|
373
|
+
security: {
|
|
374
|
+
// Require ANY of these permissions
|
|
375
|
+
insert: {
|
|
376
|
+
level: 'permission',
|
|
377
|
+
permissions: ['users.create', 'users.admin'],
|
|
378
|
+
require: 'any', // Default
|
|
379
|
+
},
|
|
380
|
+
|
|
381
|
+
// Require ALL permissions
|
|
382
|
+
delete: {
|
|
383
|
+
level: 'permission',
|
|
384
|
+
permissions: ['users.delete', 'users.admin'],
|
|
385
|
+
require: 'all',
|
|
386
|
+
},
|
|
387
|
+
},
|
|
388
|
+
}
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
See [API-CONTROLLER-SECURITY.md](./API-CONTROLLER-SECURITY.md) for detailed security configuration.
|
|
392
|
+
|
|
393
|
+
---
|
|
394
|
+
|
|
395
|
+
## Decorators
|
|
396
|
+
|
|
397
|
+
### @CurrentUser
|
|
398
|
+
|
|
399
|
+
Extract the logged-in user from request:
|
|
400
|
+
|
|
401
|
+
```typescript
|
|
402
|
+
import { CurrentUser } from '@flusys/nestjs-shared/decorators';
|
|
403
|
+
import { ILoggedUserInfo } from '@flusys/nestjs-shared/interfaces';
|
|
404
|
+
|
|
405
|
+
@Controller('profile')
|
|
406
|
+
export class ProfileController {
|
|
407
|
+
@Get()
|
|
408
|
+
getProfile(@CurrentUser() user: ILoggedUserInfo) {
|
|
409
|
+
return { userId: user.id, companyId: user.companyId };
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
### @Public
|
|
415
|
+
|
|
416
|
+
Mark route as public (skip authentication):
|
|
417
|
+
|
|
418
|
+
```typescript
|
|
419
|
+
import { Public } from '@flusys/nestjs-shared/decorators';
|
|
420
|
+
|
|
421
|
+
@Controller('auth')
|
|
422
|
+
export class AuthController {
|
|
423
|
+
@Public()
|
|
424
|
+
@Post('login')
|
|
425
|
+
login() {
|
|
426
|
+
// No JWT required
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
### @RequirePermission
|
|
432
|
+
|
|
433
|
+
Require specific permission:
|
|
434
|
+
|
|
435
|
+
```typescript
|
|
436
|
+
import { RequirePermission } from '@flusys/nestjs-shared/decorators';
|
|
437
|
+
|
|
438
|
+
@Controller('admin')
|
|
439
|
+
export class AdminController {
|
|
440
|
+
@RequirePermission('admin.dashboard')
|
|
441
|
+
@Get('dashboard')
|
|
442
|
+
getDashboard() {
|
|
443
|
+
// Requires 'admin.dashboard' permission
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
### @RequireAnyPermission
|
|
449
|
+
|
|
450
|
+
Require any of the listed permissions:
|
|
451
|
+
|
|
452
|
+
```typescript
|
|
453
|
+
import { RequireAnyPermission } from '@flusys/nestjs-shared/decorators';
|
|
454
|
+
|
|
455
|
+
@Controller('reports')
|
|
456
|
+
export class ReportsController {
|
|
457
|
+
@RequireAnyPermission('reports.view', 'reports.admin')
|
|
458
|
+
@Get()
|
|
459
|
+
getReports() {
|
|
460
|
+
// Requires 'reports.view' OR 'reports.admin'
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
### @RequireAllPermissions
|
|
466
|
+
|
|
467
|
+
Require all listed permissions:
|
|
468
|
+
|
|
469
|
+
```typescript
|
|
470
|
+
import { RequireAllPermissions } from '@flusys/nestjs-shared/decorators';
|
|
471
|
+
|
|
472
|
+
@Controller('sensitive')
|
|
473
|
+
export class SensitiveController {
|
|
474
|
+
@RequireAllPermissions('admin.access', 'security.clearance')
|
|
475
|
+
@Get()
|
|
476
|
+
getSensitiveData() {
|
|
477
|
+
// Requires BOTH permissions
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
---
|
|
483
|
+
|
|
484
|
+
## Guards
|
|
485
|
+
|
|
486
|
+
The shared package provides two guards for authentication and authorization:
|
|
487
|
+
|
|
488
|
+
### JwtAuthGuard
|
|
489
|
+
|
|
490
|
+
**Location:** `@flusys/nestjs-shared/guards/jwt-auth.guard.ts`
|
|
491
|
+
|
|
492
|
+
The `JwtAuthGuard` validates JWT tokens for protected routes. It extends Passport's `AuthGuard('jwt')` and respects the `@Public()` decorator.
|
|
493
|
+
|
|
494
|
+
**Why in shared module:**
|
|
495
|
+
- All feature modules (auth, iam, storage) need JWT authentication
|
|
496
|
+
- Keeps feature modules independent (no cross-dependencies)
|
|
497
|
+
- JwtStrategy registration remains in auth module
|
|
498
|
+
- This guard just validates tokens using the registered strategy
|
|
499
|
+
|
|
500
|
+
**Usage:**
|
|
501
|
+
|
|
502
|
+
```typescript
|
|
503
|
+
import { JwtAuthGuard } from '@flusys/nestjs-shared/guards';
|
|
504
|
+
import { UseGuards } from '@nestjs/common';
|
|
505
|
+
|
|
506
|
+
// Protect entire controller
|
|
507
|
+
@Controller('users')
|
|
508
|
+
@UseGuards(JwtAuthGuard)
|
|
509
|
+
export class UserController {
|
|
510
|
+
// All routes protected
|
|
511
|
+
|
|
512
|
+
@Post('login')
|
|
513
|
+
@Public() // Skip JWT check for this route
|
|
514
|
+
async login() { }
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// Or apply globally
|
|
518
|
+
@Module({
|
|
519
|
+
providers: [
|
|
520
|
+
{
|
|
521
|
+
provide: APP_GUARD,
|
|
522
|
+
useClass: JwtAuthGuard,
|
|
523
|
+
},
|
|
524
|
+
],
|
|
525
|
+
})
|
|
526
|
+
export class AppModule {}
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
**Features:**
|
|
530
|
+
- Validates JWT tokens from `Authorization: Bearer <token>` header
|
|
531
|
+
- Respects `@Public()` decorator to skip authentication
|
|
532
|
+
- Throws `UnauthorizedException` for invalid/expired tokens
|
|
533
|
+
- Works with JwtStrategy registered in auth module
|
|
534
|
+
|
|
535
|
+
**Note:** The JwtStrategy (which registers the JWT validation logic with Passport) is still in the auth module at `@flusys/nestjs-auth/strategies/jwt.strategy.ts`. This guard uses that registered strategy.
|
|
536
|
+
|
|
537
|
+
### PermissionGuard
|
|
538
|
+
|
|
539
|
+
**Location:** `@flusys/nestjs-shared/guards/permission.guard.ts`
|
|
540
|
+
|
|
541
|
+
The `PermissionGuard` handles permission-based access control.
|
|
542
|
+
|
|
543
|
+
```typescript
|
|
544
|
+
import { Module } from '@nestjs/common';
|
|
545
|
+
import { APP_GUARD } from '@nestjs/core';
|
|
546
|
+
import { PermissionGuard } from '@flusys/nestjs-shared/guards';
|
|
547
|
+
|
|
548
|
+
@Module({
|
|
549
|
+
providers: [
|
|
550
|
+
{
|
|
551
|
+
provide: APP_GUARD,
|
|
552
|
+
useClass: PermissionGuard,
|
|
553
|
+
},
|
|
554
|
+
],
|
|
555
|
+
})
|
|
556
|
+
export class AppModule {}
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
### Permission Cache Key Format
|
|
560
|
+
|
|
561
|
+
```typescript
|
|
562
|
+
// Without company feature
|
|
563
|
+
`permissions:user:{userId}`
|
|
564
|
+
|
|
565
|
+
// With company feature (includes branchId for DIRECT permissions)
|
|
566
|
+
`permissions:company:{companyId}:branch:{branchId}:user:{userId}`
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
### Permission Guard Configuration
|
|
570
|
+
|
|
571
|
+
```typescript
|
|
572
|
+
// Configure via PermissionModule options
|
|
573
|
+
PermissionModule.forRoot({
|
|
574
|
+
// Cache TTL in seconds
|
|
575
|
+
cacheTtl: 3600,
|
|
576
|
+
|
|
577
|
+
// Permission check mode
|
|
578
|
+
mode: 'RBAC' | 'DIRECT' | 'FULL',
|
|
579
|
+
|
|
580
|
+
// Custom permission resolver
|
|
581
|
+
resolver: CustomPermissionResolver,
|
|
582
|
+
});
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
---
|
|
586
|
+
|
|
587
|
+
## Middleware
|
|
588
|
+
|
|
589
|
+
### LoggerMiddleware (NEW - 2026-01-12, Enhanced - 2026-01-14)
|
|
590
|
+
|
|
591
|
+
**Location:** `@flusys/nestjs-shared/middlewares/logger.middleware.ts`
|
|
592
|
+
|
|
593
|
+
Combined middleware that handles:
|
|
594
|
+
1. **Request Correlation** - Tracks requests across services using AsyncLocalStorage
|
|
595
|
+
2. **HTTP Request/Response Logging** - Structured logging with Winston
|
|
596
|
+
3. **Detailed Request/Response Information** - Complete visibility into all requests
|
|
597
|
+
|
|
598
|
+
**Key Features:**
|
|
599
|
+
- **Request ID Generation** - Automatic UUID generation or uses `x-request-id` header
|
|
600
|
+
- **Tenant ID Tracking** - Extracts `x-tenant-id` from headers for multi-tenant apps
|
|
601
|
+
- **AsyncLocalStorage Context** - Thread-safe request context accessible anywhere
|
|
602
|
+
- **Security** - Automatically redacts sensitive headers (authorization, cookie, x-api-key)
|
|
603
|
+
- **Performance Monitoring** - Logs slow requests (>3s) with dedicated warning logs
|
|
604
|
+
- **Body Truncation** - Limits log size to 1000 characters
|
|
605
|
+
- **Debug Mode** - Conditionally logs headers and body based on LOG_LEVEL=debug
|
|
606
|
+
- **Complete Request Details** - URL, path, query params, content type, user agent, client IP
|
|
607
|
+
- **Complete Response Details** - Status code, message, content type, content length, user/company context
|
|
608
|
+
- **Multiple Response Hooks** - Captures responses via `res.send()`, `res.json()`, and `res.end()`
|
|
609
|
+
- **Error Handling** - Logs response errors with stack traces
|
|
610
|
+
- **User Context** - Automatically includes userId and companyId in response logs if available
|
|
611
|
+
|
|
612
|
+
**Usage:**
|
|
613
|
+
|
|
614
|
+
```typescript
|
|
615
|
+
import { LoggerMiddleware } from '@flusys/nestjs-shared/middlewares';
|
|
616
|
+
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
|
|
617
|
+
|
|
618
|
+
@Module({
|
|
619
|
+
// ...
|
|
620
|
+
})
|
|
621
|
+
export class AppModule implements NestModule {
|
|
622
|
+
configure(consumer: MiddlewareConsumer) {
|
|
623
|
+
// Apply to all routes
|
|
624
|
+
consumer.apply(LoggerMiddleware).forRoutes('*');
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
**Accessing Request Context:**
|
|
630
|
+
|
|
631
|
+
```typescript
|
|
632
|
+
import {
|
|
633
|
+
getRequestId,
|
|
634
|
+
getTenantId,
|
|
635
|
+
getUserId,
|
|
636
|
+
getCompanyId,
|
|
637
|
+
setUserId,
|
|
638
|
+
setCompanyId,
|
|
639
|
+
} from '@flusys/nestjs-shared/middlewares';
|
|
640
|
+
|
|
641
|
+
@Injectable()
|
|
642
|
+
export class MyService {
|
|
643
|
+
async doSomething() {
|
|
644
|
+
const requestId = getRequestId(); // Get current request ID
|
|
645
|
+
const tenantId = getTenantId(); // Get tenant ID from header
|
|
646
|
+
|
|
647
|
+
// Set user context after authentication
|
|
648
|
+
setUserId('user-123');
|
|
649
|
+
setCompanyId('company-456');
|
|
650
|
+
|
|
651
|
+
// Use in logs
|
|
652
|
+
this.logger.info('Processing request', {
|
|
653
|
+
requestId,
|
|
654
|
+
tenantId,
|
|
655
|
+
userId: getUserId(),
|
|
656
|
+
companyId: getCompanyId(),
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
**Request Context Interface:**
|
|
663
|
+
|
|
664
|
+
```typescript
|
|
665
|
+
interface IRequestContext {
|
|
666
|
+
requestId: string; // UUID or from x-request-id header
|
|
667
|
+
tenantId?: string; // From x-tenant-id header
|
|
668
|
+
userId?: string; // Set after authentication
|
|
669
|
+
companyId?: string; // Set after authentication
|
|
670
|
+
startTime: number; // Request start timestamp
|
|
671
|
+
}
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
**Configuration:**
|
|
675
|
+
|
|
676
|
+
```typescript
|
|
677
|
+
// Environment-based configuration
|
|
678
|
+
const IS_DEBUG = envConfig.getLogConfig().level === 'debug';
|
|
679
|
+
const TENANT_ID_HEADER = 'x-tenant-id';
|
|
680
|
+
const EXCLUDED_PATHS = ['/health', '/metrics', '/favicon.ico'];
|
|
681
|
+
const EXCLUDED_HEADERS = ['authorization', 'cookie', 'x-api-key'];
|
|
682
|
+
const MAX_BODY_LOG_SIZE = 1000;
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
**Console Log Format (Development):**
|
|
686
|
+
|
|
687
|
+
The development console logger displays HTTP requests in human-readable format:
|
|
688
|
+
|
|
689
|
+
```
|
|
690
|
+
2026-01-17 22:41:23 [INFO ] [HTTP] [POST /auth/login] [200] (45ms) [uuid] Incoming request
|
|
691
|
+
2026-01-17 22:41:23 [INFO ] [HTTP] [POST /auth/login] [200] (45ms) [uuid] (user:user-123) Response [200]
|
|
692
|
+
2026-01-17 22:41:25 [WARN ] [HTTP] [GET /api/users] [404] (12ms) [uuid] Response [404]
|
|
693
|
+
2026-01-17 22:41:30 [ERROR ] [HTTP] [POST /api/orders] [500] (234ms) [uuid] (user:user-123) Response [500]
|
|
694
|
+
```
|
|
695
|
+
|
|
696
|
+
**Format Structure:**
|
|
697
|
+
- `[timestamp]` - Request timestamp
|
|
698
|
+
- `[level]` - Log level (INFO, WARN, ERROR)
|
|
699
|
+
- `[context]` - Always "HTTP" for HTTP requests
|
|
700
|
+
- `[METHOD /path]` - HTTP method and endpoint path
|
|
701
|
+
- `[statusCode]` - HTTP response status (only in response logs)
|
|
702
|
+
- `(duration)` - Request duration (only in response logs)
|
|
703
|
+
- `[uuid]` - Request correlation ID
|
|
704
|
+
- `(user:userId)` - User ID if authenticated (only in response logs)
|
|
705
|
+
|
|
706
|
+
**File Log Format (Production):**
|
|
707
|
+
|
|
708
|
+
```json
|
|
709
|
+
// Incoming request (Enhanced with full details)
|
|
710
|
+
{
|
|
711
|
+
"level": "info",
|
|
712
|
+
"message": "Incoming request",
|
|
713
|
+
"context": "HTTP",
|
|
714
|
+
"requestId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
|
715
|
+
"tenantId": "tenant-123",
|
|
716
|
+
"method": "POST",
|
|
717
|
+
"url": "/api/users/insert?sort=name",
|
|
718
|
+
"path": "/api/users/insert",
|
|
719
|
+
"query": { "sort": "name" },
|
|
720
|
+
"ip": "192.168.1.100",
|
|
721
|
+
"userAgent": "Mozilla/5.0...",
|
|
722
|
+
"contentType": "application/json",
|
|
723
|
+
"contentLength": "342",
|
|
724
|
+
"headers": { ... }, // Only in debug mode
|
|
725
|
+
"body": { ... }, // Only in debug mode, truncated to 1000 chars
|
|
726
|
+
"params": { ... } // Only in debug mode (route params)
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
// Response (Enhanced with full details)
|
|
730
|
+
{
|
|
731
|
+
"level": "info",
|
|
732
|
+
"message": "Response [200]",
|
|
733
|
+
"context": "HTTP",
|
|
734
|
+
"requestId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
|
735
|
+
"tenantId": "tenant-123",
|
|
736
|
+
"method": "POST",
|
|
737
|
+
"url": "/api/users/insert?sort=name",
|
|
738
|
+
"path": "/api/users/insert",
|
|
739
|
+
"statusCode": 200,
|
|
740
|
+
"statusMessage": "OK",
|
|
741
|
+
"duration": "125ms",
|
|
742
|
+
"durationMs": 125,
|
|
743
|
+
"contentType": "application/json; charset=utf-8",
|
|
744
|
+
"contentLength": "156",
|
|
745
|
+
"userId": "user-456", // Automatically added if available
|
|
746
|
+
"companyId": "company-789" // Automatically added if available
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// Error response (includes response body automatically)
|
|
750
|
+
{
|
|
751
|
+
"level": "warn",
|
|
752
|
+
"message": "Response [400]",
|
|
753
|
+
"context": "HTTP",
|
|
754
|
+
"requestId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
|
755
|
+
"tenantId": "tenant-123",
|
|
756
|
+
"method": "POST",
|
|
757
|
+
"url": "/api/users/insert",
|
|
758
|
+
"path": "/api/users/insert",
|
|
759
|
+
"statusCode": 400,
|
|
760
|
+
"statusMessage": "Bad Request",
|
|
761
|
+
"duration": "45ms",
|
|
762
|
+
"durationMs": 45,
|
|
763
|
+
"userId": "user-456",
|
|
764
|
+
"companyId": "company-789",
|
|
765
|
+
"responseBody": {
|
|
766
|
+
"statusCode": 400,
|
|
767
|
+
"message": "Validation failed",
|
|
768
|
+
"errors": ["Email is required"]
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
// Slow request warning (Enhanced with more context)
|
|
773
|
+
{
|
|
774
|
+
"level": "warn",
|
|
775
|
+
"message": "Slow request detected",
|
|
776
|
+
"context": "HTTP",
|
|
777
|
+
"requestId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
|
778
|
+
"tenantId": "tenant-123",
|
|
779
|
+
"userId": "user-456",
|
|
780
|
+
"companyId": "company-789",
|
|
781
|
+
"method": "POST",
|
|
782
|
+
"url": "/api/reports/generate",
|
|
783
|
+
"path": "/api/reports/generate",
|
|
784
|
+
"duration": "3245ms",
|
|
785
|
+
"durationMs": 3245,
|
|
786
|
+
"threshold": "3000ms"
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
// Response error
|
|
790
|
+
{
|
|
791
|
+
"level": "error",
|
|
792
|
+
"message": "Response error",
|
|
793
|
+
"context": "HTTP",
|
|
794
|
+
"requestId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
|
795
|
+
"tenantId": "tenant-123",
|
|
796
|
+
"method": "POST",
|
|
797
|
+
"url": "/api/users/insert",
|
|
798
|
+
"path": "/api/users/insert",
|
|
799
|
+
"error": "Socket hang up",
|
|
800
|
+
"stack": "Error: Socket hang up\n at ..."
|
|
801
|
+
}
|
|
802
|
+
```
|
|
803
|
+
|
|
804
|
+
**Benefits:**
|
|
805
|
+
|
|
806
|
+
- **Request Tracing** - Correlate logs across multiple services using requestId
|
|
807
|
+
- **Multi-Tenant Support** - Automatic tenant isolation in logs
|
|
808
|
+
- **Security** - Sensitive data automatically redacted
|
|
809
|
+
- **Performance Monitoring** - Identify slow endpoints
|
|
810
|
+
- **Debugging** - Conditional verbose logging
|
|
811
|
+
|
|
812
|
+
---
|
|
813
|
+
|
|
814
|
+
## Interceptors
|
|
815
|
+
|
|
816
|
+
### ResponseMetaInterceptor
|
|
817
|
+
|
|
818
|
+
Adds request metadata to responses:
|
|
819
|
+
|
|
820
|
+
```typescript
|
|
821
|
+
import { ResponseMetaInterceptor } from '@flusys/nestjs-shared/interceptors';
|
|
822
|
+
|
|
823
|
+
@UseInterceptors(ResponseMetaInterceptor)
|
|
824
|
+
@Controller('users')
|
|
825
|
+
export class UserController {}
|
|
826
|
+
|
|
827
|
+
// Response:
|
|
828
|
+
{
|
|
829
|
+
"data": [...],
|
|
830
|
+
"meta": {
|
|
831
|
+
"timestamp": "2024-01-01T00:00:00.000Z",
|
|
832
|
+
"requestId": "abc-123",
|
|
833
|
+
"responseTime": 45
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
```
|
|
837
|
+
|
|
838
|
+
### IdempotencyInterceptor
|
|
839
|
+
|
|
840
|
+
Prevent duplicate requests:
|
|
841
|
+
|
|
842
|
+
```typescript
|
|
843
|
+
import { IdempotencyInterceptor } from '@flusys/nestjs-shared/interceptors';
|
|
844
|
+
|
|
845
|
+
@UseInterceptors(IdempotencyInterceptor)
|
|
846
|
+
@Controller('payments')
|
|
847
|
+
export class PaymentController {
|
|
848
|
+
@Post()
|
|
849
|
+
// Clients send X-Idempotency-Key header
|
|
850
|
+
// Duplicate requests return cached response
|
|
851
|
+
processPayment() {}
|
|
852
|
+
}
|
|
853
|
+
```
|
|
854
|
+
|
|
855
|
+
### SetCreatedByOnBody / SetUpdateByOnBody
|
|
856
|
+
|
|
857
|
+
Auto-set user IDs on request body:
|
|
858
|
+
|
|
859
|
+
```typescript
|
|
860
|
+
import { SetCreatedByOnBody, SetUpdateByOnBody } from '@flusys/nestjs-shared/interceptors';
|
|
861
|
+
|
|
862
|
+
@Controller('posts')
|
|
863
|
+
export class PostController {
|
|
864
|
+
@UseInterceptors(SetCreatedByOnBody)
|
|
865
|
+
@Post()
|
|
866
|
+
create(@Body() dto: CreatePostDto) {
|
|
867
|
+
// dto.createdById is automatically set to current user ID
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
@UseInterceptors(SetUpdateByOnBody)
|
|
871
|
+
@Put()
|
|
872
|
+
update(@Body() dto: UpdatePostDto) {
|
|
873
|
+
// dto.updatedById is automatically set to current user ID
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
```
|
|
877
|
+
|
|
878
|
+
### Slug Interceptor
|
|
879
|
+
|
|
880
|
+
Auto-generate slugs from name field:
|
|
881
|
+
|
|
882
|
+
```typescript
|
|
883
|
+
import { Slug } from '@flusys/nestjs-shared/interceptors';
|
|
884
|
+
|
|
885
|
+
@Controller('products')
|
|
886
|
+
export class ProductController {
|
|
887
|
+
@UseInterceptors(Slug)
|
|
888
|
+
@Post()
|
|
889
|
+
create(@Body() dto: CreateProductDto) {
|
|
890
|
+
// dto.slug is auto-generated from dto.name
|
|
891
|
+
// "My Product" -> "my-product"
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
```
|
|
895
|
+
|
|
896
|
+
---
|
|
897
|
+
|
|
898
|
+
## Caching System
|
|
899
|
+
|
|
900
|
+
### HybridCache
|
|
901
|
+
|
|
902
|
+
Two-tier caching with in-memory (fast) and Redis (distributed):
|
|
903
|
+
|
|
904
|
+
```typescript
|
|
905
|
+
import { HybridCache } from '@flusys/nestjs-shared/classes';
|
|
906
|
+
|
|
907
|
+
@Injectable()
|
|
908
|
+
export class MyService {
|
|
909
|
+
constructor(
|
|
910
|
+
@Inject('CACHE_INSTANCE')
|
|
911
|
+
private cache: HybridCache,
|
|
912
|
+
) {}
|
|
913
|
+
|
|
914
|
+
async getData(key: string) {
|
|
915
|
+
// Check cache first
|
|
916
|
+
const cached = await this.cache.get(key);
|
|
917
|
+
if (cached) return cached;
|
|
918
|
+
|
|
919
|
+
// Fetch from database
|
|
920
|
+
const data = await this.fetchFromDb();
|
|
921
|
+
|
|
922
|
+
// Store in cache (TTL in seconds)
|
|
923
|
+
await this.cache.set(key, data, 3600);
|
|
924
|
+
|
|
925
|
+
return data;
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
async invalidate(key: string) {
|
|
929
|
+
await this.cache.del(key);
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
async invalidatePattern(pattern: string) {
|
|
933
|
+
// Delete all keys matching pattern
|
|
934
|
+
await this.cache.delByPattern('users:*');
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
```
|
|
938
|
+
|
|
939
|
+
### CacheModule Setup
|
|
940
|
+
|
|
941
|
+
```typescript
|
|
942
|
+
import { CacheModule } from '@flusys/nestjs-shared/modules';
|
|
943
|
+
|
|
944
|
+
@Module({
|
|
945
|
+
imports: [
|
|
946
|
+
CacheModule.forRoot({
|
|
947
|
+
// In-memory cache config
|
|
948
|
+
memory: {
|
|
949
|
+
max: 500, // Max items
|
|
950
|
+
ttl: 3600, // Default TTL (seconds)
|
|
951
|
+
},
|
|
952
|
+
|
|
953
|
+
// Redis config (optional)
|
|
954
|
+
redis: {
|
|
955
|
+
url: 'redis://localhost:6379',
|
|
956
|
+
ttl: 3600,
|
|
957
|
+
},
|
|
958
|
+
}),
|
|
959
|
+
],
|
|
960
|
+
})
|
|
961
|
+
export class AppModule {}
|
|
962
|
+
```
|
|
963
|
+
|
|
964
|
+
### Cache Methods
|
|
965
|
+
|
|
966
|
+
| Method | Description |
|
|
967
|
+
|--------|-------------|
|
|
968
|
+
| `get(key)` | Get cached value |
|
|
969
|
+
| `set(key, value, ttl?)` | Set value with optional TTL |
|
|
970
|
+
| `del(key)` | Delete single key |
|
|
971
|
+
| `delByPattern(pattern)` | Delete keys matching pattern |
|
|
972
|
+
| `reset()` | Clear all cache |
|
|
973
|
+
| `has(key)` | Check if key exists |
|
|
974
|
+
|
|
975
|
+
---
|
|
976
|
+
|
|
977
|
+
## Multi-Tenant DataSource
|
|
978
|
+
|
|
979
|
+
Dynamic database connection management for multi-tenant applications.
|
|
980
|
+
|
|
981
|
+
### Setup
|
|
982
|
+
|
|
983
|
+
```typescript
|
|
984
|
+
import { DataSourceModule } from '@flusys/nestjs-shared/modules';
|
|
985
|
+
|
|
986
|
+
@Module({
|
|
987
|
+
imports: [
|
|
988
|
+
DataSourceModule.forRoot({
|
|
989
|
+
// Default database config (used as template)
|
|
990
|
+
defaultDatabaseConfig: {
|
|
991
|
+
type: 'mysql',
|
|
992
|
+
host: 'localhost',
|
|
993
|
+
port: 3306,
|
|
994
|
+
username: 'root',
|
|
995
|
+
password: 'password',
|
|
996
|
+
},
|
|
997
|
+
|
|
998
|
+
// Tenant configurations
|
|
999
|
+
tenants: [
|
|
1000
|
+
{ id: 'tenant1', database: 'tenant1_db', isActive: true },
|
|
1001
|
+
{ id: 'tenant2', database: 'tenant2_db', isActive: true },
|
|
1002
|
+
],
|
|
1003
|
+
}),
|
|
1004
|
+
],
|
|
1005
|
+
})
|
|
1006
|
+
export class AppModule {}
|
|
1007
|
+
```
|
|
1008
|
+
|
|
1009
|
+
### Usage with Tenant Header
|
|
1010
|
+
|
|
1011
|
+
```typescript
|
|
1012
|
+
// Client sends X-Tenant-ID header
|
|
1013
|
+
// DataSource automatically switches to tenant's database
|
|
1014
|
+
|
|
1015
|
+
@Injectable()
|
|
1016
|
+
export class TenantService {
|
|
1017
|
+
constructor(
|
|
1018
|
+
@Inject('DATASOURCE_PROVIDER')
|
|
1019
|
+
private dataSourceProvider: MultiTenantDataSourceService,
|
|
1020
|
+
) {}
|
|
1021
|
+
|
|
1022
|
+
async getConnection(tenantId: string) {
|
|
1023
|
+
return this.dataSourceProvider.getConnection(tenantId);
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
```
|
|
1027
|
+
|
|
1028
|
+
### Tenant Resolution
|
|
1029
|
+
|
|
1030
|
+
```typescript
|
|
1031
|
+
// Default: Resolved from X-Tenant-ID header
|
|
1032
|
+
@Controller('users')
|
|
1033
|
+
export class UserController {
|
|
1034
|
+
// Automatically uses tenant-specific database
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
// Manual tenant specification
|
|
1038
|
+
@Injectable()
|
|
1039
|
+
export class CrossTenantService {
|
|
1040
|
+
async copyData(fromTenant: string, toTenant: string) {
|
|
1041
|
+
const fromConn = await this.dataSourceProvider.getConnection(fromTenant);
|
|
1042
|
+
const toConn = await this.dataSourceProvider.getConnection(toTenant);
|
|
1043
|
+
// ...
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
```
|
|
1047
|
+
|
|
1048
|
+
---
|
|
1049
|
+
|
|
1050
|
+
## DTOs
|
|
1051
|
+
|
|
1052
|
+
### FilterAndPaginationDto
|
|
1053
|
+
|
|
1054
|
+
Standard filtering and pagination:
|
|
1055
|
+
|
|
1056
|
+
```typescript
|
|
1057
|
+
import { FilterAndPaginationDto } from '@flusys/nestjs-shared/dtos';
|
|
1058
|
+
|
|
1059
|
+
// Request body
|
|
1060
|
+
{
|
|
1061
|
+
"filter": {
|
|
1062
|
+
"status": "active",
|
|
1063
|
+
"category": "electronics"
|
|
1064
|
+
},
|
|
1065
|
+
"pagination": {
|
|
1066
|
+
"page": 1,
|
|
1067
|
+
"limit": 10
|
|
1068
|
+
},
|
|
1069
|
+
"sort": {
|
|
1070
|
+
"field": "createdAt",
|
|
1071
|
+
"order": "DESC"
|
|
1072
|
+
},
|
|
1073
|
+
"search": {
|
|
1074
|
+
"fields": ["name", "description"],
|
|
1075
|
+
"value": "laptop"
|
|
1076
|
+
},
|
|
1077
|
+
"select": ["id", "name", "price"]
|
|
1078
|
+
}
|
|
1079
|
+
```
|
|
1080
|
+
|
|
1081
|
+
### DeleteDto
|
|
1082
|
+
|
|
1083
|
+
Soft or permanent delete:
|
|
1084
|
+
|
|
1085
|
+
```typescript
|
|
1086
|
+
import { DeleteDto } from '@flusys/nestjs-shared/dtos';
|
|
1087
|
+
|
|
1088
|
+
// Soft delete (sets deletedAt)
|
|
1089
|
+
{
|
|
1090
|
+
"id": "user-123",
|
|
1091
|
+
"type": "soft"
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
// Permanent delete
|
|
1095
|
+
{
|
|
1096
|
+
"id": "user-123",
|
|
1097
|
+
"type": "permanent"
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
// Multiple IDs
|
|
1101
|
+
{
|
|
1102
|
+
"id": ["user-123", "user-456"],
|
|
1103
|
+
"type": "soft"
|
|
1104
|
+
}
|
|
1105
|
+
```
|
|
1106
|
+
|
|
1107
|
+
### ResponsePayloadDto
|
|
1108
|
+
|
|
1109
|
+
Standardized response format:
|
|
1110
|
+
|
|
1111
|
+
```typescript
|
|
1112
|
+
import { ResponsePayloadDto } from '@flusys/nestjs-shared/dtos';
|
|
1113
|
+
|
|
1114
|
+
// Success response
|
|
1115
|
+
{
|
|
1116
|
+
"success": true,
|
|
1117
|
+
"data": { ... },
|
|
1118
|
+
"message": "Operation successful"
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
// Paginated response
|
|
1122
|
+
{
|
|
1123
|
+
"success": true,
|
|
1124
|
+
"data": [...],
|
|
1125
|
+
"pagination": {
|
|
1126
|
+
"total": 100,
|
|
1127
|
+
"page": 1,
|
|
1128
|
+
"limit": 10,
|
|
1129
|
+
"totalPages": 10
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
// Error response
|
|
1134
|
+
{
|
|
1135
|
+
"success": false,
|
|
1136
|
+
"error": {
|
|
1137
|
+
"code": "VALIDATION_ERROR",
|
|
1138
|
+
"message": "Invalid input"
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
```
|
|
1142
|
+
|
|
1143
|
+
---
|
|
1144
|
+
|
|
1145
|
+
## Base Entities
|
|
1146
|
+
|
|
1147
|
+
### Identity Entity
|
|
1148
|
+
|
|
1149
|
+
Base entity with UUID and timestamps:
|
|
1150
|
+
|
|
1151
|
+
```typescript
|
|
1152
|
+
import { Identity } from '@flusys/nestjs-shared/entities';
|
|
1153
|
+
|
|
1154
|
+
@Entity()
|
|
1155
|
+
export class Product extends Identity {
|
|
1156
|
+
// Inherited: id, createdAt, updatedAt, deletedAt
|
|
1157
|
+
|
|
1158
|
+
@Column()
|
|
1159
|
+
name: string;
|
|
1160
|
+
|
|
1161
|
+
@Column()
|
|
1162
|
+
price: number;
|
|
1163
|
+
}
|
|
1164
|
+
```
|
|
1165
|
+
|
|
1166
|
+
### UserRoot Entity
|
|
1167
|
+
|
|
1168
|
+
Base user entity:
|
|
1169
|
+
|
|
1170
|
+
```typescript
|
|
1171
|
+
import { UserRoot } from '@flusys/nestjs-shared/entities';
|
|
1172
|
+
|
|
1173
|
+
@Entity()
|
|
1174
|
+
export class User extends UserRoot {
|
|
1175
|
+
// Inherited: id, name, email, password, phone, isActive, createdAt, updatedAt, deletedAt
|
|
1176
|
+
|
|
1177
|
+
@Column({ nullable: true })
|
|
1178
|
+
customField: string;
|
|
1179
|
+
}
|
|
1180
|
+
```
|
|
1181
|
+
|
|
1182
|
+
---
|
|
1183
|
+
|
|
1184
|
+
## Error Handling
|
|
1185
|
+
|
|
1186
|
+
### ErrorHandler
|
|
1187
|
+
|
|
1188
|
+
Centralized error handling utility:
|
|
1189
|
+
|
|
1190
|
+
```typescript
|
|
1191
|
+
import { ErrorHandler } from '@flusys/nestjs-shared/utils';
|
|
1192
|
+
|
|
1193
|
+
@Injectable()
|
|
1194
|
+
export class UserService {
|
|
1195
|
+
async createUser(dto: CreateUserDto) {
|
|
1196
|
+
try {
|
|
1197
|
+
return await this.repository.save(dto);
|
|
1198
|
+
} catch (error) {
|
|
1199
|
+
// Handles TypeORM errors, validation errors, etc.
|
|
1200
|
+
throw ErrorHandler.handle(error, 'Failed to create user');
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
```
|
|
1205
|
+
|
|
1206
|
+
### Error Types
|
|
1207
|
+
|
|
1208
|
+
```typescript
|
|
1209
|
+
// Automatically converted to appropriate HTTP exception:
|
|
1210
|
+
|
|
1211
|
+
// TypeORM unique constraint → ConflictException (409)
|
|
1212
|
+
// TypeORM foreign key → BadRequestException (400)
|
|
1213
|
+
// Validation error → BadRequestException (400)
|
|
1214
|
+
// Not found → NotFoundException (404)
|
|
1215
|
+
// Unknown → InternalServerErrorException (500)
|
|
1216
|
+
```
|
|
1217
|
+
|
|
1218
|
+
---
|
|
1219
|
+
|
|
1220
|
+
## API Reference
|
|
1221
|
+
|
|
1222
|
+
### Main Exports
|
|
1223
|
+
|
|
1224
|
+
```typescript
|
|
1225
|
+
// Classes
|
|
1226
|
+
import {
|
|
1227
|
+
ApiService,
|
|
1228
|
+
createApiController,
|
|
1229
|
+
HybridCache,
|
|
1230
|
+
} from '@flusys/nestjs-shared/classes';
|
|
1231
|
+
|
|
1232
|
+
// Decorators
|
|
1233
|
+
import {
|
|
1234
|
+
CurrentUser,
|
|
1235
|
+
Public,
|
|
1236
|
+
RequirePermission,
|
|
1237
|
+
RequireAnyPermission,
|
|
1238
|
+
RequireAllPermissions,
|
|
1239
|
+
} from '@flusys/nestjs-shared/decorators';
|
|
1240
|
+
|
|
1241
|
+
// Guards
|
|
1242
|
+
import {
|
|
1243
|
+
PermissionGuard,
|
|
1244
|
+
} from '@flusys/nestjs-shared/guards';
|
|
1245
|
+
|
|
1246
|
+
// Interceptors
|
|
1247
|
+
import {
|
|
1248
|
+
ResponseMetaInterceptor,
|
|
1249
|
+
IdempotencyInterceptor,
|
|
1250
|
+
SetCreatedByOnBody,
|
|
1251
|
+
SetUpdateByOnBody,
|
|
1252
|
+
Slug,
|
|
1253
|
+
} from '@flusys/nestjs-shared/interceptors';
|
|
1254
|
+
|
|
1255
|
+
// Modules
|
|
1256
|
+
import {
|
|
1257
|
+
CacheModule,
|
|
1258
|
+
DataSourceModule,
|
|
1259
|
+
UtilsModule,
|
|
1260
|
+
} from '@flusys/nestjs-shared/modules';
|
|
1261
|
+
|
|
1262
|
+
// DTOs
|
|
1263
|
+
import {
|
|
1264
|
+
FilterAndPaginationDto,
|
|
1265
|
+
DeleteDto,
|
|
1266
|
+
ResponsePayloadDto,
|
|
1267
|
+
} from '@flusys/nestjs-shared/dtos';
|
|
1268
|
+
|
|
1269
|
+
// Entities
|
|
1270
|
+
import {
|
|
1271
|
+
Identity,
|
|
1272
|
+
UserRoot,
|
|
1273
|
+
} from '@flusys/nestjs-shared/entities';
|
|
1274
|
+
|
|
1275
|
+
// Interfaces
|
|
1276
|
+
import {
|
|
1277
|
+
ILoggedUserInfo,
|
|
1278
|
+
IPermissionConfig,
|
|
1279
|
+
} from '@flusys/nestjs-shared/interfaces';
|
|
1280
|
+
|
|
1281
|
+
// Utilities
|
|
1282
|
+
import {
|
|
1283
|
+
ErrorHandler,
|
|
1284
|
+
} from '@flusys/nestjs-shared/utils';
|
|
1285
|
+
```
|
|
1286
|
+
|
|
1287
|
+
---
|
|
1288
|
+
|
|
1289
|
+
## Best Practices
|
|
1290
|
+
|
|
1291
|
+
### 1. Use Generic Service Pattern
|
|
1292
|
+
|
|
1293
|
+
```typescript
|
|
1294
|
+
// ✅ Extend ApiService for consistent CRUD
|
|
1295
|
+
@Injectable()
|
|
1296
|
+
export class ProductService extends ApiService<...> {
|
|
1297
|
+
// Override hooks for customization
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
// ❌ Don't create custom CRUD from scratch
|
|
1301
|
+
@Injectable()
|
|
1302
|
+
export class ProductService {
|
|
1303
|
+
async getAll() { /* custom implementation */ }
|
|
1304
|
+
async getById() { /* custom implementation */ }
|
|
1305
|
+
// Inconsistent, error-prone
|
|
1306
|
+
}
|
|
1307
|
+
```
|
|
1308
|
+
|
|
1309
|
+
### 2. Use Decorators Consistently
|
|
1310
|
+
|
|
1311
|
+
```typescript
|
|
1312
|
+
// ✅ Use built-in decorators
|
|
1313
|
+
@CurrentUser() user: ILoggedUserInfo
|
|
1314
|
+
@Public()
|
|
1315
|
+
@RequirePermission('users.create')
|
|
1316
|
+
|
|
1317
|
+
// ❌ Don't access request directly
|
|
1318
|
+
@Req() req: Request
|
|
1319
|
+
const user = req.user; // Not type-safe
|
|
1320
|
+
```
|
|
1321
|
+
|
|
1322
|
+
### 3. Configure Security at Controller Level
|
|
1323
|
+
|
|
1324
|
+
```typescript
|
|
1325
|
+
// ✅ Configure security in createApiController
|
|
1326
|
+
export class UserController extends createApiController(..., {
|
|
1327
|
+
security: { ... },
|
|
1328
|
+
}) {}
|
|
1329
|
+
|
|
1330
|
+
// ❌ Don't add @UseGuards to each endpoint
|
|
1331
|
+
@UseGuards(JwtGuard)
|
|
1332
|
+
@Post('create')
|
|
1333
|
+
create() {}
|
|
1334
|
+
```
|
|
1335
|
+
|
|
1336
|
+
### 4. Use Cache for Repeated Queries
|
|
1337
|
+
|
|
1338
|
+
```typescript
|
|
1339
|
+
// ✅ Cache expensive operations
|
|
1340
|
+
async getPermissions(userId: string) {
|
|
1341
|
+
const cacheKey = `permissions:${userId}`;
|
|
1342
|
+
const cached = await this.cache.get(cacheKey);
|
|
1343
|
+
if (cached) return cached;
|
|
1344
|
+
|
|
1345
|
+
const permissions = await this.fetchPermissions(userId);
|
|
1346
|
+
await this.cache.set(cacheKey, permissions, 3600);
|
|
1347
|
+
return permissions;
|
|
1348
|
+
}
|
|
1349
|
+
```
|
|
1350
|
+
|
|
1351
|
+
---
|
|
1352
|
+
|
|
1353
|
+
## Dependencies
|
|
1354
|
+
|
|
1355
|
+
- `@nestjs/common`
|
|
1356
|
+
- `@nestjs/core`
|
|
1357
|
+
- `@nestjs/typeorm`
|
|
1358
|
+
- `@nestjs/swagger`
|
|
1359
|
+
- `typeorm`
|
|
1360
|
+
- `class-validator`
|
|
1361
|
+
- `class-transformer`
|
|
1362
|
+
- `@flusys/nestjs-core`
|
|
1363
|
+
|
|
1364
|
+
---
|
|
1365
|
+
|
|
1366
|
+
## Related Documentation
|
|
1367
|
+
|
|
1368
|
+
- [API Controller Security Guide](./API-CONTROLLER-SECURITY.md)
|
|
1369
|
+
- [Core Package Guide](./CORE-GUIDE.md)
|
|
1370
|
+
- [Auth Package Guide](./AUTH-GUIDE.md)
|
|
1371
|
+
|
|
1372
|
+
---
|
|
1373
|
+
|
|
1374
|
+
## Summary
|
|
1375
|
+
|
|
1376
|
+
The `@flusys/nestjs-shared` package provides:
|
|
1377
|
+
|
|
1378
|
+
✅ **Generic CRUD** - Consistent API patterns
|
|
1379
|
+
✅ **Permission System** - Flexible access control
|
|
1380
|
+
✅ **Caching** - High-performance data access
|
|
1381
|
+
✅ **Multi-Tenancy** - Database isolation
|
|
1382
|
+
✅ **Request Correlation** - AsyncLocalStorage-based tracking (NEW)
|
|
1383
|
+
✅ **Middleware** - Logging and performance monitoring (NEW)
|
|
1384
|
+
✅ **Interceptors** - Request/response processing
|
|
1385
|
+
✅ **Error Handling** - Centralized error management
|
|
1386
|
+
|
|
1387
|
+
This is the shared infrastructure layer used by all other Flusys packages.
|
|
1388
|
+
|
|
1389
|
+
---
|
|
1390
|
+
|
|
1391
|
+
**Last Updated:** 2026-02-07
|
|
1392
|
+
**Recent Improvements:**
|
|
1393
|
+
- 2026-02-07: Documentation cleanup - removed outdated JWT configuration section, updated package architecture to match actual structure
|
|
1394
|
+
- 2026-01-14: Enhanced `LoggerMiddleware` with complete request/response details (URL, path, query, headers, user context)
|
|
1395
|
+
- 2026-01-14: Added multiple response hooks (send, json, end) for reliable response capture
|
|
1396
|
+
- 2026-01-14: Improved error logging with stack traces
|
|
1397
|
+
- 2026-01-12: Added `LoggerMiddleware` for request correlation and structured logging
|
|
1398
|
+
- 2026-01-12: Added AsyncLocalStorage-based request context (requestId, tenantId, userId, companyId)
|
|
1399
|
+
- 2026-01-13: Removed JWT config/constants (moved to auth-specific packages)
|