@flusys/nestjs-shared 0.1.0-beta.3 → 1.0.0-rc

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 (102) hide show
  1. package/README.md +106 -138
  2. package/cjs/classes/api-controller.class.js +9 -45
  3. package/cjs/classes/api-service.class.js +4 -41
  4. package/cjs/classes/index.js +1 -0
  5. package/cjs/classes/request-scoped-api.service.js +4 -53
  6. package/cjs/classes/winston.logger.class.js +5 -15
  7. package/cjs/constants/index.js +16 -11
  8. package/cjs/constants/permissions.js +174 -0
  9. package/cjs/decorators/api-response.decorator.js +1 -1
  10. package/cjs/decorators/index.js +1 -0
  11. package/cjs/decorators/sanitize-html.decorator.js +36 -0
  12. package/cjs/dtos/filter-and-pagination.dto.js +24 -34
  13. package/cjs/dtos/pagination.dto.js +4 -8
  14. package/cjs/dtos/response-payload.dto.js +35 -121
  15. package/cjs/entities/identity.js +4 -4
  16. package/cjs/entities/user-root.js +13 -14
  17. package/cjs/guards/permission.guard.js +39 -94
  18. package/cjs/interceptors/index.js +1 -0
  19. package/cjs/interceptors/set-create-by-on-body.interceptor.js +2 -30
  20. package/cjs/interceptors/set-delete-by-on-body.interceptor.js +2 -30
  21. package/cjs/interceptors/set-update-by-on-body.interceptor.js +2 -30
  22. package/cjs/interceptors/set-user-field-on-body.interceptor.js +43 -0
  23. package/cjs/interceptors/slug.interceptor.js +30 -9
  24. package/cjs/interfaces/datasource.interface.js +4 -0
  25. package/cjs/interfaces/index.js +2 -1
  26. package/cjs/interfaces/logged-user-info.interface.js +1 -2
  27. package/cjs/interfaces/module-config.interface.js +4 -0
  28. package/cjs/interfaces/permission.interface.js +1 -10
  29. package/cjs/middlewares/logger.middleware.js +2 -6
  30. package/cjs/modules/cache/cache.module.js +3 -3
  31. package/cjs/modules/datasource/multi-tenant-datasource.service.js +31 -111
  32. package/cjs/modules/utils/utils.service.js +63 -145
  33. package/cjs/utils/error-handler.util.js +91 -13
  34. package/cjs/utils/html-sanitizer.util.js +74 -0
  35. package/cjs/utils/index.js +2 -0
  36. package/cjs/utils/query-helpers.util.js +53 -0
  37. package/classes/api-controller.class.d.ts +5 -5
  38. package/classes/api-service.class.d.ts +5 -5
  39. package/classes/index.d.ts +1 -0
  40. package/classes/request-scoped-api.service.d.ts +3 -2
  41. package/constants/index.d.ts +1 -0
  42. package/constants/permissions.d.ts +167 -0
  43. package/decorators/index.d.ts +1 -0
  44. package/decorators/sanitize-html.decorator.d.ts +2 -0
  45. package/dtos/filter-and-pagination.dto.d.ts +0 -2
  46. package/dtos/response-payload.dto.d.ts +0 -7
  47. package/fesm/classes/api-controller.class.js +10 -93
  48. package/fesm/classes/api-service.class.js +5 -46
  49. package/fesm/classes/index.js +2 -0
  50. package/fesm/classes/request-scoped-api.service.js +4 -53
  51. package/fesm/classes/winston.logger.class.js +6 -18
  52. package/fesm/constants/index.js +16 -29
  53. package/fesm/constants/permissions.js +121 -0
  54. package/fesm/decorators/api-response.decorator.js +1 -1
  55. package/fesm/decorators/index.js +1 -0
  56. package/fesm/decorators/sanitize-html.decorator.js +45 -0
  57. package/fesm/dtos/filter-and-pagination.dto.js +26 -47
  58. package/fesm/dtos/pagination.dto.js +4 -8
  59. package/fesm/dtos/response-payload.dto.js +39 -142
  60. package/fesm/entities/identity.js +4 -4
  61. package/fesm/entities/user-root.js +13 -14
  62. package/fesm/guards/permission.guard.js +39 -94
  63. package/fesm/interceptors/index.js +1 -0
  64. package/fesm/interceptors/set-create-by-on-body.interceptor.js +4 -30
  65. package/fesm/interceptors/set-delete-by-on-body.interceptor.js +4 -30
  66. package/fesm/interceptors/set-update-by-on-body.interceptor.js +4 -30
  67. package/fesm/interceptors/set-user-field-on-body.interceptor.js +36 -0
  68. package/fesm/interceptors/slug.interceptor.js +31 -10
  69. package/fesm/interfaces/datasource.interface.js +20 -0
  70. package/fesm/interfaces/index.js +2 -1
  71. package/fesm/interfaces/logged-user-info.interface.js +1 -2
  72. package/fesm/interfaces/module-config.interface.js +5 -0
  73. package/fesm/interfaces/permission.interface.js +0 -12
  74. package/fesm/middlewares/logger.middleware.js +2 -6
  75. package/fesm/modules/cache/cache.module.js +2 -2
  76. package/fesm/modules/datasource/multi-tenant-datasource.service.js +31 -111
  77. package/fesm/modules/utils/utils.service.js +50 -143
  78. package/fesm/utils/error-handler.util.js +93 -14
  79. package/fesm/utils/html-sanitizer.util.js +82 -0
  80. package/fesm/utils/index.js +2 -0
  81. package/fesm/utils/query-helpers.util.js +78 -0
  82. package/interceptors/index.d.ts +1 -0
  83. package/interceptors/set-create-by-on-body.interceptor.d.ts +1 -5
  84. package/interceptors/set-delete-by-on-body.interceptor.d.ts +1 -5
  85. package/interceptors/set-update-by-on-body.interceptor.d.ts +1 -5
  86. package/interceptors/set-user-field-on-body.interceptor.d.ts +2 -0
  87. package/interceptors/slug.interceptor.d.ts +2 -1
  88. package/interfaces/api.interface.d.ts +2 -2
  89. package/interfaces/datasource.interface.d.ts +5 -0
  90. package/interfaces/identity.interface.d.ts +4 -4
  91. package/interfaces/index.d.ts +2 -1
  92. package/interfaces/module-config.interface.d.ts +6 -0
  93. package/interfaces/permission.interface.d.ts +0 -1
  94. package/modules/utils/utils.service.d.ts +10 -4
  95. package/package.json +4 -4
  96. package/utils/error-handler.util.d.ts +23 -13
  97. package/utils/html-sanitizer.util.d.ts +3 -0
  98. package/utils/index.d.ts +2 -0
  99. package/utils/query-helpers.util.d.ts +16 -0
  100. package/cjs/interfaces/base-query.interface.js +0 -6
  101. package/fesm/interfaces/base-query.interface.js +0 -3
  102. package/interfaces/base-query.interface.d.ts +0 -7
