@flusys/nestjs-shared 1.1.0 → 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.
- package/README.md +139 -1103
- package/cjs/classes/api-controller.class.js +320 -29
- package/cjs/classes/api-service.class.js +219 -64
- package/cjs/classes/handle-custome-db.js +37 -0
- package/cjs/classes/index.js +1 -2
- package/cjs/classes/winston.logger.class.js +103 -70
- package/cjs/constants/index.js +1 -4
- package/cjs/constants/message-keys.js +68 -0
- package/cjs/constants/permissions.js +114 -11
- package/cjs/decorators/api-response.decorator.js +1 -2
- package/cjs/decorators/index.js +1 -1
- package/cjs/decorators/log-action.decorator.js +149 -0
- package/cjs/decorators/require-permission.decorator.js +5 -5
- package/cjs/dtos/get-by-ids.dto.js +65 -0
- package/cjs/dtos/identity-response.dto.js +4 -8
- package/cjs/dtos/index.js +1 -0
- package/cjs/dtos/response-payload.dto.js +72 -0
- package/cjs/entities/identity.js +8 -2
- package/cjs/entities/index.js +1 -0
- package/cjs/entities/raw-type.js +4 -0
- package/cjs/entities/user-root.js +2 -1
- package/cjs/enums/index.js +20 -0
- package/cjs/enums/notification-type.enum.js +17 -0
- package/cjs/enums/participant-status.enum.js +17 -0
- package/cjs/enums/recurrence-type.enum.js +18 -0
- package/cjs/exceptions/base-app.exception.js +151 -0
- package/cjs/exceptions/index.js +1 -0
- package/cjs/exceptions/permission.exception.js +13 -9
- package/cjs/filters/global-exception.filter.js +279 -0
- package/cjs/filters/index.js +18 -0
- package/cjs/guards/jwt-auth.guard.js +4 -1
- package/cjs/guards/permission.guard.js +71 -77
- package/cjs/index.js +2 -0
- package/cjs/interceptors/idempotency.interceptor.js +1 -1
- package/cjs/interceptors/index.js +0 -1
- package/cjs/interfaces/event-manager-adapter.interface.js +11 -0
- package/cjs/interfaces/index.js +2 -1
- package/cjs/interfaces/notification-adapter.interface.js +11 -0
- package/cjs/interfaces/permission.interface.js +1 -1
- package/cjs/middlewares/logger.middleware.js +83 -26
- package/cjs/modules/datasource/index.js +0 -1
- package/cjs/modules/datasource/multi-tenant-datasource.service.js +46 -85
- package/cjs/modules/utils/utils.service.js +4 -20
- package/cjs/utils/date-time.util.js +94 -0
- package/cjs/utils/index.js +1 -1
- package/cjs/utils/query-helpers.util.js +8 -1
- package/cjs/utils/request.util.js +25 -0
- package/classes/api-controller.class.d.ts +14 -6
- package/classes/api-service.class.d.ts +25 -22
- package/classes/handle-custome-db.d.ts +3 -0
- package/classes/index.d.ts +1 -2
- package/classes/winston.logger.class.d.ts +1 -0
- package/constants/index.d.ts +1 -1
- package/constants/message-keys.d.ts +45 -0
- package/constants/permissions.d.ts +127 -0
- package/decorators/api-response.decorator.d.ts +1 -1
- package/decorators/current-user.decorator.d.ts +1 -1
- package/decorators/index.d.ts +1 -1
- package/decorators/log-action.decorator.d.ts +8 -0
- package/decorators/require-permission.decorator.d.ts +2 -2
- package/dtos/get-by-ids.dto.d.ts +4 -0
- package/dtos/index.d.ts +1 -0
- package/dtos/response-payload.dto.d.ts +8 -0
- package/entities/index.d.ts +1 -0
- package/entities/raw-type.d.ts +1 -0
- package/enums/index.d.ts +3 -0
- package/enums/notification-type.enum.d.ts +6 -0
- package/enums/participant-status.enum.d.ts +6 -0
- package/enums/recurrence-type.enum.d.ts +7 -0
- package/exceptions/base-app.exception.d.ts +41 -0
- package/exceptions/index.d.ts +1 -0
- package/exceptions/permission.exception.d.ts +2 -2
- package/fesm/classes/api-controller.class.js +321 -30
- package/fesm/classes/api-service.class.js +222 -67
- package/fesm/classes/handle-custome-db.js +19 -0
- package/fesm/classes/index.js +1 -2
- package/fesm/classes/winston.logger.class.js +100 -68
- package/fesm/constants/index.js +2 -1
- package/fesm/constants/message-keys.js +49 -0
- package/fesm/constants/permissions.js +86 -14
- package/fesm/decorators/api-response.decorator.js +2 -20
- package/fesm/decorators/index.js +1 -1
- package/fesm/decorators/log-action.decorator.js +139 -0
- package/fesm/decorators/require-permission.decorator.js +17 -65
- package/fesm/dtos/get-by-ids.dto.js +55 -0
- package/fesm/dtos/identity-response.dto.js +4 -8
- package/fesm/dtos/index.js +1 -0
- package/fesm/dtos/response-payload.dto.js +72 -0
- package/fesm/entities/identity.js +8 -2
- package/fesm/entities/index.js +1 -0
- package/fesm/entities/raw-type.js +1 -0
- package/fesm/entities/user-root.js +2 -1
- package/fesm/enums/index.js +3 -0
- package/fesm/enums/notification-type.enum.js +7 -0
- package/fesm/enums/participant-status.enum.js +7 -0
- package/fesm/enums/recurrence-type.enum.js +8 -0
- package/fesm/exceptions/base-app.exception.js +115 -0
- package/fesm/exceptions/index.js +1 -0
- package/fesm/exceptions/permission.exception.js +16 -18
- package/fesm/filters/global-exception.filter.js +269 -0
- package/fesm/filters/index.js +1 -0
- package/fesm/guards/jwt-auth.guard.js +5 -2
- package/fesm/guards/permission.guard.js +73 -79
- package/fesm/index.js +2 -0
- package/fesm/interceptors/idempotency.interceptor.js +2 -2
- package/fesm/interceptors/index.js +0 -1
- package/fesm/interfaces/datasource.interface.js +1 -20
- package/fesm/interfaces/event-manager-adapter.interface.js +1 -0
- package/fesm/interfaces/index.js +2 -1
- package/fesm/interfaces/notification-adapter.interface.js +1 -0
- package/fesm/interfaces/permission.interface.js +1 -3
- package/fesm/middlewares/logger.middleware.js +83 -26
- package/fesm/modules/datasource/index.js +0 -1
- package/fesm/modules/datasource/multi-tenant-datasource.service.js +47 -86
- package/fesm/modules/utils/utils.service.js +5 -21
- package/fesm/utils/date-time.util.js +70 -0
- package/fesm/utils/index.js +1 -1
- package/fesm/utils/query-helpers.util.js +8 -1
- package/fesm/utils/request.util.js +22 -0
- package/filters/global-exception.filter.d.ts +9 -0
- package/filters/index.d.ts +1 -0
- package/guards/permission.guard.d.ts +3 -7
- package/index.d.ts +2 -0
- package/interceptors/idempotency.interceptor.d.ts +1 -1
- package/interceptors/index.d.ts +0 -1
- package/interfaces/api.interface.d.ts +2 -0
- package/interfaces/event-manager-adapter.interface.d.ts +43 -0
- package/interfaces/index.d.ts +2 -1
- package/interfaces/notification-adapter.interface.d.ts +21 -0
- package/interfaces/permission.interface.d.ts +13 -8
- package/modules/datasource/index.d.ts +0 -1
- package/modules/datasource/multi-tenant-datasource.service.d.ts +5 -15
- package/modules/utils/utils.service.d.ts +0 -1
- package/package.json +21 -14
- package/utils/date-time.util.d.ts +8 -0
- package/utils/index.d.ts +1 -1
- package/utils/request.util.d.ts +2 -0
- package/cjs/classes/request-scoped-api.service.js +0 -61
- package/cjs/classes/winston-logger-adapter.class.js +0 -104
- package/cjs/decorators/sanitize-html.decorator.js +0 -36
- package/cjs/interceptors/query-performance.interceptor.js +0 -66
- package/cjs/interfaces/logger.interface.js +0 -7
- package/cjs/modules/datasource/datasource.module.js +0 -117
- package/cjs/utils/error-handler.util.js +0 -90
- package/classes/request-scoped-api.service.d.ts +0 -14
- package/classes/winston-logger-adapter.class.d.ts +0 -22
- package/decorators/sanitize-html.decorator.d.ts +0 -2
- package/fesm/classes/request-scoped-api.service.js +0 -51
- package/fesm/classes/winston-logger-adapter.class.js +0 -107
- package/fesm/decorators/sanitize-html.decorator.js +0 -45
- package/fesm/interceptors/query-performance.interceptor.js +0 -56
- package/fesm/interfaces/logger.interface.js +0 -4
- package/fesm/modules/datasource/datasource.module.js +0 -107
- package/fesm/utils/error-handler.util.js +0 -82
- package/interceptors/query-performance.interceptor.d.ts +0 -8
- package/interfaces/logger.interface.d.ts +0 -7
- package/modules/datasource/datasource.module.d.ts +0 -19
- package/utils/error-handler.util.d.ts +0 -19
package/README.md
CHANGED
|
@@ -1,667 +1,210 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @flusys/nestjs-shared
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
> **Version:** 1.1.0
|
|
5
|
-
> **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.
|
|
6
4
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
## Table of Contents
|
|
10
|
-
|
|
11
|
-
- [Overview](#overview)
|
|
12
|
-
- [Package Architecture](#package-architecture)
|
|
13
|
-
- [ApiService - Generic CRUD Service](#apiservice---generic-crud-service)
|
|
14
|
-
- [RequestScopedApiService](#requestscopedapiservice)
|
|
15
|
-
- [ApiController - Generic CRUD Controller](#apicontroller---generic-crud-controller)
|
|
16
|
-
- [Decorators](#decorators)
|
|
17
|
-
- [Guards](#guards)
|
|
18
|
-
- [Middleware](#middleware)
|
|
19
|
-
- [Interceptors](#interceptors)
|
|
20
|
-
- [Caching System](#caching-system)
|
|
21
|
-
- [Multi-Tenant DataSource](#multi-tenant-datasource)
|
|
22
|
-
- [DTOs](#dtos)
|
|
23
|
-
- [Base Entities](#base-entities)
|
|
24
|
-
- [Utilities](#utilities)
|
|
25
|
-
- [Error Handling](#error-handling)
|
|
26
|
-
- [Constants](#constants)
|
|
27
|
-
- [API Reference](#api-reference)
|
|
5
|
+
[](https://www.npmjs.com/package/@flusys/nestjs-shared)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
28
7
|
|
|
29
8
|
---
|
|
30
9
|
|
|
31
|
-
##
|
|
32
|
-
|
|
33
|
-
`@flusys/nestjs-shared` provides shared utilities for building scalable NestJS applications:
|
|
34
|
-
|
|
35
|
-
- **Generic CRUD** - Standardized API controller and service patterns
|
|
36
|
-
- **Permission System** - Role and permission-based access control with complex logic
|
|
37
|
-
- **Caching** - In-memory + Redis hybrid caching (HybridCache)
|
|
38
|
-
- **Request Correlation** - AsyncLocalStorage-based request tracking
|
|
39
|
-
- **Middleware** - Logging, correlation, and performance monitoring
|
|
40
|
-
- **Interceptors** - Response metadata, idempotency, auto field setting
|
|
41
|
-
- **Multi-Tenancy** - Dynamic database connection management
|
|
42
|
-
- **Error Handling** - Centralized error handling with sensitive data redaction
|
|
10
|
+
## Installation
|
|
43
11
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
@flusys/nestjs-core <- Pure TypeScript (foundation)
|
|
48
|
-
|
|
|
49
|
-
@flusys/nestjs-shared <- Shared NestJS utilities (THIS PACKAGE)
|
|
50
|
-
|
|
|
51
|
-
@flusys/nestjs-auth <- Uses common classes
|
|
52
|
-
|
|
|
53
|
-
@flusys/nestjs-iam <- Uses common patterns
|
|
54
|
-
|
|
|
55
|
-
@flusys/nestjs-storage <- Uses common patterns
|
|
12
|
+
```bash
|
|
13
|
+
npm install @flusys/nestjs-shared @flusys/nestjs-core
|
|
14
|
+
npm install typeorm @nestjs/passport @nestjs/jwt passport-jwt winston
|
|
56
15
|
```
|
|
57
16
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
## Package Architecture
|
|
61
|
-
|
|
62
|
-
```
|
|
63
|
-
nestjs-shared/
|
|
64
|
-
├── src/
|
|
65
|
-
│ ├── classes/ # Base classes
|
|
66
|
-
│ │ ├── api-service.class.ts # Generic CRUD service
|
|
67
|
-
│ │ ├── api-controller.class.ts # Generic CRUD controller factory (createApiController)
|
|
68
|
-
│ │ ├── request-scoped-api.service.ts # REQUEST-scoped service base
|
|
69
|
-
│ │ ├── hybrid-cache.class.ts # Two-tier caching
|
|
70
|
-
│ │ ├── winston.logger.class.ts # Winston logger config
|
|
71
|
-
│ │ └── winston-logger-adapter.class.ts
|
|
72
|
-
│ │
|
|
73
|
-
│ ├── constants/ # Injection tokens & constants
|
|
74
|
-
│ │ ├── permissions.ts # Permission constants (PERMISSIONS)
|
|
75
|
-
│ │ └── index.ts
|
|
76
|
-
│ │
|
|
77
|
-
│ ├── decorators/ # Custom decorators
|
|
78
|
-
│ │ ├── api-response.decorator.ts # @ApiResponseDto
|
|
79
|
-
│ │ ├── current-user.decorator.ts # @CurrentUser
|
|
80
|
-
│ │ ├── public.decorator.ts # @Public
|
|
81
|
-
│ │ ├── require-permission.decorator.ts # @RequirePermission, @RequireAnyPermission, @RequirePermissionCondition
|
|
82
|
-
│ │ ├── sanitize-html.decorator.ts # @SanitizeHtml, @SanitizeAndTrim
|
|
83
|
-
│ │ └── index.ts
|
|
84
|
-
│ │
|
|
85
|
-
│ ├── dtos/ # Shared DTOs
|
|
86
|
-
│ │ ├── delete.dto.ts
|
|
87
|
-
│ │ ├── filter-and-pagination.dto.ts
|
|
88
|
-
│ │ ├── identity-response.dto.ts
|
|
89
|
-
│ │ ├── pagination.dto.ts
|
|
90
|
-
│ │ ├── response-payload.dto.ts
|
|
91
|
-
│ │ └── index.ts
|
|
92
|
-
│ │
|
|
93
|
-
│ ├── entities/ # Base entities
|
|
94
|
-
│ │ ├── identity.ts # Base entity with UUID
|
|
95
|
-
│ │ ├── user-root.ts # Base user entity
|
|
96
|
-
│ │ └── index.ts
|
|
97
|
-
│ │
|
|
98
|
-
│ ├── exceptions/ # Custom exceptions
|
|
99
|
-
│ │ └── permission.exception.ts # Permission-related exceptions
|
|
100
|
-
│ │
|
|
101
|
-
│ ├── guards/ # Authentication & authorization
|
|
102
|
-
│ │ ├── jwt-auth.guard.ts # JWT token validation
|
|
103
|
-
│ │ └── permission.guard.ts # Permission checks
|
|
104
|
-
│ │
|
|
105
|
-
│ ├── interceptors/ # Request/response interceptors
|
|
106
|
-
│ │ ├── delete-empty-id-from-body.interceptor.ts
|
|
107
|
-
│ │ ├── idempotency.interceptor.ts
|
|
108
|
-
│ │ ├── query-performance.interceptor.ts
|
|
109
|
-
│ │ ├── response-meta.interceptor.ts
|
|
110
|
-
│ │ ├── set-user-field-on-body.interceptor.ts # SetCreatedByOnBody, SetUpdateByOnBody, SetDeletedByOnBody
|
|
111
|
-
│ │ └── slug.interceptor.ts
|
|
112
|
-
│ │
|
|
113
|
-
│ ├── interfaces/ # TypeScript interfaces
|
|
114
|
-
│ │ ├── api.interface.ts # IService interface
|
|
115
|
-
│ │ ├── datasource.interface.ts # IDataSourceProvider
|
|
116
|
-
│ │ ├── identity.interface.ts
|
|
117
|
-
│ │ ├── logged-user-info.interface.ts # ILoggedUserInfo
|
|
118
|
-
│ │ ├── logger.interface.ts # ILogger
|
|
119
|
-
│ │ ├── module-config.interface.ts # IModuleConfigService
|
|
120
|
-
│ │ └── permission.interface.ts # PermissionCondition, PermissionOperator
|
|
121
|
-
│ │
|
|
122
|
-
│ ├── middlewares/ # Middleware
|
|
123
|
-
│ │ └── logger.middleware.ts # Request logging & correlation
|
|
124
|
-
│ │
|
|
125
|
-
│ ├── modules/ # NestJS modules
|
|
126
|
-
│ │ ├── cache/cache.module.ts
|
|
127
|
-
│ │ ├── datasource/
|
|
128
|
-
│ │ │ ├── datasource.module.ts
|
|
129
|
-
│ │ │ └── multi-tenant-datasource.service.ts
|
|
130
|
-
│ │ └── utils/
|
|
131
|
-
│ │ ├── utils.module.ts
|
|
132
|
-
│ │ └── utils.service.ts
|
|
133
|
-
│ │
|
|
134
|
-
│ └── utils/ # Utility functions
|
|
135
|
-
│ ├── error-handler.util.ts
|
|
136
|
-
│ ├── query-helpers.util.ts
|
|
137
|
-
│ ├── string.util.ts
|
|
138
|
-
│ ├── request.util.ts
|
|
139
|
-
│ └── html-sanitizer.util.ts
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
---
|
|
17
|
+
## 1. Module Registration
|
|
143
18
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
The `ApiService` base class provides standardized CRUD operations with caching, pagination, and transaction support.
|
|
147
|
-
|
|
148
|
-
### Basic Usage
|
|
19
|
+
Register once in your root `AppModule`:
|
|
149
20
|
|
|
150
21
|
```typescript
|
|
151
|
-
import {
|
|
152
|
-
import {
|
|
153
|
-
import {
|
|
154
|
-
import {
|
|
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';
|
|
155
27
|
|
|
156
|
-
@
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
@InjectRepository(User)
|
|
166
|
-
protected override repository: Repository<User>,
|
|
167
|
-
@Inject('CACHE_INSTANCE')
|
|
168
|
-
protected override cacheManager: HybridCache,
|
|
169
|
-
@Inject(UtilsService)
|
|
170
|
-
protected override utilsService: UtilsService,
|
|
171
|
-
) {
|
|
172
|
-
super(
|
|
173
|
-
'user', // Entity name (for query building)
|
|
174
|
-
repository,
|
|
175
|
-
cacheManager,
|
|
176
|
-
utilsService,
|
|
177
|
-
'UserService', // Service name (for logging)
|
|
178
|
-
true, // Enable caching
|
|
179
|
-
);
|
|
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('*');
|
|
180
37
|
}
|
|
181
38
|
}
|
|
182
|
-
```
|
|
183
39
|
|
|
184
|
-
|
|
40
|
+
// In bootstrap()
|
|
41
|
+
app.useGlobalFilters(new GlobalExceptionFilter());
|
|
42
|
+
app.useGlobalInterceptors(new ResponseMetaInterceptor());
|
|
43
|
+
```
|
|
185
44
|
|
|
186
|
-
|
|
187
|
-
|--------|-------------|
|
|
188
|
-
| `insert(dto, user)` | Create single entity |
|
|
189
|
-
| `insertMany(dtos, user)` | Create multiple entities |
|
|
190
|
-
| `getById(id, user, select?)` | Get entity by ID |
|
|
191
|
-
| `findById(id, user, select?)` | Find entity (returns null if not found) |
|
|
192
|
-
| `findByIds(ids, user, select?)` | Find multiple by IDs |
|
|
193
|
-
| `getAll(dto, user)` | Get paginated list |
|
|
194
|
-
| `update(dto, user)` | Update single entity |
|
|
195
|
-
| `updateMany(dtos, user)` | Update multiple entities |
|
|
196
|
-
| `delete(dto, user)` | Soft/permanent delete or restore |
|
|
45
|
+
## 2. Entity Base Class
|
|
197
46
|
|
|
198
|
-
|
|
47
|
+
Every entity must extend `Identity`:
|
|
199
48
|
|
|
200
49
|
```typescript
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
// Convert DTO to entity
|
|
204
|
-
override async convertSingleDtoToEntity(dto, user): Promise<User> { }
|
|
205
|
-
|
|
206
|
-
// Customize SELECT query
|
|
207
|
-
override async getSelectQuery(query, user, select?): Promise<{ query, isRaw }> { }
|
|
208
|
-
|
|
209
|
-
// Add WHERE filters
|
|
210
|
-
override async getFilterQuery(query, filter, user): Promise<{ query, isRaw }> { }
|
|
211
|
-
|
|
212
|
-
// Add global search
|
|
213
|
-
override async getGlobalSearchQuery(query, globalSearch, user): Promise<{ query, isRaw }> { }
|
|
214
|
-
|
|
215
|
-
// Add sort order
|
|
216
|
-
override async getSortQuery(query, sort, user): Promise<{ query, isRaw }> { }
|
|
217
|
-
|
|
218
|
-
// Add extra query conditions (e.g., company filtering)
|
|
219
|
-
override async getExtraManipulateQuery(query, dto, user): Promise<{ query, isRaw }> { }
|
|
220
|
-
|
|
221
|
-
// Before insert hook
|
|
222
|
-
override async beforeInsertOperation(entity, user, queryRunner): Promise<void> { }
|
|
223
|
-
|
|
224
|
-
// After insert hook
|
|
225
|
-
override async afterInsertOperation(entity, user, queryRunner): Promise<void> { }
|
|
226
|
-
|
|
227
|
-
// Before update hook
|
|
228
|
-
override async beforeUpdateOperation(oldEntity, newEntity, user, queryRunner): Promise<void> { }
|
|
229
|
-
|
|
230
|
-
// After update hook
|
|
231
|
-
override async afterUpdateOperation(entity, user, queryRunner): Promise<void> { }
|
|
232
|
-
|
|
233
|
-
// Before delete hook
|
|
234
|
-
override async beforeDeleteOperation(dto, user, queryRunner): Promise<void> { }
|
|
50
|
+
import { Entity, Column } from 'typeorm';
|
|
51
|
+
import { Identity } from '@flusys/nestjs-shared/entities';
|
|
235
52
|
|
|
236
|
-
|
|
237
|
-
|
|
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
|
|
238
59
|
}
|
|
239
60
|
```
|
|
240
61
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
## RequestScopedApiService
|
|
62
|
+
## 3. Generic CRUD — `createApiController` + `RequestScopedApiService`
|
|
244
63
|
|
|
245
|
-
|
|
64
|
+
### Service (REQUEST-scoped, DataSource Provider pattern)
|
|
246
65
|
|
|
247
66
|
```typescript
|
|
248
|
-
import { RequestScopedApiService } from '@flusys/nestjs-shared/classes';
|
|
249
67
|
import { Injectable, Scope, Inject } from '@nestjs/common';
|
|
250
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';
|
|
251
72
|
|
|
252
|
-
@Injectable({ scope: Scope.REQUEST })
|
|
253
|
-
export class
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
Repository<RoleBase>
|
|
73
|
+
@Injectable({ scope: Scope.REQUEST }) // required
|
|
74
|
+
export class ProductService extends RequestScopedApiService<
|
|
75
|
+
CreateProductDto,
|
|
76
|
+
UpdateProductDto,
|
|
77
|
+
IProduct,
|
|
78
|
+
Product
|
|
259
79
|
> {
|
|
260
80
|
constructor(
|
|
261
81
|
@Inject('CACHE_INSTANCE') protected override cacheManager: HybridCache,
|
|
262
82
|
@Inject(UtilsService) protected override utilsService: UtilsService,
|
|
263
|
-
@Inject(
|
|
264
|
-
@Inject(
|
|
83
|
+
@Inject(ProductConfigService) private readonly config: ProductConfigService,
|
|
84
|
+
@Inject(ProductDataSourceProvider) private readonly dsp: ProductDataSourceProvider,
|
|
265
85
|
) {
|
|
266
|
-
|
|
267
|
-
super('role', null as any, cacheManager, utilsService, 'RoleService', true);
|
|
86
|
+
super('product', null as any, cacheManager, utilsService, ProductService.name, true);
|
|
268
87
|
}
|
|
269
88
|
|
|
270
|
-
// Required:
|
|
271
|
-
protected resolveEntity(): EntityTarget<
|
|
272
|
-
return this.config.isCompanyFeatureEnabled() ?
|
|
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;
|
|
273
92
|
}
|
|
274
93
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
return this.provider;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// Optional: Initialize additional repositories
|
|
281
|
-
protected async initializeAdditionalRepositories(): Promise<void> {
|
|
282
|
-
this.actionRepository = await this.dataSourceProvider.getRepository(Action);
|
|
94
|
+
protected getDataSourceProvider() {
|
|
95
|
+
return this.dsp;
|
|
283
96
|
}
|
|
284
97
|
}
|
|
285
98
|
```
|
|
286
99
|
|
|
287
|
-
###
|
|
288
|
-
|
|
289
|
-
| Method | Description |
|
|
290
|
-
|--------|-------------|
|
|
291
|
-
| `ensureRepositoryInitialized()` | Must call before using repository |
|
|
292
|
-
| `resolveEntity()` | Abstract - return entity class based on config |
|
|
293
|
-
| `getDataSourceProvider()` | Abstract - return datasource provider |
|
|
294
|
-
| `getDataSourceForService()` | Get raw DataSource for transactions |
|
|
295
|
-
|
|
296
|
-
---
|
|
297
|
-
|
|
298
|
-
## ApiController - Generic CRUD Controller
|
|
299
|
-
|
|
300
|
-
The `createApiController` factory creates standardized POST-only RPC controllers.
|
|
301
|
-
|
|
302
|
-
### Basic Usage
|
|
100
|
+
### Controller (factory function)
|
|
303
101
|
|
|
304
102
|
```typescript
|
|
305
|
-
import { createApiController } from '@flusys/nestjs-shared/classes';
|
|
306
103
|
import { Controller, Inject } from '@nestjs/common';
|
|
307
|
-
import {
|
|
308
|
-
|
|
309
|
-
@ApiTags('Users')
|
|
310
|
-
@Controller('users')
|
|
311
|
-
export class UserController extends createApiController<
|
|
312
|
-
CreateUserDto,
|
|
313
|
-
UpdateUserDto,
|
|
314
|
-
UserResponseDto,
|
|
315
|
-
IUser,
|
|
316
|
-
UserService
|
|
317
|
-
>(CreateUserDto, UpdateUserDto, UserResponseDto) {
|
|
318
|
-
constructor(@Inject(UserService) protected service: UserService) {
|
|
319
|
-
super(service);
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
```
|
|
323
|
-
|
|
324
|
-
### Generated Endpoints
|
|
325
|
-
|
|
326
|
-
| Endpoint | Method | Description |
|
|
327
|
-
|----------|--------|-------------|
|
|
328
|
-
| `/insert` | POST | Create entity |
|
|
329
|
-
| `/insert-many` | POST | Create multiple entities |
|
|
330
|
-
| `/get/:id` | POST | Get entity by ID |
|
|
331
|
-
| `/get-all` | POST | Get paginated list |
|
|
332
|
-
| `/update` | POST | Update entity |
|
|
333
|
-
| `/update-many` | POST | Update multiple entities |
|
|
334
|
-
| `/delete` | POST | Delete entity |
|
|
335
|
-
|
|
336
|
-
### Security Configuration
|
|
337
|
-
|
|
338
|
-
```typescript
|
|
339
|
-
// Global security (all endpoints)
|
|
340
|
-
@Controller('users')
|
|
341
|
-
export class UserController extends createApiController(
|
|
342
|
-
CreateUserDto,
|
|
343
|
-
UpdateUserDto,
|
|
344
|
-
UserResponseDto,
|
|
345
|
-
{ security: 'jwt' }, // All endpoints require JWT
|
|
346
|
-
) {}
|
|
104
|
+
import { createApiController } from '@flusys/nestjs-shared/classes';
|
|
347
105
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
},
|
|
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'] },
|
|
362
126
|
},
|
|
363
|
-
) {
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
---
|
|
367
|
-
|
|
368
|
-
## Decorators
|
|
369
|
-
|
|
370
|
-
### @CurrentUser
|
|
371
|
-
|
|
372
|
-
Extract the logged-in user from request:
|
|
373
|
-
|
|
374
|
-
```typescript
|
|
375
|
-
import { CurrentUser } from '@flusys/nestjs-shared/decorators';
|
|
376
|
-
import { ILoggedUserInfo } from '@flusys/nestjs-shared/interfaces';
|
|
377
|
-
|
|
378
|
-
@Controller('profile')
|
|
379
|
-
export class ProfileController {
|
|
380
|
-
@Post('me')
|
|
381
|
-
getProfile(@CurrentUser() user: ILoggedUserInfo) {
|
|
382
|
-
return { userId: user.id, companyId: user.companyId };
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
// Extract specific property
|
|
386
|
-
@Post('id')
|
|
387
|
-
getUserId(@CurrentUser('id') userId: string) {
|
|
388
|
-
return { userId };
|
|
127
|
+
}) {
|
|
128
|
+
constructor(@Inject(ProductService) public override service: ProductService) {
|
|
129
|
+
super(service);
|
|
389
130
|
}
|
|
390
131
|
}
|
|
391
132
|
```
|
|
392
133
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
Mark route as public (skip authentication). **Use sparingly - security risk**.
|
|
396
|
-
|
|
397
|
-
```typescript
|
|
398
|
-
import { Public } 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`.
|
|
399
136
|
|
|
400
|
-
|
|
401
|
-
export class AuthController {
|
|
402
|
-
@Public()
|
|
403
|
-
@Post('login')
|
|
404
|
-
login() { }
|
|
405
|
-
}
|
|
406
|
-
```
|
|
137
|
+
Security levels: `'public'` (no auth), `'jwt'` (token only), `'permission'` (token + action check).
|
|
407
138
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
Require specific permission(s) - **AND logic** by default:
|
|
411
|
-
|
|
412
|
-
```typescript
|
|
413
|
-
import { RequirePermission } from '@flusys/nestjs-shared/decorators';
|
|
414
|
-
|
|
415
|
-
@Controller('admin')
|
|
416
|
-
export class AdminController {
|
|
417
|
-
// Requires 'admin.dashboard' permission
|
|
418
|
-
@RequirePermission('admin.dashboard')
|
|
419
|
-
@Post('dashboard')
|
|
420
|
-
getDashboard() { }
|
|
421
|
-
|
|
422
|
-
// Requires BOTH permissions
|
|
423
|
-
@RequirePermission('users.read', 'admin.access')
|
|
424
|
-
@Post('users')
|
|
425
|
-
getUsers() { }
|
|
426
|
-
}
|
|
427
|
-
```
|
|
428
|
-
|
|
429
|
-
### @RequireAnyPermission
|
|
430
|
-
|
|
431
|
-
Require any of the listed permissions - **OR logic**:
|
|
432
|
-
|
|
433
|
-
```typescript
|
|
434
|
-
import { RequireAnyPermission } from '@flusys/nestjs-shared/decorators';
|
|
435
|
-
|
|
436
|
-
@Controller('reports')
|
|
437
|
-
export class ReportsController {
|
|
438
|
-
// Requires 'reports.view' OR 'reports.admin'
|
|
439
|
-
@RequireAnyPermission('reports.view', 'reports.admin')
|
|
440
|
-
@Post()
|
|
441
|
-
getReports() { }
|
|
442
|
-
}
|
|
443
|
-
```
|
|
444
|
-
|
|
445
|
-
### @RequirePermissionCondition
|
|
446
|
-
|
|
447
|
-
Build complex permission conditions with nested AND/OR logic:
|
|
448
|
-
|
|
449
|
-
```typescript
|
|
450
|
-
import { RequirePermissionCondition } from '@flusys/nestjs-shared/decorators';
|
|
451
|
-
|
|
452
|
-
@Controller('sensitive')
|
|
453
|
-
export class SensitiveController {
|
|
454
|
-
// Complex: User needs 'users.read' AND ('admin' OR 'manager')
|
|
455
|
-
@RequirePermissionCondition({
|
|
456
|
-
operator: 'and',
|
|
457
|
-
permissions: ['users.read'],
|
|
458
|
-
children: [
|
|
459
|
-
{ operator: 'or', permissions: ['admin', 'manager'] }
|
|
460
|
-
]
|
|
461
|
-
})
|
|
462
|
-
@Post('complex')
|
|
463
|
-
getComplexData() { }
|
|
464
|
-
}
|
|
465
|
-
```
|
|
466
|
-
|
|
467
|
-
### @SanitizeHtml / @SanitizeAndTrim
|
|
468
|
-
|
|
469
|
-
Escape HTML entities for XSS prevention:
|
|
470
|
-
|
|
471
|
-
```typescript
|
|
472
|
-
import { SanitizeHtml, SanitizeAndTrim } from '@flusys/nestjs-shared/decorators';
|
|
473
|
-
|
|
474
|
-
export class CreateCommentDto {
|
|
475
|
-
@SanitizeHtml()
|
|
476
|
-
@IsString()
|
|
477
|
-
content: string;
|
|
478
|
-
|
|
479
|
-
@SanitizeAndTrim() // Escapes HTML AND trims whitespace
|
|
480
|
-
@IsString()
|
|
481
|
-
title: string;
|
|
482
|
-
}
|
|
483
|
-
```
|
|
484
|
-
|
|
485
|
-
### @ApiResponseDto
|
|
486
|
-
|
|
487
|
-
Generates Swagger schema for response:
|
|
488
|
-
|
|
489
|
-
```typescript
|
|
490
|
-
import { ApiResponseDto } from '@flusys/nestjs-shared/decorators';
|
|
491
|
-
|
|
492
|
-
@Controller('users')
|
|
493
|
-
export class UserController {
|
|
494
|
-
@Post('get-all')
|
|
495
|
-
@ApiResponseDto(UserResponseDto, true, 'list') // Array with PaginationMetaDto
|
|
496
|
-
getAll() { }
|
|
497
|
-
|
|
498
|
-
@Post('insert')
|
|
499
|
-
@ApiResponseDto(UserResponseDto, false) // Single item
|
|
500
|
-
insert() { }
|
|
501
|
-
}
|
|
502
|
-
```
|
|
503
|
-
|
|
504
|
-
---
|
|
505
|
-
|
|
506
|
-
## Guards
|
|
507
|
-
|
|
508
|
-
### JwtAuthGuard
|
|
509
|
-
|
|
510
|
-
Validates JWT tokens for protected routes. Extends Passport's `AuthGuard('jwt')` and respects `@Public()` decorator.
|
|
139
|
+
## 4. Guards and Decorators
|
|
511
140
|
|
|
512
141
|
```typescript
|
|
513
142
|
import { JwtAuthGuard } from '@flusys/nestjs-shared/guards';
|
|
514
|
-
|
|
515
|
-
// Apply globally in main.ts
|
|
516
|
-
@Module({
|
|
517
|
-
providers: [{ provide: APP_GUARD, useClass: JwtAuthGuard }],
|
|
518
|
-
})
|
|
519
|
-
export class AppModule {}
|
|
520
|
-
```
|
|
521
|
-
|
|
522
|
-
**Important:** Constructor needs `@Inject(Reflector)` for bundled code.
|
|
523
|
-
|
|
524
|
-
### PermissionGuard
|
|
525
|
-
|
|
526
|
-
Checks user permissions from cache with AND/OR/nested logic support.
|
|
527
|
-
|
|
528
|
-
```typescript
|
|
529
143
|
import { PermissionGuard } from '@flusys/nestjs-shared/guards';
|
|
530
|
-
|
|
531
|
-
@Module({
|
|
532
|
-
providers: [
|
|
533
|
-
{ provide: APP_GUARD, useClass: PermissionGuard },
|
|
534
|
-
{
|
|
535
|
-
provide: 'PERMISSION_GUARD_CONFIG',
|
|
536
|
-
useValue: {
|
|
537
|
-
enableCompanyFeature: true,
|
|
538
|
-
userPermissionKeyFormat: 'permissions:user:{userId}',
|
|
539
|
-
companyPermissionKeyFormat: 'permissions:company:{companyId}:branch:{branchId}:user:{userId}',
|
|
540
|
-
},
|
|
541
|
-
},
|
|
542
|
-
],
|
|
543
|
-
})
|
|
544
|
-
export class AppModule {}
|
|
545
|
-
```
|
|
546
|
-
|
|
547
|
-
**Cache Key Formats:**
|
|
548
|
-
|
|
549
|
-
```typescript
|
|
550
|
-
// Without company feature
|
|
551
|
-
`permissions:user:{userId}`
|
|
552
|
-
|
|
553
|
-
// With company feature
|
|
554
|
-
`permissions:company:{companyId}:branch:{branchId}:user:{userId}`
|
|
555
|
-
```
|
|
556
|
-
|
|
557
|
-
### Permission Exceptions
|
|
558
|
-
|
|
559
|
-
```typescript
|
|
560
144
|
import {
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
---
|
|
145
|
+
CurrentUser,
|
|
146
|
+
Public,
|
|
147
|
+
RequirePermission,
|
|
148
|
+
RequireAnyPermission,
|
|
149
|
+
} from '@flusys/nestjs-shared/decorators';
|
|
150
|
+
import { ILoggedUserInfo } from '@flusys/nestjs-shared/interfaces';
|
|
568
151
|
|
|
569
|
-
|
|
152
|
+
@UseGuards(JwtAuthGuard, PermissionGuard)
|
|
153
|
+
@RequirePermission('product.create')
|
|
154
|
+
@Post('insert')
|
|
155
|
+
async insert(@Body() dto: CreateProductDto, @CurrentUser() user: ILoggedUserInfo) { }
|
|
570
156
|
|
|
571
|
-
|
|
157
|
+
// Mark endpoint public (no JWT required)
|
|
158
|
+
@Public()
|
|
159
|
+
@Post('public-list')
|
|
160
|
+
async publicList() { }
|
|
161
|
+
```
|
|
572
162
|
|
|
573
|
-
|
|
163
|
+
`ILoggedUserInfo` fields: `id`, `email`, `name?`, `phone?`, `profilePictureId?`, `companyId?`, `branchId?`.
|
|
574
164
|
|
|
575
|
-
|
|
576
|
-
- Request ID generation/tracking (UUID or from `x-request-id` header)
|
|
577
|
-
- Tenant ID tracking (from `x-tenant-id` header)
|
|
578
|
-
- AsyncLocalStorage context for thread-safe access
|
|
579
|
-
- Automatic sensitive header redaction (authorization, cookie, x-api-key)
|
|
580
|
-
- Performance monitoring (warns on requests > 3s)
|
|
581
|
-
- Body truncation (max 1000 chars)
|
|
165
|
+
## 5. Response DTOs
|
|
582
166
|
|
|
583
|
-
|
|
167
|
+
All controllers return one of these shapes:
|
|
584
168
|
|
|
585
169
|
```typescript
|
|
586
|
-
import {
|
|
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';
|
|
587
176
|
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
configure(consumer: MiddlewareConsumer) {
|
|
591
|
-
consumer.apply(LoggerMiddleware).forRoutes('*');
|
|
592
|
-
}
|
|
593
|
-
}
|
|
177
|
+
// All shapes include: success, message, messageKey (for i18n), _meta (requestId, timestamp, duration)
|
|
178
|
+
// ListResponseDto / BulkResponseDto also include: meta (total, page, pageSize, count, totalPages)
|
|
594
179
|
```
|
|
595
180
|
|
|
596
|
-
|
|
181
|
+
## 6. Exceptions
|
|
597
182
|
|
|
598
183
|
```typescript
|
|
599
184
|
import {
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
} from '@flusys/nestjs-shared/
|
|
185
|
+
NotFoundException,
|
|
186
|
+
ConflictException,
|
|
187
|
+
UnauthorizedException,
|
|
188
|
+
ForbiddenException,
|
|
189
|
+
ValidationException,
|
|
190
|
+
InternalServerException,
|
|
191
|
+
} from '@flusys/nestjs-shared/exceptions';
|
|
607
192
|
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
}
|
|
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' }]);
|
|
616
203
|
```
|
|
617
204
|
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
## Interceptors
|
|
621
|
-
|
|
622
|
-
### ResponseMetaInterceptor
|
|
205
|
+
All exceptions produce: `{ success: false, message, messageKey,messageVariables:{} code, _meta }`.
|
|
623
206
|
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
```typescript
|
|
627
|
-
// Response includes:
|
|
628
|
-
{
|
|
629
|
-
"data": [...],
|
|
630
|
-
"_meta": {
|
|
631
|
-
"requestId": "abc-123",
|
|
632
|
-
"timestamp": "2024-01-01T00:00:00.000Z",
|
|
633
|
-
"responseTime": 45
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
```
|
|
637
|
-
|
|
638
|
-
### IdempotencyInterceptor
|
|
639
|
-
|
|
640
|
-
Prevents duplicate POST requests using `X-Idempotency-Key` header. Caches responses for 24 hours.
|
|
641
|
-
|
|
642
|
-
### SetCreatedByOnBody / SetUpdateByOnBody / SetDeletedByOnBody
|
|
643
|
-
|
|
644
|
-
Auto-set audit user IDs on request body from authenticated user.
|
|
645
|
-
|
|
646
|
-
### DeleteEmptyIdFromBodyInterceptor
|
|
647
|
-
|
|
648
|
-
Removes empty `id` fields from request body (single and array bodies).
|
|
649
|
-
|
|
650
|
-
### QueryPerformanceInterceptor
|
|
651
|
-
|
|
652
|
-
Monitors execution time and warns if request > 1000ms (configurable).
|
|
653
|
-
|
|
654
|
-
### Slug Interceptor
|
|
655
|
-
|
|
656
|
-
Auto-generates `slug` from `name` field using `UtilsService.transformToSlug()`.
|
|
657
|
-
|
|
658
|
-
---
|
|
659
|
-
|
|
660
|
-
## Caching System
|
|
661
|
-
|
|
662
|
-
### HybridCache
|
|
663
|
-
|
|
664
|
-
Two-tier caching with in-memory (L1) and Redis (L2):
|
|
207
|
+
## 7. Hybrid Cache
|
|
665
208
|
|
|
666
209
|
```typescript
|
|
667
210
|
import { HybridCache } from '@flusys/nestjs-shared/classes';
|
|
@@ -671,531 +214,24 @@ export class MyService {
|
|
|
671
214
|
constructor(@Inject('CACHE_INSTANCE') private cache: HybridCache) {}
|
|
672
215
|
|
|
673
216
|
async getData(key: string) {
|
|
674
|
-
// Check cache first
|
|
675
217
|
const cached = await this.cache.get(key);
|
|
676
218
|
if (cached) return cached;
|
|
677
|
-
|
|
678
219
|
const data = await this.fetchFromDb();
|
|
679
|
-
await this.cache.set(key, data, 3600);
|
|
220
|
+
await this.cache.set(key, data, 3600); // TTL in seconds
|
|
680
221
|
return data;
|
|
681
222
|
}
|
|
682
223
|
|
|
683
224
|
async invalidate(key: string) {
|
|
684
|
-
await this.cache.
|
|
225
|
+
await this.cache.delete(key);
|
|
685
226
|
}
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
await this.cache.reset(); // Clear L1
|
|
689
|
-
await this.cache.resetL2(); // Clear L2 (Redis)
|
|
227
|
+
async invalidatePrefix(prefix: string) {
|
|
228
|
+
await this.cache.deleteByPrefix(prefix);
|
|
690
229
|
}
|
|
691
230
|
}
|
|
692
231
|
```
|
|
693
232
|
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
```typescript
|
|
697
|
-
import { CacheModule } from '@flusys/nestjs-shared/modules';
|
|
698
|
-
|
|
699
|
-
@Module({
|
|
700
|
-
imports: [
|
|
701
|
-
CacheModule.forRoot(
|
|
702
|
-
true, // isGlobal
|
|
703
|
-
60_000, // memoryTtl (ms)
|
|
704
|
-
5000 // memorySize (LRU max items)
|
|
705
|
-
),
|
|
706
|
-
],
|
|
707
|
-
})
|
|
708
|
-
export class AppModule {}
|
|
709
|
-
```
|
|
710
|
-
|
|
711
|
-
**Configuration via `USE_CACHE_LABEL` env:**
|
|
712
|
-
- `'memory'` - L1 only
|
|
713
|
-
- `'redis'` - L2 only
|
|
714
|
-
- `'hybrid'` - Both (default)
|
|
715
|
-
|
|
716
|
-
---
|
|
717
|
-
|
|
718
|
-
## Multi-Tenant DataSource
|
|
719
|
-
|
|
720
|
-
Dynamic database connection management with connection pooling.
|
|
721
|
-
|
|
722
|
-
### Setup
|
|
723
|
-
|
|
724
|
-
```typescript
|
|
725
|
-
import { DataSourceModule } from '@flusys/nestjs-shared/modules';
|
|
726
|
-
|
|
727
|
-
@Module({
|
|
728
|
-
imports: [
|
|
729
|
-
DataSourceModule.forRoot({
|
|
730
|
-
bootstrapAppConfig: { databaseMode: 'multi-tenant' },
|
|
731
|
-
defaultDatabaseConfig: {
|
|
732
|
-
type: 'mysql',
|
|
733
|
-
host: 'localhost',
|
|
734
|
-
port: 3306,
|
|
735
|
-
username: 'root',
|
|
736
|
-
password: 'password',
|
|
737
|
-
},
|
|
738
|
-
tenants: [
|
|
739
|
-
{ id: 'tenant1', database: 'tenant1_db' },
|
|
740
|
-
{ id: 'tenant2', database: 'tenant2_db', host: 'other-server.com' },
|
|
741
|
-
],
|
|
742
|
-
}),
|
|
743
|
-
],
|
|
744
|
-
})
|
|
745
|
-
export class AppModule {}
|
|
746
|
-
```
|
|
747
|
-
|
|
748
|
-
### MultiTenantDataSourceService
|
|
749
|
-
|
|
750
|
-
| Method | Description |
|
|
751
|
-
|--------|-------------|
|
|
752
|
-
| `getDataSource()` | Get DataSource for current tenant (from header) |
|
|
753
|
-
| `getDataSourceForTenant(id)` | Get DataSource for specific tenant |
|
|
754
|
-
| `getRepository(entity)` | Get repository for current tenant |
|
|
755
|
-
| `withTenant(id, callback)` | Execute callback with specific tenant |
|
|
756
|
-
| `forAllTenants(callback)` | Execute callback for all tenants |
|
|
757
|
-
| `registerTenant(config)` | Register new tenant at runtime |
|
|
758
|
-
| `removeTenant(id)` | Remove tenant and close connection |
|
|
759
|
-
| `getCurrentTenantId()` | Get tenant ID from request header |
|
|
760
|
-
|
|
761
|
-
---
|
|
762
|
-
|
|
763
|
-
## DTOs
|
|
764
|
-
|
|
765
|
-
### FilterAndPaginationDto
|
|
766
|
-
|
|
767
|
-
```typescript
|
|
768
|
-
import { FilterAndPaginationDto } from '@flusys/nestjs-shared/dtos';
|
|
769
|
-
|
|
770
|
-
// Request body
|
|
771
|
-
{
|
|
772
|
-
"filter": { "status": "active" },
|
|
773
|
-
"pagination": { "currentPage": 0, "pageSize": 10 },
|
|
774
|
-
"sort": { "createdAt": "DESC" },
|
|
775
|
-
"select": ["id", "name", "email"],
|
|
776
|
-
"withDeleted": false
|
|
777
|
-
}
|
|
778
|
-
```
|
|
779
|
-
|
|
780
|
-
### DeleteDto
|
|
781
|
-
|
|
782
|
-
```typescript
|
|
783
|
-
import { DeleteDto } from '@flusys/nestjs-shared/dtos';
|
|
784
|
-
|
|
785
|
-
// Soft delete
|
|
786
|
-
{ "id": "uuid", "type": "delete" }
|
|
787
|
-
|
|
788
|
-
// Restore
|
|
789
|
-
{ "id": "uuid", "type": "restore" }
|
|
790
|
-
|
|
791
|
-
// Permanent delete
|
|
792
|
-
{ "id": "uuid", "type": "permanent" }
|
|
793
|
-
|
|
794
|
-
// Multiple IDs
|
|
795
|
-
{ "id": ["uuid1", "uuid2"], "type": "delete" }
|
|
796
|
-
```
|
|
797
|
-
|
|
798
|
-
### Response DTOs
|
|
799
|
-
|
|
800
|
-
```typescript
|
|
801
|
-
// Single item
|
|
802
|
-
class SingleResponseDto<T> {
|
|
803
|
-
success: boolean;
|
|
804
|
-
message: string;
|
|
805
|
-
data?: T;
|
|
806
|
-
_meta?: RequestMetaDto;
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
// Paginated list
|
|
810
|
-
class ListResponseDto<T> {
|
|
811
|
-
success: boolean;
|
|
812
|
-
message: string;
|
|
813
|
-
data?: T[];
|
|
814
|
-
meta: PaginationMetaDto; // { total, page, pageSize, count, hasMore?, totalPages? }
|
|
815
|
-
_meta?: RequestMetaDto;
|
|
816
|
-
}
|
|
817
|
-
|
|
818
|
-
// Bulk operations
|
|
819
|
-
class BulkResponseDto<T> {
|
|
820
|
-
success: boolean;
|
|
821
|
-
message: string;
|
|
822
|
-
data?: T[];
|
|
823
|
-
meta: BulkMetaDto; // { count, failed?, total? }
|
|
824
|
-
_meta?: RequestMetaDto;
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
// Message only
|
|
828
|
-
class MessageResponseDto {
|
|
829
|
-
success: boolean;
|
|
830
|
-
message: string;
|
|
831
|
-
_meta?: RequestMetaDto;
|
|
832
|
-
}
|
|
833
|
-
```
|
|
834
|
-
|
|
835
|
-
---
|
|
836
|
-
|
|
837
|
-
## Base Entities
|
|
838
|
-
|
|
839
|
-
### Identity Entity
|
|
840
|
-
|
|
841
|
-
Base entity with UUID and timestamps:
|
|
842
|
-
|
|
843
|
-
```typescript
|
|
844
|
-
import { Identity } from '@flusys/nestjs-shared/entities';
|
|
845
|
-
|
|
846
|
-
@Entity()
|
|
847
|
-
export class Product extends Identity {
|
|
848
|
-
// Inherited: id, createdAt, updatedAt, deletedAt
|
|
849
|
-
// Inherited: createdById, updatedById, deletedById
|
|
850
|
-
|
|
851
|
-
@Column()
|
|
852
|
-
name: string;
|
|
853
|
-
}
|
|
854
|
-
```
|
|
855
|
-
|
|
856
|
-
### UserRoot Entity
|
|
857
|
-
|
|
858
|
-
Base user entity with common fields:
|
|
859
|
-
|
|
860
|
-
```typescript
|
|
861
|
-
import { UserRoot } from '@flusys/nestjs-shared/entities';
|
|
862
|
-
|
|
863
|
-
@Entity()
|
|
864
|
-
export class User extends UserRoot {
|
|
865
|
-
// Inherited: id, name, email, phone, profilePictureId
|
|
866
|
-
// Inherited: isActive, emailVerified, phoneVerified
|
|
867
|
-
// Inherited: lastLoginAt, additionalFields
|
|
868
|
-
// Inherited: createdAt, updatedAt, deletedAt
|
|
869
|
-
}
|
|
870
|
-
```
|
|
871
|
-
|
|
872
|
-
---
|
|
873
|
-
|
|
874
|
-
## Utilities
|
|
875
|
-
|
|
876
|
-
### Query Helpers
|
|
877
|
-
|
|
878
|
-
```typescript
|
|
879
|
-
import {
|
|
880
|
-
applyCompanyFilter,
|
|
881
|
-
buildCompanyWhereCondition,
|
|
882
|
-
hasCompanyId,
|
|
883
|
-
validateCompanyOwnership,
|
|
884
|
-
} from '@flusys/nestjs-shared/utils';
|
|
885
|
-
|
|
886
|
-
// Add company filter to TypeORM query
|
|
887
|
-
applyCompanyFilter(query, {
|
|
888
|
-
isCompanyFeatureEnabled: true,
|
|
889
|
-
entityAlias: 'entity',
|
|
890
|
-
}, user);
|
|
891
|
-
|
|
892
|
-
// Build where condition for company
|
|
893
|
-
const where = buildCompanyWhereCondition(baseWhere, user, isCompanyFeatureEnabled);
|
|
894
|
-
|
|
895
|
-
// Validate entity belongs to user's company
|
|
896
|
-
validateCompanyOwnership(entity, user, 'Entity');
|
|
897
|
-
```
|
|
898
|
-
|
|
899
|
-
### String Utilities
|
|
900
|
-
|
|
901
|
-
```typescript
|
|
902
|
-
import { generateSlug, generateUniqueSlug } from '@flusys/nestjs-shared/utils';
|
|
903
|
-
|
|
904
|
-
// Generate URL-friendly slug
|
|
905
|
-
const slug = generateSlug('My Product Name', 100);
|
|
906
|
-
// Returns: 'my-product-name'
|
|
907
|
-
|
|
908
|
-
// Generate unique slug with collision detection
|
|
909
|
-
const uniqueSlug = await generateUniqueSlug(
|
|
910
|
-
'My Product',
|
|
911
|
-
async (pattern) => existingSlugs.filter(s => s.startsWith(pattern)),
|
|
912
|
-
100
|
|
913
|
-
);
|
|
914
|
-
```
|
|
915
|
-
|
|
916
|
-
### Request Utilities
|
|
917
|
-
|
|
918
|
-
```typescript
|
|
919
|
-
import {
|
|
920
|
-
isBrowserRequest,
|
|
921
|
-
buildCookieOptions,
|
|
922
|
-
parseDurationToMs,
|
|
923
|
-
} from '@flusys/nestjs-shared/utils';
|
|
924
|
-
|
|
925
|
-
// Detect browser vs API client
|
|
926
|
-
const isBrowser = isBrowserRequest(req);
|
|
927
|
-
|
|
928
|
-
// Build secure cookie options
|
|
929
|
-
const cookieOpts = buildCookieOptions(req);
|
|
930
|
-
|
|
931
|
-
// Parse duration string
|
|
932
|
-
const ms = parseDurationToMs('7d'); // 604800000
|
|
933
|
-
```
|
|
934
|
-
|
|
935
|
-
### HTML Sanitizer
|
|
936
|
-
|
|
937
|
-
```typescript
|
|
938
|
-
import { escapeHtml, escapeHtmlVariables } from '@flusys/nestjs-shared/utils';
|
|
939
|
-
|
|
940
|
-
// Escape single string
|
|
941
|
-
const safe = escapeHtml('<script>alert("xss")</script>');
|
|
942
|
-
// Returns: '<script>alert("xss")</script>'
|
|
943
|
-
|
|
944
|
-
// Escape all values in an object
|
|
945
|
-
const safeVars = escapeHtmlVariables({
|
|
946
|
-
userName: '<script>evil</script>',
|
|
947
|
-
message: 'Hello, World!',
|
|
948
|
-
});
|
|
949
|
-
```
|
|
950
|
-
|
|
951
|
-
---
|
|
952
|
-
|
|
953
|
-
## Error Handling
|
|
954
|
-
|
|
955
|
-
### Error Handler Utilities
|
|
956
|
-
|
|
957
|
-
```typescript
|
|
958
|
-
import {
|
|
959
|
-
getErrorMessage,
|
|
960
|
-
logError,
|
|
961
|
-
rethrowError,
|
|
962
|
-
logAndRethrow,
|
|
963
|
-
} from '@flusys/nestjs-shared/utils';
|
|
964
|
-
|
|
965
|
-
interface IErrorContext {
|
|
966
|
-
operation?: string;
|
|
967
|
-
entity?: string;
|
|
968
|
-
userId?: string;
|
|
969
|
-
id?: string;
|
|
970
|
-
companyId?: string;
|
|
971
|
-
data?: Record<string, unknown>;
|
|
972
|
-
}
|
|
973
|
-
|
|
974
|
-
// Safe error message extraction
|
|
975
|
-
const message = getErrorMessage(error);
|
|
976
|
-
|
|
977
|
-
// Log with sensitive key redaction (password, secret, token, apiKey)
|
|
978
|
-
logError(logger, error, 'createUser', { userId: user.id });
|
|
979
|
-
|
|
980
|
-
// Type-safe rethrow
|
|
981
|
-
rethrowError(error);
|
|
982
|
-
|
|
983
|
-
// Combined log + rethrow
|
|
984
|
-
logAndRethrow(logger, error, 'updateUser', context);
|
|
985
|
-
```
|
|
986
|
-
|
|
987
|
-
---
|
|
988
|
-
|
|
989
|
-
## Constants
|
|
990
|
-
|
|
991
|
-
### Permission Constants
|
|
992
|
-
|
|
993
|
-
```typescript
|
|
994
|
-
import { PERMISSIONS } from '@flusys/nestjs-shared/constants';
|
|
995
|
-
|
|
996
|
-
// Organized by module:
|
|
997
|
-
PERMISSIONS.USER.CREATE // 'user.create'
|
|
998
|
-
PERMISSIONS.USER.READ // 'user.read'
|
|
999
|
-
PERMISSIONS.USER.UPDATE // 'user.update'
|
|
1000
|
-
PERMISSIONS.USER.DELETE // 'user.delete'
|
|
1001
|
-
|
|
1002
|
-
PERMISSIONS.COMPANY.CREATE // 'company.create'
|
|
1003
|
-
PERMISSIONS.BRANCH.CREATE // 'branch.create'
|
|
1004
|
-
|
|
1005
|
-
PERMISSIONS.ROLE.CREATE // 'role.create'
|
|
1006
|
-
PERMISSIONS.FILE.CREATE // 'file.create'
|
|
1007
|
-
PERMISSIONS.FOLDER.CREATE // 'folder.create'
|
|
1008
|
-
// ...etc
|
|
1009
|
-
```
|
|
1010
|
-
|
|
1011
|
-
### Injection Tokens
|
|
1012
|
-
|
|
1013
|
-
```typescript
|
|
1014
|
-
'CACHE_INSTANCE' // HybridCache provider
|
|
1015
|
-
'PERMISSION_GUARD_CONFIG' // PermissionGuard config
|
|
1016
|
-
'LOGGER_INSTANCE' // Logger provider
|
|
1017
|
-
```
|
|
233
|
+
`CacheModule.forRoot(true)` connects to Redis automatically when `REDIS_URL` is set; otherwise uses in-memory only.
|
|
1018
234
|
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
## API Reference
|
|
1022
|
-
|
|
1023
|
-
### Main Exports
|
|
1024
|
-
|
|
1025
|
-
```typescript
|
|
1026
|
-
// Classes
|
|
1027
|
-
import {
|
|
1028
|
-
ApiService,
|
|
1029
|
-
RequestScopedApiService,
|
|
1030
|
-
createApiController,
|
|
1031
|
-
HybridCache,
|
|
1032
|
-
WinstonLoggerAdapter,
|
|
1033
|
-
NestLoggerAdapter,
|
|
1034
|
-
} from '@flusys/nestjs-shared/classes';
|
|
1035
|
-
|
|
1036
|
-
// Decorators
|
|
1037
|
-
import {
|
|
1038
|
-
CurrentUser,
|
|
1039
|
-
Public,
|
|
1040
|
-
RequirePermission,
|
|
1041
|
-
RequireAnyPermission,
|
|
1042
|
-
RequirePermissionCondition,
|
|
1043
|
-
SanitizeHtml,
|
|
1044
|
-
SanitizeAndTrim,
|
|
1045
|
-
ApiResponseDto,
|
|
1046
|
-
} from '@flusys/nestjs-shared/decorators';
|
|
1047
|
-
|
|
1048
|
-
// Guards
|
|
1049
|
-
import { JwtAuthGuard, PermissionGuard } from '@flusys/nestjs-shared/guards';
|
|
1050
|
-
|
|
1051
|
-
// Interceptors
|
|
1052
|
-
import {
|
|
1053
|
-
ResponseMetaInterceptor,
|
|
1054
|
-
IdempotencyInterceptor,
|
|
1055
|
-
SetCreatedByOnBody,
|
|
1056
|
-
SetUpdateByOnBody,
|
|
1057
|
-
SetDeletedByOnBody,
|
|
1058
|
-
DeleteEmptyIdFromBodyInterceptor,
|
|
1059
|
-
QueryPerformanceInterceptor,
|
|
1060
|
-
Slug,
|
|
1061
|
-
} from '@flusys/nestjs-shared/interceptors';
|
|
1062
|
-
|
|
1063
|
-
// Modules
|
|
1064
|
-
import {
|
|
1065
|
-
CacheModule,
|
|
1066
|
-
DataSourceModule,
|
|
1067
|
-
UtilsModule,
|
|
1068
|
-
UtilsService,
|
|
1069
|
-
MultiTenantDataSourceService,
|
|
1070
|
-
} from '@flusys/nestjs-shared/modules';
|
|
1071
|
-
|
|
1072
|
-
// DTOs
|
|
1073
|
-
import {
|
|
1074
|
-
FilterAndPaginationDto,
|
|
1075
|
-
PaginationDto,
|
|
1076
|
-
DeleteDto,
|
|
1077
|
-
GetByIdBodyDto,
|
|
1078
|
-
SingleResponseDto,
|
|
1079
|
-
ListResponseDto,
|
|
1080
|
-
BulkResponseDto,
|
|
1081
|
-
MessageResponseDto,
|
|
1082
|
-
IdentityResponseDto,
|
|
1083
|
-
PaginationMetaDto,
|
|
1084
|
-
BulkMetaDto,
|
|
1085
|
-
RequestMetaDto,
|
|
1086
|
-
} from '@flusys/nestjs-shared/dtos';
|
|
1087
|
-
|
|
1088
|
-
// Entities
|
|
1089
|
-
import { Identity, UserRoot } from '@flusys/nestjs-shared/entities';
|
|
1090
|
-
|
|
1091
|
-
// Interfaces
|
|
1092
|
-
import {
|
|
1093
|
-
ILoggedUserInfo,
|
|
1094
|
-
IService,
|
|
1095
|
-
IDataSourceProvider,
|
|
1096
|
-
IModuleConfigService,
|
|
1097
|
-
ILogger,
|
|
1098
|
-
PermissionCondition,
|
|
1099
|
-
PermissionOperator,
|
|
1100
|
-
} from '@flusys/nestjs-shared/interfaces';
|
|
1101
|
-
|
|
1102
|
-
// Utilities
|
|
1103
|
-
import {
|
|
1104
|
-
getErrorMessage,
|
|
1105
|
-
logError,
|
|
1106
|
-
applyCompanyFilter,
|
|
1107
|
-
buildCompanyWhereCondition,
|
|
1108
|
-
validateCompanyOwnership,
|
|
1109
|
-
generateSlug,
|
|
1110
|
-
generateUniqueSlug,
|
|
1111
|
-
isBrowserRequest,
|
|
1112
|
-
buildCookieOptions,
|
|
1113
|
-
parseDurationToMs,
|
|
1114
|
-
escapeHtml,
|
|
1115
|
-
escapeHtmlVariables,
|
|
1116
|
-
} from '@flusys/nestjs-shared/utils';
|
|
1117
|
-
|
|
1118
|
-
// Middleware
|
|
1119
|
-
import {
|
|
1120
|
-
LoggerMiddleware,
|
|
1121
|
-
getRequestId,
|
|
1122
|
-
getTenantId,
|
|
1123
|
-
getUserId,
|
|
1124
|
-
getCompanyId,
|
|
1125
|
-
setUserId,
|
|
1126
|
-
setCompanyId,
|
|
1127
|
-
} from '@flusys/nestjs-shared/middlewares';
|
|
1128
|
-
|
|
1129
|
-
// Exceptions
|
|
1130
|
-
import {
|
|
1131
|
-
InsufficientPermissionsException,
|
|
1132
|
-
NoPermissionsFoundException,
|
|
1133
|
-
PermissionSystemUnavailableException,
|
|
1134
|
-
} from '@flusys/nestjs-shared/exceptions';
|
|
1135
|
-
|
|
1136
|
-
// Constants
|
|
1137
|
-
import { PERMISSIONS } from '@flusys/nestjs-shared/constants';
|
|
1138
|
-
```
|
|
1139
|
-
|
|
1140
|
-
---
|
|
1141
|
-
|
|
1142
|
-
## Best Practices
|
|
1143
|
-
|
|
1144
|
-
### 1. Use Generic Service Pattern
|
|
1145
|
-
|
|
1146
|
-
```typescript
|
|
1147
|
-
// Extend ApiService for consistent CRUD
|
|
1148
|
-
@Injectable()
|
|
1149
|
-
export class ProductService extends ApiService<...> {
|
|
1150
|
-
// Override hooks for customization
|
|
1151
|
-
}
|
|
1152
|
-
|
|
1153
|
-
// For dynamic entities, use RequestScopedApiService
|
|
1154
|
-
@Injectable({ scope: Scope.REQUEST })
|
|
1155
|
-
export class RoleService extends RequestScopedApiService<...> { }
|
|
1156
|
-
```
|
|
1157
|
-
|
|
1158
|
-
### 2. Always Use @Inject() Decorators
|
|
1159
|
-
|
|
1160
|
-
Required for esbuild bundled code:
|
|
1161
|
-
|
|
1162
|
-
```typescript
|
|
1163
|
-
// CORRECT
|
|
1164
|
-
constructor(
|
|
1165
|
-
@Inject(MyService) private readonly myService: MyService,
|
|
1166
|
-
@Inject('CACHE_INSTANCE') private readonly cache: HybridCache,
|
|
1167
|
-
) {}
|
|
1168
|
-
|
|
1169
|
-
// WRONG - fails in bundled code
|
|
1170
|
-
constructor(private readonly myService: MyService) {}
|
|
1171
|
-
```
|
|
1172
|
-
|
|
1173
|
-
### 3. Use Decorators Consistently
|
|
1174
|
-
|
|
1175
|
-
```typescript
|
|
1176
|
-
// Use built-in decorators
|
|
1177
|
-
@CurrentUser() user: ILoggedUserInfo
|
|
1178
|
-
@RequirePermission('users.create')
|
|
1179
|
-
@Public() // Use sparingly!
|
|
1180
|
-
|
|
1181
|
-
// Don't access request directly
|
|
1182
|
-
@Req() req: Request // Not type-safe
|
|
1183
|
-
```
|
|
1184
|
-
|
|
1185
|
-
### 4. Configure Security at Controller Level
|
|
1186
|
-
|
|
1187
|
-
```typescript
|
|
1188
|
-
// GOOD - configure in createApiController
|
|
1189
|
-
export class UserController extends createApiController(..., {
|
|
1190
|
-
security: { insert: 'permission', ... },
|
|
1191
|
-
}) {}
|
|
1192
|
-
|
|
1193
|
-
// AVOID - adding guards to each endpoint
|
|
1194
|
-
@UseGuards(JwtGuard)
|
|
1195
|
-
@Post('create')
|
|
1196
|
-
create() {}
|
|
1197
|
-
```
|
|
1198
|
-
|
|
1199
|
-
---
|
|
235
|
+
## License
|
|
1200
236
|
|
|
1201
|
-
|
|
237
|
+
MIT © FLUSYS
|