@flusys/nestjs-shared 1.1.0-beta → 1.2.0-rc.1

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