package/README.md CHANGED
@@ -32,8 +32,8 @@ This comprehensive guide covers the shared package - the shared NestJS infrastru
32
32
  - **Generic CRUD** - Standardized API controller and service patterns
33
33
  - **Permission System** - Role and permission-based access control
34
34
  - **Caching** - In-memory + Redis hybrid caching
35
- - **Request Correlation** - AsyncLocalStorage-based request tracking (NEW)
36
- - **Middleware** - Logging, correlation, and performance monitoring (NEW)
35
+ - **Request Correlation** - AsyncLocalStorage-based request tracking
36
+ - **Middleware** - Logging, correlation, and performance monitoring
37
37
  - **Interceptors** - Response metadata, idempotency, auto field setting
38
38
  - **Multi-Tenancy** - Dynamic database connection management
39
39
  - **Error Handling** - Centralized error handling utilities
@@ -121,7 +121,6 @@ nestjs-shared/
121
121
  │ │
122
122
  │ ├── interfaces/ # TypeScript interfaces
123
123
  │ │ ├── api.interface.ts
124
- │ │ ├── base-query.interface.ts
125
124
  │ │ ├── identity.interface.ts
126
125
  │ │ ├── logged-user-info.interface.ts
127
126
  │ │ ├── logger.interface.ts
@@ -298,7 +297,7 @@ override async getExtraManipulateQuery(
298
297
 
299
298
  ## ApiController - Generic CRUD Controller
300
299
 
301
- The `createApiController` factory creates standardized REST API controllers.
300
+ The `createApiController` factory creates standardized POST-only RPC controllers.
302
301
 
303
302
  ### Basic Usage
304
303
 
@@ -462,23 +461,52 @@ export class ReportsController {
462
461
  }
463
462
  ```
464
463
 
465
- ### @RequireAllPermissions
464
+ ### @RequirePermissionCondition
466
465
 
467
- Require all listed permissions:
466
+ Build complex permission conditions with nested AND/OR logic:
468
467
 
469
468
  ```typescript
470
- import { RequireAllPermissions } from '@flusys/nestjs-shared/decorators';
469
+ import { RequirePermissionCondition } from '@flusys/nestjs-shared/decorators';
471
470
 
472
471
  @Controller('sensitive')
473
472
  export class SensitiveController {
474
- @RequireAllPermissions('admin.access', 'security.clearance')
475
- @Get()
476
- getSensitiveData() {
477
- // Requires BOTH permissions
478
- }
473
+ // Simple: User needs 'admin' OR 'manager'
474
+ @RequirePermissionCondition({
475
+ operator: 'or',
476
+ permissions: ['admin', 'manager'],
477
+ })
478
+ @Get('simple')
479
+ getSimpleData() {}
480
+
481
+ // Complex: User needs 'users.read' AND ('admin' OR 'manager')
482
+ @RequirePermissionCondition({
483
+ operator: 'and',
484
+ permissions: ['users.read'],
485
+ children: [
486
+ { operator: 'or', permissions: ['admin', 'manager'] }
487
+ ]
488
+ })
489
+ @Get('complex')
490
+ getComplexData() {}
491
+
492
+ // Very complex: (A AND B) OR (C AND D)
493
+ @RequirePermissionCondition({
494
+ operator: 'or',
495
+ children: [
496
+ { operator: 'and', permissions: ['finance.read', 'finance.write'] },
497
+ { operator: 'and', permissions: ['admin.full', 'reports.access'] }
498
+ ]
499
+ })
500
+ @Get('very-complex')
501
+ getVeryComplexData() {}
479
502
  }
480
503
  ```
481
504
 
505
+ **Note:** For simple "require ALL permissions" use case, use `@RequirePermission` which defaults to AND logic:
506
+ ```typescript
507
+ @RequirePermission('admin.access', 'security.clearance') // User needs BOTH
508
+ ```
509
+
482
510
  ---
483
511
 
484
512
  ## Guards
@@ -487,106 +515,62 @@ The shared package provides two guards for authentication and authorization:
487
515
 
488
516
  ### JwtAuthGuard
489
517
 
490
- **Location:** `@flusys/nestjs-shared/guards/jwt-auth.guard.ts`
491
-
492
- The `JwtAuthGuard` validates JWT tokens for protected routes. It extends Passport's `AuthGuard('jwt')` and respects the `@Public()` decorator.
493
-
494
- **Why in shared module:**
495
- - All feature modules (auth, iam, storage) need JWT authentication
496
- - Keeps feature modules independent (no cross-dependencies)
497
- - JwtStrategy registration remains in auth module
498
- - This guard just validates tokens using the registered strategy
499
-
500
- **Usage:**
518
+ Validates JWT tokens for protected routes. Extends Passport's `AuthGuard('jwt')` and respects `@Public()` decorator.
501
519
 
502
520
  ```typescript
503
521
  import { JwtAuthGuard } from '@flusys/nestjs-shared/guards';
504
- import { UseGuards } from '@nestjs/common';
505
522
 
506
- // Protect entire controller
523
+ // Apply globally
524
+ @Module({
525
+ providers: [{ provide: APP_GUARD, useClass: JwtAuthGuard }],
526
+ })
527
+ export class AppModule {}
528
+
529
+ // Or per controller
507
530
  @Controller('users')
508
531
  @UseGuards(JwtAuthGuard)
509
532
  export class UserController {
510
- // All routes protected
511
-
512
533
  @Post('login')
513
- @Public() // Skip JWT check for this route
534
+ @Public() // Skip JWT check
514
535
  async login() { }
515
536
  }
516
-
517
- // Or apply globally
518
- @Module({
519
- providers: [
520
- {
521
- provide: APP_GUARD,
522
- useClass: JwtAuthGuard,
523
- },
524
- ],
525
- })
526
- export class AppModule {}
527
537
  ```
528
538
 
529
- **Features:**
530
- - Validates JWT tokens from `Authorization: Bearer <token>` header
531
- - Respects `@Public()` decorator to skip authentication
532
- - Throws `UnauthorizedException` for invalid/expired tokens
533
- - Works with JwtStrategy registered in auth module
534
-
535
- **Note:** The JwtStrategy (which registers the JWT validation logic with Passport) is still in the auth module at `@flusys/nestjs-auth/strategies/jwt.strategy.ts`. This guard uses that registered strategy.
536
-
537
539
  ### PermissionGuard
538
540
 
539
- **Location:** `@flusys/nestjs-shared/guards/permission.guard.ts`
540
-
541
- The `PermissionGuard` handles permission-based access control.
541
+ Checks user permissions from cache with AND/OR/nested logic support.
542
542
 
543
543
  ```typescript
544
- import { Module } from '@nestjs/common';
545
- import { APP_GUARD } from '@nestjs/core';
546
544
  import { PermissionGuard } from '@flusys/nestjs-shared/guards';
547
545
 
546
+ // Apply globally
548
547
  @Module({
549
- providers: [
550
- {
551
- provide: APP_GUARD,
552
- useClass: PermissionGuard,
553
- },
554
- ],
548
+ providers: [{ provide: APP_GUARD, useClass: PermissionGuard }],
555
549
  })
556
550
  export class AppModule {}
557
551
  ```
558
552
 
559
- ### Permission Cache Key Format
553
+ **Cache Key Formats:**
560
554
 
561
555
  ```typescript
562
556
  // Without company feature
563
557
  `permissions:user:{userId}`
564
558
 
565
- // With company feature (includes branchId for DIRECT permissions)
559
+ // With company feature
566
560
  `permissions:company:{companyId}:branch:{branchId}:user:{userId}`
567
561
  ```
568
562
 
569
- ### Permission Guard Configuration
570
-
571
- ```typescript
572
- // Configure via PermissionModule options
573
- PermissionModule.forRoot({
574
- // Cache TTL in seconds
575
- cacheTtl: 3600,
576
-
577
- // Permission check mode
578
- mode: 'RBAC' | 'DIRECT' | 'FULL',
579
-
580
- // Custom permission resolver
581
- resolver: CustomPermissionResolver,
582
- });
583
- ```
563
+ **Features:**
564
+ - Supports AND/OR operators via `@RequirePermission` and `@RequireAnyPermission`
565
+ - Nested conditions via `@RequirePermissionCondition`
566
+ - Wildcard support (`*` and `*.suffix`)
567
+ - Company/branch scoped permissions
584
568
 
585
569
  ---
586
570
 
587
571
  ## Middleware
588
572
 
589
- ### LoggerMiddleware (NEW - 2026-01-12, Enhanced - 2026-01-14)
573
+ ### LoggerMiddleware
590
574
 
591
575
  **Location:** `@flusys/nestjs-shared/middlewares/logger.middleware.ts`
592
576
 
@@ -877,7 +861,7 @@ export class PostController {
877
861
 
878
862
  ### Slug Interceptor
879
863
 
880
- Auto-generate slugs from name field:
864
+ Auto-generate slugs from name field using UtilsService (injected via DI):
881
865
 
882
866
  ```typescript
883
867
  import { Slug } from '@flusys/nestjs-shared/interceptors';
@@ -893,6 +877,8 @@ export class ProductController {
893
877
  }
894
878
  ```
895
879
 
880
+ **Note:** Requires `UtilsModule` to be imported in your module for dependency injection.
881
+
896
882
  ---
897
883
 
898
884
  ## Caching System
@@ -976,7 +962,7 @@ export class AppModule {}
976
962
 
977
963
  ## Multi-Tenant DataSource
978
964
 
979
- Dynamic database connection management for multi-tenant applications.
965
+ Dynamic database connection management with connection pooling and request-scoped tenant resolution.
980
966
 
981
967
  ### Setup
982
968
 
@@ -986,7 +972,7 @@ import { DataSourceModule } from '@flusys/nestjs-shared/modules';
986
972
  @Module({
987
973
  imports: [
988
974
  DataSourceModule.forRoot({
989
- // Default database config (used as template)
975
+ bootstrapAppConfig: { databaseMode: 'multi-tenant' },
990
976
  defaultDatabaseConfig: {
991
977
  type: 'mysql',
992
978
  host: 'localhost',
@@ -994,11 +980,9 @@ import { DataSourceModule } from '@flusys/nestjs-shared/modules';
994
980
  username: 'root',
995
981
  password: 'password',
996
982
  },
997
-
998
- // Tenant configurations
999
983
  tenants: [
1000
- { id: 'tenant1', database: 'tenant1_db', isActive: true },
1001
- { id: 'tenant2', database: 'tenant2_db', isActive: true },
984
+ { id: 'tenant1', database: 'tenant1_db' },
985
+ { id: 'tenant2', database: 'tenant2_db' },
1002
986
  ],
1003
987
  }),
1004
988
  ],
@@ -1006,41 +990,35 @@ import { DataSourceModule } from '@flusys/nestjs-shared/modules';
1006
990
  export class AppModule {}
1007
991
  ```
1008
992
 
1009
- ### Usage with Tenant Header
993
+ ### Key Methods
1010
994
 
1011
- ```typescript
1012
- // Client sends X-Tenant-ID header
1013
- // DataSource automatically switches to tenant's database
995
+ | Method | Description |
996
+ |--------|-------------|
997
+ | `getDataSource()` | Get DataSource for current tenant (from header) |
998
+ | `getDataSourceForTenant(id)` | Get DataSource for specific tenant |
999
+ | `getRepository(entity)` | Get repository for current tenant |
1000
+ | `withTenant(id, callback)` | Execute callback with specific tenant DataSource |
1001
+ | `forAllTenants(callback)` | Execute callback for all active tenants |
1002
+ | `registerTenant(config)` | Register new tenant at runtime |
1003
+ | `removeTenant(id)` | Remove tenant and close connection |
1004
+
1005
+ ### Usage
1014
1006
 
1007
+ ```typescript
1015
1008
  @Injectable()
1016
1009
  export class TenantService {
1017
- constructor(
1018
- @Inject('DATASOURCE_PROVIDER')
1019
- private dataSourceProvider: MultiTenantDataSourceService,
1020
- ) {}
1010
+ constructor(private dataSource: MultiTenantDataSourceService) {}
1021
1011
 
1022
- async getConnection(tenantId: string) {
1023
- return this.dataSourceProvider.getConnection(tenantId);
1012
+ async getUsers() {
1013
+ // Auto-resolves tenant from X-Tenant-ID header
1014
+ const repo = await this.dataSource.getRepository(User);
1015
+ return repo.find();
1024
1016
  }
1025
- }
1026
- ```
1027
1017
 
1028
- ### Tenant Resolution
1029
-
1030
- ```typescript
1031
- // Default: Resolved from X-Tenant-ID header
1032
- @Controller('users')
1033
- export class UserController {
1034
- // Automatically uses tenant-specific database
1035
- }
1036
-
1037
- // Manual tenant specification
1038
- @Injectable()
1039
- export class CrossTenantService {
1040
- async copyData(fromTenant: string, toTenant: string) {
1041
- const fromConn = await this.dataSourceProvider.getConnection(fromTenant);
1042
- const toConn = await this.dataSourceProvider.getConnection(toTenant);
1043
- // ...
1018
+ async crossTenantCopy(fromId: string, toId: string) {
1019
+ const fromDs = await this.dataSource.getDataSourceForTenant(fromId);
1020
+ const toDs = await this.dataSource.getDataSourceForTenant(toId);
1021
+ // Copy data between tenants
1044
1022
  }
1045
1023
  }
1046
1024
  ```
@@ -1063,18 +1041,15 @@ import { FilterAndPaginationDto } from '@flusys/nestjs-shared/dtos';
1063
1041
  "category": "electronics"
1064
1042
  },
1065
1043
  "pagination": {
1066
- "page": 1,
1067
- "limit": 10
1044
+ "currentPage": 0,
1045
+ "pageSize": 10
1068
1046
  },
1069
1047
  "sort": {
1070
- "field": "createdAt",
1071
- "order": "DESC"
1072
- },
1073
- "search": {
1074
- "fields": ["name", "description"],
1075
- "value": "laptop"
1048
+ "createdAt": "DESC",
1049
+ "name": "ASC"
1076
1050
  },
1077
- "select": ["id", "name", "price"]
1051
+ "select": ["id", "name", "price"],
1052
+ "withDeleted": false
1078
1053
  }
1079
1054
  ```
1080
1055
 
@@ -1235,11 +1210,12 @@ import {
1235
1210
  Public,
1236
1211
  RequirePermission,
1237
1212
  RequireAnyPermission,
1238
- RequireAllPermissions,
1213
+ RequirePermissionCondition,
1239
1214
  } from '@flusys/nestjs-shared/decorators';
1240
1215
 
1241
1216
  // Guards
1242
1217
  import {
1218
+ JwtAuthGuard,
1243
1219
  PermissionGuard,
1244
1220
  } from '@flusys/nestjs-shared/guards';
1245
1221
 
@@ -1375,25 +1351,17 @@ async getPermissions(userId: string) {
1375
1351
 
1376
1352
  The `@flusys/nestjs-shared` package provides:
1377
1353
 
1378
- **Generic CRUD** - Consistent API patterns
1379
- **Permission System** - Flexible access control
1380
- **Caching** - High-performance data access
1381
- **Multi-Tenancy** - Database isolation
1382
- **Request Correlation** - AsyncLocalStorage-based tracking (NEW)
1383
- **Middleware** - Logging and performance monitoring (NEW)
1384
- **Interceptors** - Request/response processing
1385
- **Error Handling** - Centralized error management
1354
+ - **Generic CRUD** - Consistent API patterns
1355
+ - **Permission System** - Flexible access control
1356
+ - **Caching** - High-performance data access
1357
+ - **Multi-Tenancy** - Database isolation
1358
+ - **Request Correlation** - AsyncLocalStorage-based tracking
1359
+ - **Middleware** - Logging and performance monitoring
1360
+ - **Interceptors** - Request/response processing
1361
+ - **Error Handling** - Centralized error management
1386
1362
 
1387
1363
  This is the shared infrastructure layer used by all other Flusys packages.
1388
1364
 
1389
1365
  ---
1390
1366
 
1391
- **Last Updated:** 2026-02-07
1392
- **Recent Improvements:**
1393
- - 2026-02-07: Documentation cleanup - removed outdated JWT configuration section, updated package architecture to match actual structure
1394
- - 2026-01-14: Enhanced `LoggerMiddleware` with complete request/response details (URL, path, query, headers, user context)
1395
- - 2026-01-14: Added multiple response hooks (send, json, end) for reliable response capture
1396
- - 2026-01-14: Improved error logging with stack traces
1397
- - 2026-01-12: Added `LoggerMiddleware` for request correlation and structured logging
1398
- - 2026-01-12: Added AsyncLocalStorage-based request context (requestId, tenantId, userId, companyId)
1399
- - 2026-01-13: Removed JWT config/constants (moved to auth-specific packages)
1367
+ **Last Updated:** 2026-02-18
@@ -8,10 +8,10 @@ Object.defineProperty(exports, "createApiController", {
8
8
  return createApiController;
9
9
  }
10
10
  });
11
- const _decorators = require("@flusys/nestjs-shared/decorators");
12
- const _dtos = require("@flusys/nestjs-shared/dtos");
13
- const _guards = require("@flusys/nestjs-shared/guards");
14
- const _interceptors = require("@flusys/nestjs-shared/interceptors");
11
+ const _decorators = require("../decorators");
12
+ const _dtos = require("../dtos");
13
+ const _guards = require("../guards");
14
+ const _interceptors = require("../interceptors");
15
15
  const _common = require("@nestjs/common");
16
16
  const _swagger = require("@nestjs/swagger");
17
17
  const _classtransformer = require("class-transformer");
@@ -104,8 +104,10 @@ function createApiController(createDtoClass, updateDtoClass, responseDtoClass, o
104
104
  // 2. It's an object with 'level' property but no endpoint keys
105
105
  const isGlobalSecurity = typeof securityConfig === 'string' || securityConfig && typeof securityConfig === 'object' && 'level' in securityConfig && !endpointKeys.some((key)=>key in securityConfig);
106
106
  // Normalize security config for each endpoint
107
+ // IMPORTANT: When per-endpoint security is specified, default to 'jwt' for unconfigured endpoints
108
+ // to prevent accidentally exposing endpoints without authentication
107
109
  const defaultSecurity = isGlobalSecurity ? normalizeSecurity(securityConfig) : {
108
- level: 'public'
110
+ level: 'jwt'
109
111
  };
110
112
  const security = {
111
113
  insert: isGlobalSecurity ? defaultSecurity : normalizeSecurity(securityConfig?.insert),
@@ -117,9 +119,6 @@ function createApiController(createDtoClass, updateDtoClass, responseDtoClass, o
117
119
  delete: isGlobalSecurity ? defaultSecurity : normalizeSecurity(securityConfig?.delete)
118
120
  };
119
121
  let ApiController = class ApiController {
120
- // =========================================================================
121
- // INSERT (Single Item) - With Idempotency Support
122
- // =========================================================================
123
122
  async insert(addDto, user) {
124
123
  const entity = await this.service.insert(addDto, user);
125
124
  const data = (0, _classtransformer.plainToInstance)(responseDtoClass, entity);
@@ -129,9 +128,6 @@ function createApiController(createDtoClass, updateDtoClass, responseDtoClass, o
129
128
  data
130
129
  };
131
130
  }
132
- // =========================================================================
133
- // INSERT MANY (Bulk) - With Idempotency Support
134
- // =========================================================================
135
131
  async insertMany(addDto, user) {
136
132
  const entities = await this.service.insertMany(addDto, user);
137
133
  const data = entities.map((item)=>(0, _classtransformer.plainToInstance)(responseDtoClass, item));
@@ -146,9 +142,6 @@ function createApiController(createDtoClass, updateDtoClass, responseDtoClass, o
146
142
  }
147
143
  };
148
144
  }
149
- // =========================================================================
150
- // GET BY ID (Single Item)
151
- // =========================================================================
152
145
  async getById(id, body, user) {
153
146
  const entity = await this.service.findById(id, user, body?.select);
154
147
  const data = (0, _classtransformer.plainToInstance)(responseDtoClass, entity);
@@ -158,9 +151,6 @@ function createApiController(createDtoClass, updateDtoClass, responseDtoClass, o
158
151
  data
159
152
  };
160
153
  }
161
- // =========================================================================
162
- // UPDATE (Single Item)
163
- // =========================================================================
164
154
  async update(updateDto, user) {
165
155
  const entity = await this.service.update(updateDto, user);
166
156
  const data = (0, _classtransformer.plainToInstance)(responseDtoClass, entity);
@@ -170,9 +160,6 @@ function createApiController(createDtoClass, updateDtoClass, responseDtoClass, o
170
160
  data
171
161
  };
172
162
  }
173
- // =========================================================================
174
- // UPDATE MANY (Bulk)
175
- // =========================================================================
176
163
  async updateMany(updateDtos, user) {
177
164
  const entities = await this.service.updateMany(updateDtos, user);
178
165
  const data = (0, _classtransformer.plainToInstance)(responseDtoClass, entities);
@@ -187,9 +174,6 @@ function createApiController(createDtoClass, updateDtoClass, responseDtoClass, o
187
174
  }
188
175
  };
189
176
  }
190
- // =========================================================================
191
- // GET ALL (Paginated List)
192
- // =========================================================================
193
177
  async getAll(filterAndPaginationDto, user, search) {
194
178
  const result = await this.service.getAll(search ?? '', filterAndPaginationDto, user);
195
179
  const data = (0, _classtransformer.plainToInstance)(responseDtoClass, result.data);
@@ -210,9 +194,6 @@ function createApiController(createDtoClass, updateDtoClass, responseDtoClass, o
210
194
  }
211
195
  };
212
196
  }
213
- // =========================================================================
214
- // DELETE (Soft/Restore/Permanent)
215
- // =========================================================================
216
197
  async delete(deleteDto, user) {
217
198
  await this.service.delete(deleteDto, user);
218
199
  const count = Array.isArray(deleteDto.id) ? deleteDto.id.length : 1;
@@ -368,16 +349,7 @@ function createApiController(createDtoClass, updateDtoClass, responseDtoClass, o
368
349
  (0, _common.HttpCode)(_common.HttpStatus.OK),
369
350
  (0, _swagger.ApiOperation)({
370
351
  summary: 'Get all items with filters and pagination',
371
- description: `
372
- Retrieves items with support for:
373
- - **filter**: Apply field-based filters (e.g., \`{ "isActive": true }\`)
374
- - **pagination**: Control page and page size
375
- - **sort**: Order by any field (e.g., \`{ "createdAt": "DESC" }\`)
376
- - **select**: Choose specific fields to return
377
- - **withDeleted**: Include soft-deleted items
378
- - **extraKey**: Include additional relations
379
- - **q** (query param): Global text search
380
- `
352
+ description: 'Supports filter, pagination, sort, select, withDeleted, and q (search) params'
381
353
  }),
382
354
  (0, _swagger.ApiQuery)({
383
355
  name: 'q',
@@ -406,15 +378,7 @@ Retrieves items with support for:
406
378
  (0, _common.HttpCode)(_common.HttpStatus.OK),
407
379
  (0, _swagger.ApiOperation)({
408
380
  summary: 'Delete, restore, or permanently remove items',
409
- description: `
410
- Performs one of three actions:
411
-
412
- - **"delete"** (soft delete): Marks items as deleted but keeps in database
413
- - **"restore"**: Reverts soft-deleted items to active
414
- - **"permanent"**: Completely removes items from database
415
-
416
- Supports single ID or array of IDs for batch operations.
417
- `
381
+ description: 'Types: delete (soft), restore, permanent. Supports batch IDs.'
418
382
  }),
419
383
  (0, _swagger.ApiResponse)({
420
384
  status: 200,
@@ -25,9 +25,6 @@ function _define_property(obj, key, value) {
25
25
  return obj;
26
26
  }
27
27
  let ApiService = class ApiService {
28
- // ---------------------------------------------------------------------
29
- // INSERT SINGLE ENTITY
30
- // ---------------------------------------------------------------------
31
28
  async insert(dto, user) {
32
29
  await this.ensureRepositoryInitialized();
33
30
  const qr = this.repository.manager.connection.createQueryRunner();
@@ -54,9 +51,6 @@ let ApiService = class ApiService {
54
51
  await qr.release();
55
52
  }
56
53
  }
57
- // ---------------------------------------------------------------------
58
- // INSERT MULTIPLE ENTITIES
59
- // ---------------------------------------------------------------------
60
54
  async insertMany(dtos, user) {
61
55
  await this.ensureRepositoryInitialized();
62
56
  const qr = this.repository.manager.connection.createQueryRunner();
@@ -87,9 +81,6 @@ let ApiService = class ApiService {
87
81
  await qr.release();
88
82
  }
89
83
  }
90
- // ---------------------------------------------------------------------
91
- // UPDATE SINGLE ENTITY
92
- // ---------------------------------------------------------------------
93
84
  async update(dto, user) {
94
85
  await this.ensureRepositoryInitialized();
95
86
  const qr = this.repository.manager.connection.createQueryRunner();
@@ -116,9 +107,6 @@ let ApiService = class ApiService {
116
107
  await qr.release();
117
108
  }
118
109
  }
119
- // ---------------------------------------------------------------------
120
- // UPDATE MULTIPLE ENTITIES
121
- // ---------------------------------------------------------------------
122
110
  async updateMany(dtos, user) {
123
111
  await this.ensureRepositoryInitialized();
124
112
  const qr = this.repository.manager.connection.createQueryRunner();
@@ -149,9 +137,6 @@ let ApiService = class ApiService {
149
137
  await qr.release();
150
138
  }
151
139
  }
152
- // ---------------------------------------------------------------------
153
- // FIND BY IDS
154
- // ---------------------------------------------------------------------
155
140
  async findByIds(ids, user) {
156
141
  await this.ensureRepositoryInitialized();
157
142
  try {
@@ -176,9 +161,6 @@ let ApiService = class ApiService {
176
161
  this.handleError(error, 'findByIds');
177
162
  }
178
163
  }
179
- // ---------------------------------------------------------------------
180
- // FIND BY ID
181
- // ---------------------------------------------------------------------
182
164
  async findById(id, user, select) {
183
165
  await this.ensureRepositoryInitialized();
184
166
  try {
@@ -217,9 +199,6 @@ let ApiService = class ApiService {
217
199
  this.handleError(error, 'findById');
218
200
  }
219
201
  }
220
- // ---------------------------------------------------------------------
221
- // GET ALL (WITH FILTER, SORT, SEARCH, PAGINATION & CACHING)
222
- // ---------------------------------------------------------------------
223
202
  async getAll(search, filterAndPaginationDto, user) {
224
203
  await this.ensureRepositoryInitialized();
225
204
  try {
@@ -318,9 +297,6 @@ let ApiService = class ApiService {
318
297
  this.handleError(error, 'getAll');
319
298
  }
320
299
  }
321
- // ---------------------------------------------------------------------
322
- // DELETE / RESTORE / HARD DELETE
323
- // ---------------------------------------------------------------------
324
300
  async delete(option, user) {
325
301
  await this.ensureRepositoryInitialized();
326
302
  const queryRunner = this.repository.manager.connection.createQueryRunner();
@@ -363,9 +339,7 @@ let ApiService = class ApiService {
363
339
  await queryRunner.release();
364
340
  }
365
341
  }
366
- // ---------------------------------------------------------------------
367
- // CACHING HELPERS
368
- // ---------------------------------------------------------------------
342
+ // Caching
369
343
  async clearCacheForAll() {
370
344
  await this.utilsService.clearCache(this.entityName, this.cacheManager);
371
345
  }
@@ -380,17 +354,8 @@ let ApiService = class ApiService {
380
354
  });
381
355
  _errorhandlerutil.ErrorHandler.rethrowError(error);
382
356
  }
383
- // ---------------------------------------------------------------------
384
- // HOOKS / HELPERS (OVERRIDE IN CHILD CLASSES)
385
- // ---------------------------------------------------------------------
386
- /**
387
- * Hook called before ANY repository access
388
- * CRITICAL: Override this in REQUEST-scoped services that use lazy repository initialization
389
- * Example use case: DataSource Provider pattern where repository is set dynamically
390
- */ async ensureRepositoryInitialized() {
391
- // Default: no-op - repository is already initialized in constructor
392
- // Override in child classes that need lazy initialization
393
- }
357
+ // Hooks (override in child classes)
358
+ async ensureRepositoryInitialized() {}
394
359
  async beforeInsertOperation(_dto, _user, _queryRunner) {}
395
360
  async afterInsertOperation(_entity, _user, _queryRunner) {}
396
361
  async beforeUpdateOperation(_dto, _user, _queryRunner) {}
@@ -439,9 +404,7 @@ let ApiService = class ApiService {
439
404
  isRaw: false
440
405
  };
441
406
  }
442
- // ---------------------------------------------------------------------
443
- // DTO <-> ENTITY CONVERSION
444
- // ---------------------------------------------------------------------
407
+ // DTO conversion
445
408
  async convertRequestDtoToEntity(dto, user) {
446
409
  return Array.isArray(dto) ? await this.convertArrayDtoToEntities(dto, user) : [
447
410
  await this.convertSingleDtoToEntity(dto, user)
@@ -8,6 +8,7 @@ _export_star(require("./request-scoped-api.service"), exports);
8
8
  _export_star(require("./hybrid-cache.class"), exports);
9
9
  _export_star(require("./winston-logger-adapter.class"), exports);
10
10
  _export_star(require("./winston.logger.class"), exports);
11
+ _export_star(require("../constants/permissions"), exports);
11
12
  function _export_star(from, to) {
12
13
  Object.keys(from).forEach(function(k) {
13
14
  if (k !== "default" && !Object.prototype.hasOwnProperty.call(to, k)) {