@flusys/nestjs-shared 1.0.0-beta → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/README.md +501 -720
  2. package/cjs/classes/api-controller.class.js +9 -24
  3. package/cjs/classes/api-service.class.js +59 -92
  4. package/cjs/classes/index.js +1 -0
  5. package/cjs/classes/winston-logger-adapter.class.js +23 -40
  6. package/cjs/constants/index.js +14 -0
  7. package/cjs/constants/permissions.js +184 -0
  8. package/cjs/decorators/api-response.decorator.js +1 -1
  9. package/cjs/decorators/index.js +1 -0
  10. package/cjs/decorators/sanitize-html.decorator.js +36 -0
  11. package/cjs/dtos/delete.dto.js +10 -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 +0 -116
  15. package/cjs/entities/identity.js +4 -4
  16. package/cjs/entities/user-root.js +13 -14
  17. package/cjs/guards/permission.guard.js +51 -105
  18. package/cjs/interceptors/index.js +1 -3
  19. package/cjs/interceptors/set-user-field-on-body.interceptor.js +60 -0
  20. package/cjs/interceptors/slug.interceptor.js +30 -9
  21. package/cjs/interfaces/datasource.interface.js +4 -0
  22. package/cjs/interfaces/index.js +2 -1
  23. package/cjs/interfaces/module-config.interface.js +4 -0
  24. package/cjs/middlewares/logger.middleware.js +50 -89
  25. package/cjs/modules/cache/cache.module.js +3 -3
  26. package/cjs/modules/datasource/datasource.module.js +11 -14
  27. package/cjs/modules/datasource/multi-tenant-datasource.service.js +29 -113
  28. package/cjs/modules/utils/utils.service.js +40 -203
  29. package/cjs/utils/error-handler.util.js +35 -12
  30. package/cjs/utils/html-sanitizer.util.js +64 -0
  31. package/cjs/utils/index.js +4 -0
  32. package/cjs/utils/query-helpers.util.js +53 -0
  33. package/cjs/utils/request.util.js +70 -0
  34. package/cjs/utils/string.util.js +63 -0
  35. package/classes/api-controller.class.d.ts +5 -5
  36. package/classes/api-service.class.d.ts +7 -5
  37. package/classes/index.d.ts +1 -0
  38. package/classes/request-scoped-api.service.d.ts +3 -2
  39. package/classes/winston-logger-adapter.class.d.ts +2 -0
  40. package/constants/index.d.ts +1 -0
  41. package/constants/permissions.d.ts +179 -0
  42. package/decorators/index.d.ts +1 -0
  43. package/decorators/sanitize-html.decorator.d.ts +2 -0
  44. package/dtos/delete.dto.d.ts +1 -0
  45. package/dtos/filter-and-pagination.dto.d.ts +0 -2
  46. package/dtos/response-payload.dto.d.ts +0 -20
  47. package/fesm/classes/api-controller.class.js +9 -24
  48. package/fesm/classes/api-service.class.js +59 -92
  49. package/fesm/classes/index.js +2 -0
  50. package/fesm/classes/winston-logger-adapter.class.js +23 -40
  51. package/fesm/constants/index.js +2 -0
  52. package/fesm/constants/permissions.js +128 -0
  53. package/fesm/decorators/api-response.decorator.js +1 -1
  54. package/fesm/decorators/index.js +1 -0
  55. package/fesm/decorators/sanitize-html.decorator.js +45 -0
  56. package/fesm/dtos/delete.dto.js +12 -2
  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 +0 -107
  60. package/fesm/entities/identity.js +4 -4
  61. package/fesm/entities/user-root.js +13 -14
  62. package/fesm/guards/permission.guard.js +51 -105
  63. package/fesm/interceptors/index.js +1 -3
  64. package/fesm/interceptors/set-user-field-on-body.interceptor.js +39 -0
  65. package/fesm/interceptors/slug.interceptor.js +31 -10
  66. package/fesm/interfaces/datasource.interface.js +20 -0
  67. package/fesm/interfaces/index.js +2 -1
  68. package/fesm/interfaces/module-config.interface.js +5 -0
  69. package/fesm/middlewares/logger.middleware.js +50 -83
  70. package/fesm/modules/cache/cache.module.js +2 -2
  71. package/fesm/modules/datasource/datasource.module.js +11 -14
  72. package/fesm/modules/datasource/multi-tenant-datasource.service.js +29 -113
  73. package/fesm/modules/utils/utils.service.js +41 -204
  74. package/fesm/utils/error-handler.util.js +36 -13
  75. package/fesm/utils/html-sanitizer.util.js +69 -0
  76. package/fesm/utils/index.js +4 -0
  77. package/fesm/utils/query-helpers.util.js +78 -0
  78. package/fesm/utils/request.util.js +58 -0
  79. package/fesm/utils/string.util.js +71 -0
  80. package/guards/permission.guard.d.ts +2 -0
  81. package/interceptors/index.d.ts +1 -3
  82. package/interceptors/set-user-field-on-body.interceptor.d.ts +5 -0
  83. package/interceptors/slug.interceptor.d.ts +2 -1
  84. package/interfaces/api.interface.d.ts +2 -2
  85. package/interfaces/datasource.interface.d.ts +5 -0
  86. package/interfaces/identity.interface.d.ts +4 -4
  87. package/interfaces/index.d.ts +2 -1
  88. package/interfaces/logged-user-info.interface.d.ts +0 -2
  89. package/interfaces/module-config.interface.d.ts +6 -0
  90. package/interfaces/permission.interface.d.ts +0 -1
  91. package/middlewares/logger.middleware.d.ts +2 -2
  92. package/modules/datasource/datasource.module.d.ts +1 -0
  93. package/modules/datasource/multi-tenant-datasource.service.d.ts +0 -1
  94. package/modules/utils/utils.service.d.ts +4 -14
  95. package/package.json +4 -4
  96. package/utils/error-handler.util.d.ts +14 -19
  97. package/utils/html-sanitizer.util.d.ts +2 -0
  98. package/utils/index.d.ts +4 -0
  99. package/utils/query-helpers.util.d.ts +16 -0
  100. package/utils/request.util.d.ts +4 -0
  101. package/utils/string.util.d.ts +2 -0
  102. package/cjs/interceptors/set-create-by-on-body.interceptor.js +0 -40
  103. package/cjs/interceptors/set-delete-by-on-body.interceptor.js +0 -40
  104. package/cjs/interceptors/set-update-by-on-body.interceptor.js +0 -40
  105. package/cjs/interfaces/base-query.interface.js +0 -6
  106. package/fesm/interceptors/set-create-by-on-body.interceptor.js +0 -30
  107. package/fesm/interceptors/set-delete-by-on-body.interceptor.js +0 -30
  108. package/fesm/interceptors/set-update-by-on-body.interceptor.js +0 -30
  109. package/fesm/interfaces/base-query.interface.js +0 -3
  110. package/interceptors/set-create-by-on-body.interceptor.d.ts +0 -5
  111. package/interceptors/set-delete-by-on-body.interceptor.d.ts +0 -5
  112. package/interceptors/set-update-by-on-body.interceptor.d.ts +0 -5
  113. package/interfaces/base-query.interface.d.ts +0 -7
@@ -1,11 +1,11 @@
1
- import { DeleteDto, FilterAndPaginationDto } from '@flusys/nestjs-shared/dtos';
2
- import { Identity } from '@flusys/nestjs-shared/entities';
3
- import { ILoggedUserInfo, IService } from '@flusys/nestjs-shared/interfaces';
4
- import { UtilsService } from '@flusys/nestjs-shared/modules';
1
+ import { DeleteDto, FilterAndPaginationDto } from '../dtos';
2
+ import { Identity } from '../entities';
3
+ import { ILoggedUserInfo, IService } from '../interfaces';
4
+ import { UtilsService } from '../modules/utils/utils.service';
5
5
  import { Logger } from '@nestjs/common';
6
6
  import { QueryRunner, Repository, SelectQueryBuilder } from 'typeorm';
7
7
  import { HybridCache } from './hybrid-cache.class';
8
- export declare abstract class ApiService<CreateDtoT extends Record<string, unknown>, UpdateDtoT extends {
8
+ export declare abstract class ApiService<CreateDtoT extends object, UpdateDtoT extends {
9
9
  id: string;
10
10
  }, InterfaceT extends Identity, EntityT extends Identity, RepositoryT extends Repository<EntityT>> implements IService<CreateDtoT, UpdateDtoT, InterfaceT> {
11
11
  protected entityName: string;
@@ -30,6 +30,8 @@ export declare abstract class ApiService<CreateDtoT extends Record<string, unkno
30
30
  clearCacheForAll(): Promise<void>;
31
31
  clearCacheForId(entities: EntityT[]): Promise<void>;
32
32
  private handleError;
33
+ private ensureArray;
34
+ private executeInTransaction;
33
35
  protected ensureRepositoryInitialized(): Promise<void>;
34
36
  protected beforeInsertOperation(_dto: CreateDtoT | Array<CreateDtoT>, _user: ILoggedUserInfo | null, _queryRunner: QueryRunner): Promise<void>;
35
37
  protected afterInsertOperation(_entity: EntityT[], _user: ILoggedUserInfo | null, _queryRunner: QueryRunner): Promise<void>;
@@ -4,3 +4,4 @@ export * from './request-scoped-api.service';
4
4
  export * from './hybrid-cache.class';
5
5
  export * from './winston-logger-adapter.class';
6
6
  export * from './winston.logger.class';
7
+ export * from '../constants/permissions';
@@ -1,12 +1,13 @@
1
1
  import { DataSource, EntityTarget, Repository } from 'typeorm';
2
2
  import { Identity } from '../entities';
3
+ import { IDataSourceProvider } from '../interfaces';
3
4
  import { ApiService } from './api-service.class';
4
- export declare abstract class RequestScopedApiService<CreateDtoT extends Record<string, unknown>, UpdateDtoT extends {
5
+ export declare abstract class RequestScopedApiService<CreateDtoT extends object, UpdateDtoT extends {
5
6
  id: string;
6
7
  }, InterfaceT extends Identity, EntityT extends Identity, RepositoryT extends Repository<EntityT>> extends ApiService<CreateDtoT, UpdateDtoT, InterfaceT, EntityT, RepositoryT> {
7
8
  private repositoryInitialized;
8
9
  protected abstract resolveEntity(): EntityTarget<EntityT>;
9
- protected abstract getDataSourceProvider(): any;
10
+ protected abstract getDataSourceProvider(): IDataSourceProvider;
10
11
  protected ensureRepositoryInitialized(): Promise<void>;
11
12
  protected initializeAdditionalRepositories(entities: EntityTarget<any>[]): Promise<Repository<any>[]>;
12
13
  protected getDataSourceForService(): Promise<DataSource>;
@@ -3,6 +3,7 @@ import { ILogger } from '../interfaces/logger.interface';
3
3
  export declare class WinstonLoggerAdapter implements ILogger {
4
4
  private readonly context?;
5
5
  constructor(context?: string);
6
+ private buildLogMeta;
6
7
  log(message: string, context?: string, ...args: any[]): void;
7
8
  error(message: string, trace?: string, context?: string, ...args: any[]): void;
8
9
  warn(message: string, context?: string, ...args: any[]): void;
@@ -12,6 +13,7 @@ export declare class WinstonLoggerAdapter implements ILogger {
12
13
  export declare class NestLoggerAdapter implements ILogger {
13
14
  private readonly logger;
14
15
  constructor(logger: Logger);
16
+ private formatMessage;
15
17
  log(message: string, context?: string, ...args: any[]): void;
16
18
  error(message: string, trace?: string, context?: string, ...args: any[]): void;
17
19
  warn(message: string, context?: string, ...args: any[]): void;
@@ -8,3 +8,4 @@ export declare const REQUEST_ID_HEADER = "x-request-id";
8
8
  export declare const CLIENT_TYPE_HEADER = "x-client-type";
9
9
  export declare const PERMISSIONS_CACHE_PREFIX = "permissions";
10
10
  export declare const IDEMPOTENCY_CACHE_PREFIX = "idempotency";
11
+ export * from './permissions';
@@ -0,0 +1,179 @@
1
+ export declare const USER_PERMISSIONS: {
2
+ readonly CREATE: "user.create";
3
+ readonly READ: "user.read";
4
+ readonly UPDATE: "user.update";
5
+ readonly DELETE: "user.delete";
6
+ };
7
+ export declare const COMPANY_PERMISSIONS: {
8
+ readonly CREATE: "company.create";
9
+ readonly READ: "company.read";
10
+ readonly UPDATE: "company.update";
11
+ readonly DELETE: "company.delete";
12
+ };
13
+ export declare const BRANCH_PERMISSIONS: {
14
+ readonly CREATE: "branch.create";
15
+ readonly READ: "branch.read";
16
+ readonly UPDATE: "branch.update";
17
+ readonly DELETE: "branch.delete";
18
+ };
19
+ export declare const ACTION_PERMISSIONS: {
20
+ readonly CREATE: "action.create";
21
+ readonly READ: "action.read";
22
+ readonly UPDATE: "action.update";
23
+ readonly DELETE: "action.delete";
24
+ };
25
+ export declare const ROLE_PERMISSIONS: {
26
+ readonly CREATE: "role.create";
27
+ readonly READ: "role.read";
28
+ readonly UPDATE: "role.update";
29
+ readonly DELETE: "role.delete";
30
+ };
31
+ export declare const ROLE_ACTION_PERMISSIONS: {
32
+ readonly READ: "role-action.read";
33
+ readonly ASSIGN: "role-action.assign";
34
+ };
35
+ export declare const USER_ROLE_PERMISSIONS: {
36
+ readonly READ: "user-role.read";
37
+ readonly ASSIGN: "user-role.assign";
38
+ };
39
+ export declare const USER_ACTION_PERMISSIONS: {
40
+ readonly READ: "user-action.read";
41
+ readonly ASSIGN: "user-action.assign";
42
+ };
43
+ export declare const COMPANY_ACTION_PERMISSIONS: {
44
+ readonly READ: "company-action.read";
45
+ readonly ASSIGN: "company-action.assign";
46
+ };
47
+ export declare const FILE_PERMISSIONS: {
48
+ readonly CREATE: "file.create";
49
+ readonly READ: "file.read";
50
+ readonly UPDATE: "file.update";
51
+ readonly DELETE: "file.delete";
52
+ };
53
+ export declare const FOLDER_PERMISSIONS: {
54
+ readonly CREATE: "folder.create";
55
+ readonly READ: "folder.read";
56
+ readonly UPDATE: "folder.update";
57
+ readonly DELETE: "folder.delete";
58
+ };
59
+ export declare const STORAGE_CONFIG_PERMISSIONS: {
60
+ readonly CREATE: "storage-config.create";
61
+ readonly READ: "storage-config.read";
62
+ readonly UPDATE: "storage-config.update";
63
+ readonly DELETE: "storage-config.delete";
64
+ };
65
+ export declare const EMAIL_CONFIG_PERMISSIONS: {
66
+ readonly CREATE: "email-config.create";
67
+ readonly READ: "email-config.read";
68
+ readonly UPDATE: "email-config.update";
69
+ readonly DELETE: "email-config.delete";
70
+ };
71
+ export declare const EMAIL_TEMPLATE_PERMISSIONS: {
72
+ readonly CREATE: "email-template.create";
73
+ readonly READ: "email-template.read";
74
+ readonly UPDATE: "email-template.update";
75
+ readonly DELETE: "email-template.delete";
76
+ };
77
+ export declare const FORM_PERMISSIONS: {
78
+ readonly CREATE: "form.create";
79
+ readonly READ: "form.read";
80
+ readonly UPDATE: "form.update";
81
+ readonly DELETE: "form.delete";
82
+ };
83
+ export declare const FORM_RESULT_PERMISSIONS: {
84
+ readonly CREATE: "form-result.create";
85
+ readonly READ: "form-result.read";
86
+ readonly UPDATE: "form-result.update";
87
+ readonly DELETE: "form-result.delete";
88
+ };
89
+ export declare const PERMISSIONS: {
90
+ readonly USER: {
91
+ readonly CREATE: "user.create";
92
+ readonly READ: "user.read";
93
+ readonly UPDATE: "user.update";
94
+ readonly DELETE: "user.delete";
95
+ };
96
+ readonly COMPANY: {
97
+ readonly CREATE: "company.create";
98
+ readonly READ: "company.read";
99
+ readonly UPDATE: "company.update";
100
+ readonly DELETE: "company.delete";
101
+ };
102
+ readonly BRANCH: {
103
+ readonly CREATE: "branch.create";
104
+ readonly READ: "branch.read";
105
+ readonly UPDATE: "branch.update";
106
+ readonly DELETE: "branch.delete";
107
+ };
108
+ readonly ACTION: {
109
+ readonly CREATE: "action.create";
110
+ readonly READ: "action.read";
111
+ readonly UPDATE: "action.update";
112
+ readonly DELETE: "action.delete";
113
+ };
114
+ readonly ROLE: {
115
+ readonly CREATE: "role.create";
116
+ readonly READ: "role.read";
117
+ readonly UPDATE: "role.update";
118
+ readonly DELETE: "role.delete";
119
+ };
120
+ readonly ROLE_ACTION: {
121
+ readonly READ: "role-action.read";
122
+ readonly ASSIGN: "role-action.assign";
123
+ };
124
+ readonly USER_ROLE: {
125
+ readonly READ: "user-role.read";
126
+ readonly ASSIGN: "user-role.assign";
127
+ };
128
+ readonly USER_ACTION: {
129
+ readonly READ: "user-action.read";
130
+ readonly ASSIGN: "user-action.assign";
131
+ };
132
+ readonly COMPANY_ACTION: {
133
+ readonly READ: "company-action.read";
134
+ readonly ASSIGN: "company-action.assign";
135
+ };
136
+ readonly FILE: {
137
+ readonly CREATE: "file.create";
138
+ readonly READ: "file.read";
139
+ readonly UPDATE: "file.update";
140
+ readonly DELETE: "file.delete";
141
+ };
142
+ readonly FOLDER: {
143
+ readonly CREATE: "folder.create";
144
+ readonly READ: "folder.read";
145
+ readonly UPDATE: "folder.update";
146
+ readonly DELETE: "folder.delete";
147
+ };
148
+ readonly STORAGE_CONFIG: {
149
+ readonly CREATE: "storage-config.create";
150
+ readonly READ: "storage-config.read";
151
+ readonly UPDATE: "storage-config.update";
152
+ readonly DELETE: "storage-config.delete";
153
+ };
154
+ readonly EMAIL_CONFIG: {
155
+ readonly CREATE: "email-config.create";
156
+ readonly READ: "email-config.read";
157
+ readonly UPDATE: "email-config.update";
158
+ readonly DELETE: "email-config.delete";
159
+ };
160
+ readonly EMAIL_TEMPLATE: {
161
+ readonly CREATE: "email-template.create";
162
+ readonly READ: "email-template.read";
163
+ readonly UPDATE: "email-template.update";
164
+ readonly DELETE: "email-template.delete";
165
+ };
166
+ readonly FORM: {
167
+ readonly CREATE: "form.create";
168
+ readonly READ: "form.read";
169
+ readonly UPDATE: "form.update";
170
+ readonly DELETE: "form.delete";
171
+ };
172
+ readonly FORM_RESULT: {
173
+ readonly CREATE: "form-result.create";
174
+ readonly READ: "form-result.read";
175
+ readonly UPDATE: "form-result.update";
176
+ readonly DELETE: "form-result.delete";
177
+ };
178
+ };
179
+ export type PermissionCode = (typeof PERMISSIONS)[keyof typeof PERMISSIONS][keyof (typeof PERMISSIONS)[keyof typeof PERMISSIONS]];
@@ -2,3 +2,4 @@ export * from './api-response.decorator';
2
2
  export * from './current-user.decorator';
3
3
  export * from './public.decorator';
4
4
  export * from './require-permission.decorator';
5
+ export * from './sanitize-html.decorator';
@@ -0,0 +1,2 @@
1
+ export declare function SanitizeHtml(): PropertyDecorator;
2
+ export declare function SanitizeAndTrim(): PropertyDecorator;
@@ -1,4 +1,5 @@
1
1
  export declare class DeleteDto {
2
2
  id: string | string[];
3
3
  type: 'delete' | 'restore' | 'permanent';
4
+ deletedById?: string;
4
5
  }
@@ -5,9 +5,7 @@ export declare class FilterAndPaginationDto {
5
5
  sort?: Record<string, 'ASC' | 'DESC'>;
6
6
  select?: string[];
7
7
  withDeleted?: boolean;
8
- extraKey?: string[];
9
8
  }
10
9
  export declare class GetByIdBodyDto {
11
10
  select?: string[];
12
- extraKey?: string[];
13
11
  }
@@ -41,23 +41,3 @@ export declare class MessageResponseDto {
41
41
  message: string;
42
42
  _meta?: RequestMetaDto;
43
43
  }
44
- export declare class ValidationErrorDto {
45
- field: string;
46
- message: string;
47
- constraint?: string;
48
- }
49
- export declare class ErrorResponseDto {
50
- success: false;
51
- message: string;
52
- code?: string;
53
- errors?: ValidationErrorDto[];
54
- _meta?: RequestMetaDto;
55
- }
56
- export declare class ResponsePayloadDto<T> {
57
- success: boolean;
58
- message: string;
59
- data?: T;
60
- meta?: PaginationMetaDto | BulkMetaDto;
61
- _meta?: RequestMetaDto;
62
- }
63
- export type ApiResponse<T> = SingleResponseDto<T> | ListResponseDto<T> | BulkResponseDto<T> | MessageResponseDto | ErrorResponseDto;
@@ -25,10 +25,10 @@ function _ts_param(paramIndex, decorator) {
25
25
  decorator(target, key, paramIndex);
26
26
  };
27
27
  }
28
- import { CurrentUser, Public, RequirePermission, RequireAnyPermission, RequirePermissionCondition } from '@flusys/nestjs-shared/decorators';
29
- import { DeleteDto, FilterAndPaginationDto, GetByIdBodyDto } from '@flusys/nestjs-shared/dtos';
30
- import { JwtAuthGuard, PermissionGuard } from '@flusys/nestjs-shared/guards';
31
- import { IdempotencyInterceptor, SetCreatedByOnBody, SetDeletedByOnBody, SetUpdateByOnBody, Slug } from '@flusys/nestjs-shared/interceptors';
28
+ import { CurrentUser, Public, RequirePermission, RequireAnyPermission, RequirePermissionCondition } from '../decorators';
29
+ import { DeleteDto, FilterAndPaginationDto, GetByIdBodyDto } from '../dtos';
30
+ import { JwtAuthGuard, PermissionGuard } from '../guards';
31
+ import { IdempotencyInterceptor, SetCreatedByOnBody, SetDeletedByOnBody, SetUpdateByOnBody, Slug } from '../interceptors';
32
32
  import { applyDecorators, Body, HttpCode, HttpStatus, Param, Post, Query, UseGuards, UseInterceptors, Version, VERSION_NEUTRAL } from '@nestjs/common';
33
33
  import { ApiBearerAuth, ApiBody, ApiHeader, ApiOperation, ApiParam, ApiQuery, ApiResponse } from '@nestjs/swagger';
34
34
  import { plainToInstance } from 'class-transformer';
@@ -94,8 +94,10 @@ import { ApiResponseDto } from '../decorators/api-response.decorator';
94
94
  // 2. It's an object with 'level' property but no endpoint keys
95
95
  const isGlobalSecurity = typeof securityConfig === 'string' || securityConfig && typeof securityConfig === 'object' && 'level' in securityConfig && !endpointKeys.some((key)=>key in securityConfig);
96
96
  // Normalize security config for each endpoint
97
+ // IMPORTANT: When per-endpoint security is specified, default to 'jwt' for unconfigured endpoints
98
+ // to prevent accidentally exposing endpoints without authentication
97
99
  const defaultSecurity = isGlobalSecurity ? normalizeSecurity(securityConfig) : {
98
- level: 'public'
100
+ level: 'jwt'
99
101
  };
100
102
  const security = {
101
103
  insert: isGlobalSecurity ? defaultSecurity : normalizeSecurity(securityConfig?.insert),
@@ -337,16 +339,7 @@ import { ApiResponseDto } from '../decorators/api-response.decorator';
337
339
  HttpCode(HttpStatus.OK),
338
340
  ApiOperation({
339
341
  summary: 'Get all items with filters and pagination',
340
- description: `
341
- Retrieves items with support for:
342
- - **filter**: Apply field-based filters (e.g., \`{ "isActive": true }\`)
343
- - **pagination**: Control page and page size
344
- - **sort**: Order by any field (e.g., \`{ "createdAt": "DESC" }\`)
345
- - **select**: Choose specific fields to return
346
- - **withDeleted**: Include soft-deleted items
347
- - **extraKey**: Include additional relations
348
- - **q** (query param): Global text search
349
- `
342
+ description: 'Supports filter, pagination, sort, select, withDeleted, and q (search) params'
350
343
  }),
351
344
  ApiQuery({
352
345
  name: 'q',
@@ -375,15 +368,7 @@ Retrieves items with support for:
375
368
  HttpCode(HttpStatus.OK),
376
369
  ApiOperation({
377
370
  summary: 'Delete, restore, or permanently remove items',
378
- description: `
379
- Performs one of three actions:
380
-
381
- - **"delete"** (soft delete): Marks items as deleted but keeps in database
382
- - **"restore"**: Reverts soft-deleted items to active
383
- - **"permanent"**: Completely removes items from database
384
-
385
- Supports single ID or array of IDs for batch operations.
386
- `
371
+ description: 'Types: delete (soft), restore, permanent. Supports batch IDs.'
387
372
  }),
388
373
  ApiResponse({
389
374
  status: 200,
@@ -16,116 +16,52 @@ import { Logger, NotFoundException } from '@nestjs/common';
16
16
  import { In } from 'typeorm';
17
17
  /** Generic API service with CRUD operations and caching support */ export class ApiService {
18
18
  async insert(dto, user) {
19
- await this.ensureRepositoryInitialized();
20
- const qr = this.repository.manager.connection.createQueryRunner();
21
- await qr.connect();
22
- await qr.startTransaction();
23
- try {
19
+ return this.executeInTransaction('insert', async (qr)=>{
24
20
  await this.beforeInsertOperation(dto, user, qr);
25
21
  const entities = await this.convertRequestDtoToEntity(dto, user);
26
22
  const saved = await qr.manager.save(this.repository.target, entities);
27
- await this.afterInsertOperation(saved, user, qr);
28
- await qr.commitTransaction();
29
- if (this.isCacheable) await Promise.all([
30
- this.clearCacheForAll(),
31
- this.clearCacheForId(Array.isArray(saved) ? saved : [
32
- saved
33
- ])
34
- ]);
35
- const first = Array.isArray(saved) ? saved[0] : saved;
36
- return this.convertEntityToResponseDto(first, false);
37
- } catch (error) {
38
- await qr.rollbackTransaction();
39
- this.handleError(error, 'insert');
40
- } finally{
41
- await qr.release();
42
- }
23
+ await this.afterInsertOperation(this.ensureArray(saved), user, qr);
24
+ return {
25
+ saved,
26
+ returnFirst: true
27
+ };
28
+ });
43
29
  }
44
30
  async insertMany(dtos, user) {
45
- await this.ensureRepositoryInitialized();
46
- const qr = this.repository.manager.connection.createQueryRunner();
47
- await qr.connect();
48
- await qr.startTransaction();
49
- try {
31
+ return this.executeInTransaction('insertMany', async (qr)=>{
50
32
  await this.beforeInsertOperation(dtos, user, qr);
51
33
  const entities = await this.convertRequestDtoToEntity(dtos, user);
52
34
  const saved = await qr.manager.save(this.repository.target, entities);
53
- await this.afterInsertOperation(Array.isArray(saved) ? saved : [
54
- saved
55
- ], user, qr);
56
- await qr.commitTransaction();
57
- if (this.isCacheable) await Promise.all([
58
- this.clearCacheForAll(),
59
- this.clearCacheForId(Array.isArray(saved) ? saved : [
60
- saved
61
- ])
62
- ]);
63
- const list = (Array.isArray(saved) ? saved : [
64
- saved
65
- ]).map((e)=>this.convertEntityToResponseDto(e, false));
66
- return list;
67
- } catch (error) {
68
- await qr.rollbackTransaction();
69
- this.handleError(error, 'insertMany');
70
- } finally{
71
- await qr.release();
72
- }
35
+ await this.afterInsertOperation(this.ensureArray(saved), user, qr);
36
+ return {
37
+ saved,
38
+ returnFirst: false
39
+ };
40
+ });
73
41
  }
74
42
  async update(dto, user) {
75
- await this.ensureRepositoryInitialized();
76
- const qr = this.repository.manager.connection.createQueryRunner();
77
- await qr.connect();
78
- await qr.startTransaction();
79
- try {
43
+ return this.executeInTransaction('update', async (qr)=>{
80
44
  await this.beforeUpdateOperation(dto, user, qr);
81
45
  const entities = await this.convertRequestDtoToEntity(dto, user);
82
46
  const saved = await qr.manager.save(this.repository.target, entities);
83
- await this.afterUpdateOperation(saved, user, qr);
84
- await qr.commitTransaction();
85
- if (this.isCacheable) await Promise.all([
86
- this.clearCacheForAll(),
87
- this.clearCacheForId(Array.isArray(saved) ? saved : [
88
- saved
89
- ])
90
- ]);
91
- const first = Array.isArray(saved) ? saved[0] : saved;
92
- return this.convertEntityToResponseDto(first, false);
93
- } catch (error) {
94
- await qr.rollbackTransaction();
95
- this.handleError(error, 'update');
96
- } finally{
97
- await qr.release();
98
- }
47
+ await this.afterUpdateOperation(this.ensureArray(saved), user, qr);
48
+ return {
49
+ saved,
50
+ returnFirst: true
51
+ };
52
+ });
99
53
  }
100
54
  async updateMany(dtos, user) {
101
- await this.ensureRepositoryInitialized();
102
- const qr = this.repository.manager.connection.createQueryRunner();
103
- await qr.connect();
104
- await qr.startTransaction();
105
- try {
55
+ return this.executeInTransaction('updateMany', async (qr)=>{
106
56
  await this.beforeUpdateOperation(dtos, user, qr);
107
57
  const entities = await this.convertRequestDtoToEntity(dtos, user);
108
58
  const saved = await qr.manager.save(this.repository.target, entities);
109
- await this.afterUpdateOperation(Array.isArray(saved) ? saved : [
110
- saved
111
- ], user, qr);
112
- await qr.commitTransaction();
113
- if (this.isCacheable) await Promise.all([
114
- this.clearCacheForAll(),
115
- this.clearCacheForId(Array.isArray(saved) ? saved : [
116
- saved
117
- ])
118
- ]);
119
- const list = (Array.isArray(saved) ? saved : [
120
- saved
121
- ]).map((e)=>this.convertEntityToResponseDto(e, false));
122
- return list;
123
- } catch (error) {
124
- await qr.rollbackTransaction();
125
- this.handleError(error, 'updateMany');
126
- } finally{
127
- await qr.release();
128
- }
59
+ await this.afterUpdateOperation(this.ensureArray(saved), user, qr);
60
+ return {
61
+ saved,
62
+ returnFirst: false
63
+ };
64
+ });
129
65
  }
130
66
  async findByIds(ids, user) {
131
67
  await this.ensureRepositoryInitialized();
@@ -344,6 +280,37 @@ import { In } from 'typeorm';
344
280
  });
345
281
  ErrorHandler.rethrowError(error);
346
282
  }
283
+ /** Ensures value is always an array */ ensureArray(value) {
284
+ return Array.isArray(value) ? value : [
285
+ value
286
+ ];
287
+ }
288
+ /** Executes operation in transaction with automatic cache clearing */ async executeInTransaction(operation, fn) {
289
+ await this.ensureRepositoryInitialized();
290
+ const qr = this.repository.manager.connection.createQueryRunner();
291
+ await qr.connect();
292
+ await qr.startTransaction();
293
+ try {
294
+ const { saved, returnFirst } = await fn(qr);
295
+ await qr.commitTransaction();
296
+ const savedArray = this.ensureArray(saved);
297
+ if (this.isCacheable) {
298
+ await Promise.all([
299
+ this.clearCacheForAll(),
300
+ this.clearCacheForId(savedArray)
301
+ ]);
302
+ }
303
+ if (returnFirst) {
304
+ return this.convertEntityToResponseDto(savedArray[0], false);
305
+ }
306
+ return savedArray.map((e)=>this.convertEntityToResponseDto(e, false));
307
+ } catch (error) {
308
+ await qr.rollbackTransaction();
309
+ this.handleError(error, operation);
310
+ } finally{
311
+ await qr.release();
312
+ }
313
+ }
347
314
  // Hooks (override in child classes)
348
315
  async ensureRepositoryInitialized() {}
349
316
  async beforeInsertOperation(_dto, _user, _queryRunner) {}
@@ -4,3 +4,5 @@ export * from './request-scoped-api.service';
4
4
  export * from './hybrid-cache.class';
5
5
  export * from './winston-logger-adapter.class';
6
6
  export * from './winston.logger.class';
7
+ // Re-export permission constants for convenience
8
+ export * from '../constants/permissions';
@@ -44,46 +44,31 @@ import { getRequestId, getUserId, getTenantId, getCompanyId } from '../middlewar
44
44
  * // Output: { requestId: 'uuid', userId: 'user-id', context: 'MyService', message: 'Operation completed', orderId: '123' }
45
45
  * ```
46
46
  */ export class WinstonLoggerAdapter {
47
- log(message, context, ...args) {
48
- const meta = args.length > 0 && typeof args[0] === 'object' ? args[0] : {};
49
- winstonLogger.info(message, {
47
+ buildLogMeta(context, args, extra) {
48
+ const meta = args?.length && typeof args[0] === 'object' ? args[0] : {};
49
+ return {
50
50
  context: context || this.context,
51
51
  ...getCorrelationMeta(),
52
- ...meta
53
- });
52
+ ...meta,
53
+ ...extra
54
+ };
55
+ }
56
+ log(message, context, ...args) {
57
+ winstonLogger.info(message, this.buildLogMeta(context, args));
54
58
  }
55
59
  error(message, trace, context, ...args) {
56
- const meta = args.length > 0 && typeof args[0] === 'object' ? args[0] : {};
57
- winstonLogger.error(message, {
58
- context: context || this.context,
59
- stack: trace,
60
- ...getCorrelationMeta(),
61
- ...meta
62
- });
60
+ winstonLogger.error(message, this.buildLogMeta(context, args, {
61
+ stack: trace
62
+ }));
63
63
  }
64
64
  warn(message, context, ...args) {
65
- const meta = args.length > 0 && typeof args[0] === 'object' ? args[0] : {};
66
- winstonLogger.warn(message, {
67
- context: context || this.context,
68
- ...getCorrelationMeta(),
69
- ...meta
70
- });
65
+ winstonLogger.warn(message, this.buildLogMeta(context, args));
71
66
  }
72
67
  debug(message, context, ...args) {
73
- const meta = args.length > 0 && typeof args[0] === 'object' ? args[0] : {};
74
- winstonLogger.debug(message, {
75
- context: context || this.context,
76
- ...getCorrelationMeta(),
77
- ...meta
78
- });
68
+ winstonLogger.debug(message, this.buildLogMeta(context, args));
79
69
  }
80
70
  verbose(message, context, ...args) {
81
- const meta = args.length > 0 && typeof args[0] === 'object' ? args[0] : {};
82
- winstonLogger.verbose(message, {
83
- context: context || this.context,
84
- ...getCorrelationMeta(),
85
- ...meta
86
- });
71
+ winstonLogger.verbose(message, this.buildLogMeta(context, args));
87
72
  }
88
73
  constructor(context){
89
74
  _define_property(this, "context", void 0);
@@ -97,25 +82,23 @@ import { getRequestId, getUserId, getTenantId, getCompanyId } from '../middlewar
97
82
  * Use this when you need to use NestJS's built-in logger
98
83
  * instead of Winston (e.g., for testing)
99
84
  */ export class NestLoggerAdapter {
85
+ formatMessage(message, args) {
86
+ return args?.length ? `${message} ${JSON.stringify(args)}` : message;
87
+ }
100
88
  log(message, context, ...args) {
101
- const logMessage = args.length > 0 ? `${message} ${JSON.stringify(args)}` : message;
102
- this.logger.log(logMessage, context);
89
+ this.logger.log(this.formatMessage(message, args), context);
103
90
  }
104
91
  error(message, trace, context, ...args) {
105
- const logMessage = args.length > 0 ? `${message} ${JSON.stringify(args)}` : message;
106
- this.logger.error(logMessage, trace, context);
92
+ this.logger.error(this.formatMessage(message, args), trace, context);
107
93
  }
108
94
  warn(message, context, ...args) {
109
- const logMessage = args.length > 0 ? `${message} ${JSON.stringify(args)}` : message;
110
- this.logger.warn(logMessage, context);
95
+ this.logger.warn(this.formatMessage(message, args), context);
111
96
  }
112
97
  debug(message, context, ...args) {
113
- const logMessage = args.length > 0 ? `${message} ${JSON.stringify(args)}` : message;
114
- this.logger.debug(logMessage, context);
98
+ this.logger.debug(this.formatMessage(message, args), context);
115
99
  }
116
100
  verbose(message, context, ...args) {
117
- const logMessage = args.length > 0 ? `${message} ${JSON.stringify(args)}` : message;
118
- this.logger.verbose(logMessage, context);
101
+ this.logger.verbose(this.formatMessage(message, args), context);
119
102
  }
120
103
  constructor(logger){
121
104
  _define_property(this, "logger", void 0);
@@ -12,3 +12,5 @@ export const CLIENT_TYPE_HEADER = 'x-client-type';
12
12
  // Cache key prefixes
13
13
  export const PERMISSIONS_CACHE_PREFIX = 'permissions';
14
14
  export const IDEMPOTENCY_CACHE_PREFIX = 'idempotency';
15
+ // Permission codes
16
+ export * from './permissions';