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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (198) hide show
  1. package/README.md +146 -1330
  2. package/cjs/classes/api-controller.class.js +325 -49
  3. package/cjs/classes/api-service.class.js +271 -149
  4. package/cjs/classes/handle-custome-db.js +37 -0
  5. package/cjs/classes/index.js +2 -2
  6. package/cjs/classes/winston.logger.class.js +103 -70
  7. package/cjs/constants/index.js +15 -4
  8. package/cjs/constants/message-keys.js +68 -0
  9. package/cjs/constants/permissions.js +287 -0
  10. package/cjs/decorators/api-response.decorator.js +2 -3
  11. package/cjs/decorators/index.js +1 -0
  12. package/cjs/decorators/log-action.decorator.js +149 -0
  13. package/cjs/decorators/require-permission.decorator.js +5 -5
  14. package/cjs/dtos/delete.dto.js +10 -0
  15. package/cjs/dtos/filter-and-pagination.dto.js +24 -34
  16. package/cjs/dtos/get-by-ids.dto.js +65 -0
  17. package/cjs/dtos/identity-response.dto.js +4 -8
  18. package/cjs/dtos/index.js +1 -0
  19. package/cjs/dtos/pagination.dto.js +4 -8
  20. package/cjs/dtos/response-payload.dto.js +68 -112
  21. package/cjs/entities/identity.js +12 -6
  22. package/cjs/entities/index.js +1 -0
  23. package/cjs/entities/raw-type.js +4 -0
  24. package/cjs/entities/user-root.js +15 -15
  25. package/cjs/enums/index.js +20 -0
  26. package/cjs/enums/notification-type.enum.js +17 -0
  27. package/cjs/enums/participant-status.enum.js +17 -0
  28. package/cjs/enums/recurrence-type.enum.js +18 -0
  29. package/cjs/exceptions/base-app.exception.js +151 -0
  30. package/cjs/exceptions/index.js +1 -0
  31. package/cjs/exceptions/permission.exception.js +13 -9
  32. package/cjs/filters/global-exception.filter.js +279 -0
  33. package/cjs/filters/index.js +18 -0
  34. package/cjs/guards/jwt-auth.guard.js +4 -1
  35. package/cjs/guards/permission.guard.js +88 -148
  36. package/cjs/index.js +2 -0
  37. package/cjs/interceptors/idempotency.interceptor.js +1 -1
  38. package/cjs/interceptors/index.js +1 -4
  39. package/cjs/interceptors/set-user-field-on-body.interceptor.js +60 -0
  40. package/cjs/interceptors/slug.interceptor.js +30 -9
  41. package/cjs/interfaces/datasource.interface.js +4 -0
  42. package/cjs/interfaces/event-manager-adapter.interface.js +11 -0
  43. package/cjs/interfaces/index.js +4 -2
  44. package/cjs/interfaces/module-config.interface.js +4 -0
  45. package/cjs/interfaces/notification-adapter.interface.js +11 -0
  46. package/cjs/interfaces/permission.interface.js +1 -1
  47. package/cjs/middlewares/logger.middleware.js +133 -115
  48. package/cjs/modules/cache/cache.module.js +3 -3
  49. package/cjs/modules/datasource/index.js +0 -1
  50. package/cjs/modules/datasource/multi-tenant-datasource.service.js +60 -183
  51. package/cjs/modules/utils/utils.service.js +27 -206
  52. package/cjs/utils/date-time.util.js +94 -0
  53. package/cjs/utils/html-sanitizer.util.js +64 -0
  54. package/cjs/utils/index.js +5 -1
  55. package/cjs/utils/query-helpers.util.js +60 -0
  56. package/cjs/utils/request.util.js +96 -0
  57. package/cjs/utils/string.util.js +63 -0
  58. package/classes/api-controller.class.d.ts +16 -8
  59. package/classes/api-service.class.d.ts +30 -25
  60. package/classes/handle-custome-db.d.ts +3 -0
  61. package/classes/index.d.ts +2 -2
  62. package/classes/winston.logger.class.d.ts +1 -0
  63. package/constants/index.d.ts +2 -1
  64. package/constants/message-keys.d.ts +45 -0
  65. package/constants/permissions.d.ts +306 -0
  66. package/decorators/api-response.decorator.d.ts +1 -1
  67. package/decorators/current-user.decorator.d.ts +1 -1
  68. package/decorators/index.d.ts +1 -0
  69. package/decorators/log-action.decorator.d.ts +8 -0
  70. package/decorators/require-permission.decorator.d.ts +2 -2
  71. package/dtos/delete.dto.d.ts +1 -0
  72. package/dtos/filter-and-pagination.dto.d.ts +0 -2
  73. package/dtos/get-by-ids.dto.d.ts +4 -0
  74. package/dtos/index.d.ts +1 -0
  75. package/dtos/response-payload.dto.d.ts +8 -20
  76. package/entities/index.d.ts +1 -0
  77. package/entities/raw-type.d.ts +1 -0
  78. package/enums/index.d.ts +3 -0
  79. package/enums/notification-type.enum.d.ts +6 -0
  80. package/enums/participant-status.enum.d.ts +6 -0
  81. package/enums/recurrence-type.enum.d.ts +7 -0
  82. package/exceptions/base-app.exception.d.ts +41 -0
  83. package/exceptions/index.d.ts +1 -0
  84. package/exceptions/permission.exception.d.ts +2 -2
  85. package/fesm/classes/api-controller.class.js +326 -50
  86. package/fesm/classes/api-service.class.js +274 -152
  87. package/fesm/classes/handle-custome-db.js +19 -0
  88. package/fesm/classes/index.js +3 -2
  89. package/fesm/classes/winston.logger.class.js +100 -68
  90. package/fesm/constants/index.js +4 -1
  91. package/fesm/constants/message-keys.js +49 -0
  92. package/fesm/constants/permissions.js +200 -0
  93. package/fesm/decorators/api-response.decorator.js +3 -21
  94. package/fesm/decorators/index.js +1 -0
  95. package/fesm/decorators/log-action.decorator.js +139 -0
  96. package/fesm/decorators/require-permission.decorator.js +17 -65
  97. package/fesm/dtos/delete.dto.js +12 -2
  98. package/fesm/dtos/filter-and-pagination.dto.js +26 -47
  99. package/fesm/dtos/get-by-ids.dto.js +55 -0
  100. package/fesm/dtos/identity-response.dto.js +4 -8
  101. package/fesm/dtos/index.js +1 -0
  102. package/fesm/dtos/pagination.dto.js +4 -8
  103. package/fesm/dtos/response-payload.dto.js +68 -103
  104. package/fesm/entities/identity.js +12 -6
  105. package/fesm/entities/index.js +1 -0
  106. package/fesm/entities/raw-type.js +1 -0
  107. package/fesm/entities/user-root.js +15 -15
  108. package/fesm/enums/index.js +3 -0
  109. package/fesm/enums/notification-type.enum.js +7 -0
  110. package/fesm/enums/participant-status.enum.js +7 -0
  111. package/fesm/enums/recurrence-type.enum.js +8 -0
  112. package/fesm/exceptions/base-app.exception.js +115 -0
  113. package/fesm/exceptions/index.js +1 -0
  114. package/fesm/exceptions/permission.exception.js +16 -18
  115. package/fesm/filters/global-exception.filter.js +269 -0
  116. package/fesm/filters/index.js +1 -0
  117. package/fesm/guards/jwt-auth.guard.js +5 -2
  118. package/fesm/guards/permission.guard.js +90 -150
  119. package/fesm/index.js +2 -0
  120. package/fesm/interceptors/idempotency.interceptor.js +2 -2
  121. package/fesm/interceptors/index.js +1 -4
  122. package/fesm/interceptors/set-user-field-on-body.interceptor.js +39 -0
  123. package/fesm/interceptors/slug.interceptor.js +31 -10
  124. package/fesm/interfaces/datasource.interface.js +1 -0
  125. package/fesm/interfaces/event-manager-adapter.interface.js +1 -0
  126. package/fesm/interfaces/index.js +4 -2
  127. package/fesm/interfaces/module-config.interface.js +5 -0
  128. package/fesm/interfaces/notification-adapter.interface.js +1 -0
  129. package/fesm/interfaces/permission.interface.js +1 -3
  130. package/fesm/middlewares/logger.middleware.js +133 -109
  131. package/fesm/modules/cache/cache.module.js +2 -2
  132. package/fesm/modules/datasource/index.js +0 -1
  133. package/fesm/modules/datasource/multi-tenant-datasource.service.js +61 -184
  134. package/fesm/modules/utils/utils.service.js +27 -206
  135. package/fesm/utils/date-time.util.js +70 -0
  136. package/fesm/utils/html-sanitizer.util.js +69 -0
  137. package/fesm/utils/index.js +5 -1
  138. package/fesm/utils/query-helpers.util.js +85 -0
  139. package/fesm/utils/request.util.js +81 -0
  140. package/fesm/utils/string.util.js +71 -0
  141. package/filters/global-exception.filter.d.ts +9 -0
  142. package/filters/index.d.ts +1 -0
  143. package/guards/permission.guard.d.ts +4 -6
  144. package/index.d.ts +2 -0
  145. package/interceptors/idempotency.interceptor.d.ts +1 -1
  146. package/interceptors/index.d.ts +1 -4
  147. package/interceptors/set-user-field-on-body.interceptor.d.ts +5 -0
  148. package/interceptors/slug.interceptor.d.ts +2 -1
  149. package/interfaces/api.interface.d.ts +4 -2
  150. package/interfaces/datasource.interface.d.ts +5 -0
  151. package/interfaces/event-manager-adapter.interface.d.ts +43 -0
  152. package/interfaces/identity.interface.d.ts +4 -4
  153. package/interfaces/index.d.ts +4 -2
  154. package/interfaces/logged-user-info.interface.d.ts +0 -2
  155. package/interfaces/module-config.interface.d.ts +6 -0
  156. package/interfaces/notification-adapter.interface.d.ts +21 -0
  157. package/interfaces/permission.interface.d.ts +13 -9
  158. package/middlewares/logger.middleware.d.ts +2 -2
  159. package/modules/datasource/index.d.ts +0 -1
  160. package/modules/datasource/multi-tenant-datasource.service.d.ts +5 -16
  161. package/modules/utils/utils.service.d.ts +3 -14
  162. package/package.json +21 -14
  163. package/utils/date-time.util.d.ts +8 -0
  164. package/utils/html-sanitizer.util.d.ts +2 -0
  165. package/utils/index.d.ts +5 -1
  166. package/utils/query-helpers.util.d.ts +16 -0
  167. package/utils/request.util.d.ts +6 -0
  168. package/utils/string.util.d.ts +2 -0
  169. package/cjs/classes/request-scoped-api.service.js +0 -61
  170. package/cjs/classes/winston-logger-adapter.class.js +0 -121
  171. package/cjs/interceptors/query-performance.interceptor.js +0 -66
  172. package/cjs/interceptors/set-create-by-on-body.interceptor.js +0 -40
  173. package/cjs/interceptors/set-delete-by-on-body.interceptor.js +0 -40
  174. package/cjs/interceptors/set-update-by-on-body.interceptor.js +0 -40
  175. package/cjs/interfaces/base-query.interface.js +0 -6
  176. package/cjs/interfaces/logger.interface.js +0 -7
  177. package/cjs/modules/datasource/datasource.module.js +0 -120
  178. package/cjs/utils/error-handler.util.js +0 -67
  179. package/classes/request-scoped-api.service.d.ts +0 -13
  180. package/classes/winston-logger-adapter.class.d.ts +0 -20
  181. package/fesm/classes/request-scoped-api.service.js +0 -51
  182. package/fesm/classes/winston-logger-adapter.class.js +0 -124
  183. package/fesm/interceptors/query-performance.interceptor.js +0 -56
  184. package/fesm/interceptors/set-create-by-on-body.interceptor.js +0 -30
  185. package/fesm/interceptors/set-delete-by-on-body.interceptor.js +0 -30
  186. package/fesm/interceptors/set-update-by-on-body.interceptor.js +0 -30
  187. package/fesm/interfaces/base-query.interface.js +0 -3
  188. package/fesm/interfaces/logger.interface.js +0 -4
  189. package/fesm/modules/datasource/datasource.module.js +0 -110
  190. package/fesm/utils/error-handler.util.js +0 -59
  191. package/interceptors/query-performance.interceptor.d.ts +0 -8
  192. package/interceptors/set-create-by-on-body.interceptor.d.ts +0 -5
  193. package/interceptors/set-delete-by-on-body.interceptor.d.ts +0 -5
  194. package/interceptors/set-update-by-on-body.interceptor.d.ts +0 -5
  195. package/interfaces/base-query.interface.d.ts +0 -7
  196. package/interfaces/logger.interface.d.ts +0 -7
  197. package/modules/datasource/datasource.module.d.ts +0 -18
  198. package/utils/error-handler.util.d.ts +0 -24
@@ -25,14 +25,16 @@ 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';
32
28
  import { applyDecorators, Body, HttpCode, HttpStatus, Param, Post, Query, UseGuards, UseInterceptors, Version, VERSION_NEUTRAL } from '@nestjs/common';
33
- import { ApiBearerAuth, ApiBody, ApiHeader, ApiOperation, ApiParam, ApiQuery, ApiResponse } from '@nestjs/swagger';
29
+ import { ApiBearerAuth, ApiBody, ApiExcludeEndpoint, ApiHeader, ApiOperation, ApiParam, ApiQuery, ApiResponse } from '@nestjs/swagger';
34
30
  import { plainToInstance } from 'class-transformer';
31
+ import { ERROR_MESSAGES } from '../constants/message-keys';
32
+ import { CurrentUser, Public, RequireAnyPermission, RequirePermission, RequirePermissionLogic } from '../decorators';
35
33
  import { ApiResponseDto } from '../decorators/api-response.decorator';
34
+ import { DeleteDto, FilterAndPaginationDto, GetByIdBodyDto, GetByIdsDto } from '../dtos';
35
+ import { BaseAppException, NotFoundException } from '../exceptions';
36
+ import { JwtAuthGuard, PermissionGuard } from '../guards';
37
+ import { IdempotencyInterceptor, SetCreatedByOnBody, SetDeletedByOnBody, SetUpdateByOnBody, Slug } from '../interceptors';
36
38
  /**
37
39
  * Helper to normalize security config
38
40
  */ function normalizeSecurity(config) {
@@ -44,6 +46,24 @@ import { ApiResponseDto } from '../decorators/api-response.decorator';
44
46
  };
45
47
  return config;
46
48
  }
49
+ /**
50
+ * Helper to check if endpoint is enabled
51
+ */ function isEndpointEnabled(endpoint, enabledEndpoints) {
52
+ if (!enabledEndpoints || enabledEndpoints === 'all') return true;
53
+ return enabledEndpoints.includes(endpoint);
54
+ }
55
+ /**
56
+ * Helper to conditionally exclude endpoint from Swagger if disabled
57
+ */ function createEndpointDecorators(security, endpoint, enabledEndpoints) {
58
+ const decorators = [];
59
+ // Add security decorators
60
+ decorators.push(createSecurityDecorators(security));
61
+ // Exclude from Swagger if endpoint is disabled
62
+ if (!isEndpointEnabled(endpoint, enabledEndpoints)) {
63
+ decorators.push(ApiExcludeEndpoint());
64
+ }
65
+ return applyDecorators(...decorators);
66
+ }
47
67
  /**
48
68
  * Creates security decorators based on configuration
49
69
  */ function createSecurityDecorators(security) {
@@ -62,11 +82,11 @@ import { ApiResponseDto } from '../decorators/api-response.decorator';
62
82
  // Apply PermissionGuard to check permissions from cache
63
83
  decorators.push(UseGuards(JwtAuthGuard, PermissionGuard));
64
84
  decorators.push(ApiBearerAuth());
65
- // Check for complex nested condition first
66
- if (security.condition) {
67
- decorators.push(RequirePermissionCondition(security.condition));
85
+ // Check for complex logic first
86
+ if (security.logic) {
87
+ decorators.push(RequirePermissionLogic(security.logic));
68
88
  } else if (security.permissions && security.permissions.length > 0) {
69
- if (security.operator === 'or') {
89
+ if (security.operator === 'OR') {
70
90
  decorators.push(RequireAnyPermission(...security.permissions));
71
91
  } else {
72
92
  // AND is default and most secure
@@ -78,13 +98,24 @@ import { ApiResponseDto } from '../decorators/api-response.decorator';
78
98
  return applyDecorators(...decorators);
79
99
  }
80
100
  /** Creates an API Controller with standardized CRUD endpoints (POST-only RPC pattern) */ export function createApiController(createDtoClass, updateDtoClass, responseDtoClass, options = {}) {
101
+ const excludeExtraneousValues = options.excludeExtraneousValues ?? true;
102
+ function toDto(cls, input) {
103
+ return plainToInstance(cls, input, {
104
+ excludeExtraneousValues
105
+ });
106
+ }
107
+ // Entity name for message keys (defaults to 'item')
108
+ const entityName = options.entityName ?? 'item';
81
109
  // Determine if security is global (applies to all) or per-endpoint
82
110
  const securityConfig = options.security;
83
111
  const endpointKeys = [
84
112
  'insert',
85
113
  'insertMany',
86
114
  'getById',
115
+ 'getByIds',
87
116
  'getAll',
117
+ 'bulkUpsert',
118
+ 'getByFilter',
88
119
  'update',
89
120
  'updateMany',
90
121
  'delete'
@@ -94,34 +125,68 @@ import { ApiResponseDto } from '../decorators/api-response.decorator';
94
125
  // 2. It's an object with 'level' property but no endpoint keys
95
126
  const isGlobalSecurity = typeof securityConfig === 'string' || securityConfig && typeof securityConfig === 'object' && 'level' in securityConfig && !endpointKeys.some((key)=>key in securityConfig);
96
127
  // Normalize security config for each endpoint
128
+ // IMPORTANT: When per-endpoint security is specified, default to 'jwt' for unconfigured endpoints
129
+ // to prevent accidentally exposing endpoints without authentication
97
130
  const defaultSecurity = isGlobalSecurity ? normalizeSecurity(securityConfig) : {
98
- level: 'public'
131
+ level: 'jwt'
99
132
  };
100
133
  const security = {
101
134
  insert: isGlobalSecurity ? defaultSecurity : normalizeSecurity(securityConfig?.insert),
102
135
  insertMany: isGlobalSecurity ? defaultSecurity : normalizeSecurity(securityConfig?.insertMany),
103
136
  getById: isGlobalSecurity ? defaultSecurity : normalizeSecurity(securityConfig?.getById),
137
+ getByIds: isGlobalSecurity ? defaultSecurity : normalizeSecurity(securityConfig?.getByIds),
104
138
  getAll: isGlobalSecurity ? defaultSecurity : normalizeSecurity(securityConfig?.getAll),
139
+ getByFilter: isGlobalSecurity ? defaultSecurity : normalizeSecurity(securityConfig?.getByFilter),
140
+ bulkUpsert: isGlobalSecurity ? defaultSecurity : normalizeSecurity(securityConfig?.bulkUpsert),
105
141
  update: isGlobalSecurity ? defaultSecurity : normalizeSecurity(securityConfig?.update),
106
142
  updateMany: isGlobalSecurity ? defaultSecurity : normalizeSecurity(securityConfig?.updateMany),
107
143
  delete: isGlobalSecurity ? defaultSecurity : normalizeSecurity(securityConfig?.delete)
108
144
  };
109
145
  let ApiController = class ApiController {
146
+ isEnabled(endpoint) {
147
+ if (this.enabledEndpoints === 'all') return true;
148
+ return Array.isArray(this.enabledEndpoints) && this.enabledEndpoints.includes(endpoint);
149
+ }
110
150
  async insert(addDto, user) {
151
+ if (!this.isEnabled('insert')) {
152
+ throw new BaseAppException({
153
+ message: `Endpoint 'insert' is disabled`,
154
+ messageKey: ERROR_MESSAGES.ENDPOINT_DISABLED,
155
+ messageVariables: {
156
+ endpoint: 'insert'
157
+ },
158
+ status: HttpStatus.FORBIDDEN
159
+ });
160
+ }
111
161
  const entity = await this.service.insert(addDto, user);
112
- const data = plainToInstance(responseDtoClass, entity);
162
+ const data = toDto(responseDtoClass, entity);
113
163
  return {
114
164
  success: true,
115
- message: 'Item created successfully',
165
+ message: `${entityName.charAt(0).toUpperCase() + entityName.slice(1)} created successfully`,
166
+ messageKey: `${entityName}.create.success`,
116
167
  data
117
168
  };
118
169
  }
119
170
  async insertMany(addDto, user) {
171
+ if (!this.isEnabled('insertMany')) {
172
+ throw new BaseAppException({
173
+ message: `Endpoint 'insertMany' is disabled`,
174
+ messageKey: ERROR_MESSAGES.ENDPOINT_DISABLED,
175
+ messageVariables: {
176
+ endpoint: 'insertMany'
177
+ },
178
+ status: HttpStatus.FORBIDDEN
179
+ });
180
+ }
120
181
  const entities = await this.service.insertMany(addDto, user);
121
- const data = entities.map((item)=>plainToInstance(responseDtoClass, item));
182
+ const data = entities.map((item)=>toDto(responseDtoClass, item));
122
183
  return {
123
184
  success: true,
124
- message: `${data.length} items created successfully`,
185
+ message: `${data.length} ${entityName}s created successfully`,
186
+ messageKey: `${entityName}.create.many.success`,
187
+ messageVariables: {
188
+ count: data.length
189
+ },
125
190
  data,
126
191
  meta: {
127
192
  count: data.length,
@@ -131,29 +196,92 @@ import { ApiResponseDto } from '../decorators/api-response.decorator';
131
196
  };
132
197
  }
133
198
  async getById(id, body, user) {
199
+ if (!this.isEnabled('getById')) {
200
+ throw new BaseAppException({
201
+ message: `Endpoint 'getById' is disabled`,
202
+ messageKey: ERROR_MESSAGES.ENDPOINT_DISABLED,
203
+ messageVariables: {
204
+ endpoint: 'getById'
205
+ },
206
+ status: HttpStatus.FORBIDDEN
207
+ });
208
+ }
134
209
  const entity = await this.service.findById(id, user, body?.select);
135
- const data = plainToInstance(responseDtoClass, entity);
210
+ const data = toDto(responseDtoClass, entity);
136
211
  return {
137
212
  success: true,
138
- message: 'Item retrieved successfully',
213
+ message: `${entityName.charAt(0).toUpperCase() + entityName.slice(1)} retrieved successfully`,
214
+ messageKey: `${entityName}.get.success`,
139
215
  data
140
216
  };
141
217
  }
218
+ async getByIds(body, user) {
219
+ if (!this.isEnabled('getByIds')) {
220
+ throw new BaseAppException({
221
+ message: `Endpoint 'getByIds' is disabled`,
222
+ messageKey: ERROR_MESSAGES.ENDPOINT_DISABLED,
223
+ messageVariables: {
224
+ endpoint: 'getByIds'
225
+ },
226
+ status: HttpStatus.FORBIDDEN
227
+ });
228
+ }
229
+ const entities = await this.service.findByIds(body.ids, user, body?.select);
230
+ const data = entities.map((item)=>toDto(responseDtoClass, item));
231
+ return {
232
+ success: true,
233
+ message: `${data.length} ${entityName}${data.length !== 1 ? 's' : ''} retrieved successfully`,
234
+ messageKey: `${entityName}.get.by.ids.success`,
235
+ data,
236
+ meta: {
237
+ total: data.length,
238
+ page: 0,
239
+ pageSize: data.length,
240
+ count: data.length,
241
+ totalPages: 1
242
+ }
243
+ };
244
+ }
142
245
  async update(updateDto, user) {
246
+ if (!this.isEnabled('update')) {
247
+ throw new BaseAppException({
248
+ message: `Endpoint 'update' is disabled`,
249
+ messageKey: ERROR_MESSAGES.ENDPOINT_DISABLED,
250
+ messageVariables: {
251
+ endpoint: 'update'
252
+ },
253
+ status: HttpStatus.FORBIDDEN
254
+ });
255
+ }
143
256
  const entity = await this.service.update(updateDto, user);
144
- const data = plainToInstance(responseDtoClass, entity);
257
+ const data = toDto(responseDtoClass, entity);
145
258
  return {
146
259
  success: true,
147
- message: 'Item updated successfully',
260
+ message: `${entityName.charAt(0).toUpperCase() + entityName.slice(1)} updated successfully`,
261
+ messageKey: `${entityName}.update.success`,
148
262
  data
149
263
  };
150
264
  }
151
265
  async updateMany(updateDtos, user) {
266
+ if (!this.isEnabled('updateMany')) {
267
+ throw new BaseAppException({
268
+ message: `Endpoint 'updateMany' is disabled`,
269
+ messageKey: ERROR_MESSAGES.ENDPOINT_DISABLED,
270
+ messageVariables: {
271
+ endpoint: 'updateMany'
272
+ },
273
+ status: HttpStatus.FORBIDDEN
274
+ });
275
+ }
152
276
  const entities = await this.service.updateMany(updateDtos, user);
153
- const data = plainToInstance(responseDtoClass, entities);
277
+ const data = entities.map((item)=>toDto(responseDtoClass, item));
154
278
  return {
155
279
  success: true,
156
- message: `${data.length} items updated successfully`,
280
+ message: `${data.length} ${entityName}s updated successfully`,
281
+ messageKey: `${entityName}.update.many.success`,
282
+ messageVariables: {
283
+ count: data.length
284
+ },
157
285
  data,
158
286
  meta: {
159
287
  count: data.length,
@@ -162,15 +290,93 @@ import { ApiResponseDto } from '../decorators/api-response.decorator';
162
290
  }
163
291
  };
164
292
  }
293
+ async bulkUpsert(dtos, user) {
294
+ if (!this.isEnabled('bulkUpsert')) {
295
+ throw new BaseAppException({
296
+ message: `Endpoint 'bulkUpsert' is disabled`,
297
+ messageKey: ERROR_MESSAGES.ENDPOINT_DISABLED,
298
+ messageVariables: {
299
+ endpoint: 'bulkUpsert'
300
+ },
301
+ status: HttpStatus.FORBIDDEN
302
+ });
303
+ }
304
+ const toInsert = [];
305
+ const toUpdate = [];
306
+ for (const dto of dtos){
307
+ if ('id' in dto && dto.id) {
308
+ toUpdate.push(dto);
309
+ } else {
310
+ toInsert.push(dto);
311
+ }
312
+ }
313
+ const allEntities = await this.service.bulkUpsert(toInsert, toUpdate, user);
314
+ const data = allEntities.map((item)=>toDto(responseDtoClass, item));
315
+ return {
316
+ success: true,
317
+ message: `${data.length} ${entityName}s upserted successfully`,
318
+ messageKey: `${entityName}.upsert.many.success`,
319
+ messageVariables: {
320
+ count: data.length
321
+ },
322
+ data,
323
+ meta: {
324
+ count: data.length,
325
+ total: dtos.length,
326
+ failed: dtos.length - data.length
327
+ }
328
+ };
329
+ }
330
+ async getByFilter(filter, user) {
331
+ if (!this.isEnabled('getByFilter')) {
332
+ throw new BaseAppException({
333
+ message: `Endpoint 'getByFilter' is disabled`,
334
+ messageKey: ERROR_MESSAGES.ENDPOINT_DISABLED,
335
+ messageVariables: {
336
+ endpoint: 'getByFilter'
337
+ },
338
+ status: HttpStatus.FORBIDDEN
339
+ });
340
+ }
341
+ const result = await this.service.getAll('', {
342
+ filter,
343
+ pagination: {
344
+ currentPage: 0,
345
+ pageSize: 1
346
+ }
347
+ }, user);
348
+ if (!result.data || result.data.length === 0) {
349
+ throw new NotFoundException(entityName);
350
+ }
351
+ const entity = result.data[0];
352
+ const data = toDto(responseDtoClass, entity);
353
+ return {
354
+ success: true,
355
+ message: `${entityName.charAt(0).toUpperCase() + entityName.slice(1)} retrieved successfully`,
356
+ messageKey: `${entityName}.get.by.filter.success`,
357
+ data
358
+ };
359
+ }
165
360
  async getAll(filterAndPaginationDto, user, search) {
361
+ if (!this.isEnabled('getAll')) {
362
+ throw new BaseAppException({
363
+ message: `Endpoint 'getAll' is disabled`,
364
+ messageKey: ERROR_MESSAGES.ENDPOINT_DISABLED,
365
+ messageVariables: {
366
+ endpoint: 'getAll'
367
+ },
368
+ status: HttpStatus.FORBIDDEN
369
+ });
370
+ }
166
371
  const result = await this.service.getAll(search ?? '', filterAndPaginationDto, user);
167
- const data = plainToInstance(responseDtoClass, result.data);
372
+ const data = result.data.map((item)=>toDto(responseDtoClass, item));
168
373
  const page = filterAndPaginationDto.pagination?.currentPage ?? 0;
169
374
  const pageSize = filterAndPaginationDto.pagination?.pageSize ?? data.length;
170
375
  const totalPages = pageSize > 0 ? Math.ceil(result.total / pageSize) : 1;
171
376
  return {
172
377
  success: true,
173
- message: 'Data retrieved successfully',
378
+ message: `${entityName.charAt(0).toUpperCase() + entityName.slice(1)}s retrieved successfully`,
379
+ messageKey: `${entityName}.get.all.success`,
174
380
  data,
175
381
  meta: {
176
382
  total: result.total,
@@ -183,21 +389,37 @@ import { ApiResponseDto } from '../decorators/api-response.decorator';
183
389
  };
184
390
  }
185
391
  async delete(deleteDto, user) {
392
+ if (!this.isEnabled('delete')) {
393
+ throw new BaseAppException({
394
+ message: `Endpoint 'delete' is disabled`,
395
+ messageKey: ERROR_MESSAGES.ENDPOINT_DISABLED,
396
+ messageVariables: {
397
+ endpoint: 'delete'
398
+ },
399
+ status: HttpStatus.FORBIDDEN
400
+ });
401
+ }
186
402
  await this.service.delete(deleteDto, user);
187
403
  const count = Array.isArray(deleteDto.id) ? deleteDto.id.length : 1;
188
- const action = deleteDto.type === 'restore' ? 'restored' : 'deleted';
404
+ const action = deleteDto.type === 'restore' ? 'restore' : 'delete';
189
405
  return {
190
406
  success: true,
191
- message: `${count} item${count > 1 ? 's' : ''} ${action} successfully`
407
+ message: `${count} ${entityName}${count > 1 ? 's' : ''} ${action}d successfully`,
408
+ messageKey: `${entityName}.${action}.success`,
409
+ messageVariables: {
410
+ count
411
+ }
192
412
  };
193
413
  }
194
414
  constructor(service){
195
415
  _define_property(this, "service", void 0);
416
+ _define_property(this, "enabledEndpoints", void 0);
196
417
  this.service = service;
418
+ this.enabledEndpoints = options.enabledEndpoints ?? 'all';
197
419
  }
198
420
  };
199
421
  _ts_decorate([
200
- createSecurityDecorators(security.insert),
422
+ createEndpointDecorators(security.insert, 'insert', options.enabledEndpoints),
201
423
  Version(VERSION_NEUTRAL),
202
424
  Post('insert'),
203
425
  HttpCode(HttpStatus.CREATED),
@@ -225,7 +447,7 @@ import { ApiResponseDto } from '../decorators/api-response.decorator';
225
447
  _ts_metadata("design:returntype", Promise)
226
448
  ], ApiController.prototype, "insert", null);
227
449
  _ts_decorate([
228
- createSecurityDecorators(security.insertMany),
450
+ createEndpointDecorators(security.insertMany, 'insertMany', options.enabledEndpoints),
229
451
  Version(VERSION_NEUTRAL),
230
452
  Post('insert-many'),
231
453
  HttpCode(HttpStatus.CREATED),
@@ -254,7 +476,7 @@ import { ApiResponseDto } from '../decorators/api-response.decorator';
254
476
  _ts_metadata("design:returntype", Promise)
255
477
  ], ApiController.prototype, "insertMany", null);
256
478
  _ts_decorate([
257
- createSecurityDecorators(security.getById),
479
+ createEndpointDecorators(security.getById, 'getById', options.enabledEndpoints),
258
480
  Version(VERSION_NEUTRAL),
259
481
  Post('get/:id'),
260
482
  HttpCode(HttpStatus.OK),
@@ -284,7 +506,29 @@ import { ApiResponseDto } from '../decorators/api-response.decorator';
284
506
  _ts_metadata("design:returntype", Promise)
285
507
  ], ApiController.prototype, "getById", null);
286
508
  _ts_decorate([
287
- createSecurityDecorators(security.update),
509
+ createEndpointDecorators(security.getByIds, 'getByIds', options.enabledEndpoints),
510
+ Version(VERSION_NEUTRAL),
511
+ Post('get-by-ids'),
512
+ HttpCode(HttpStatus.OK),
513
+ ApiOperation({
514
+ summary: 'Get items by IDs',
515
+ description: 'Retrieves multiple items by their IDs. Optionally specify fields to select.'
516
+ }),
517
+ ApiBody({
518
+ type: GetByIdsDto
519
+ }),
520
+ ApiResponseDto(responseDtoClass, true, 'list'),
521
+ _ts_param(0, Body()),
522
+ _ts_param(1, CurrentUser()),
523
+ _ts_metadata("design:type", Function),
524
+ _ts_metadata("design:paramtypes", [
525
+ typeof GetByIdsDto === "undefined" ? Object : GetByIdsDto,
526
+ Object
527
+ ]),
528
+ _ts_metadata("design:returntype", Promise)
529
+ ], ApiController.prototype, "getByIds", null);
530
+ _ts_decorate([
531
+ createEndpointDecorators(security.update, 'update', options.enabledEndpoints),
288
532
  Version(VERSION_NEUTRAL),
289
533
  Post('update'),
290
534
  HttpCode(HttpStatus.OK),
@@ -307,7 +551,7 @@ import { ApiResponseDto } from '../decorators/api-response.decorator';
307
551
  _ts_metadata("design:returntype", Promise)
308
552
  ], ApiController.prototype, "update", null);
309
553
  _ts_decorate([
310
- createSecurityDecorators(security.updateMany),
554
+ createEndpointDecorators(security.updateMany, 'updateMany', options.enabledEndpoints),
311
555
  Version(VERSION_NEUTRAL),
312
556
  Post('update-many'),
313
557
  HttpCode(HttpStatus.OK),
@@ -331,22 +575,62 @@ import { ApiResponseDto } from '../decorators/api-response.decorator';
331
575
  _ts_metadata("design:returntype", Promise)
332
576
  ], ApiController.prototype, "updateMany", null);
333
577
  _ts_decorate([
334
- createSecurityDecorators(security.getAll),
578
+ createEndpointDecorators(security.bulkUpsert, 'bulkUpsert', options.enabledEndpoints),
579
+ Version(VERSION_NEUTRAL),
580
+ Post('bulk-upsert'),
581
+ HttpCode(HttpStatus.OK),
582
+ ApiOperation({
583
+ summary: 'Create or update multiple items',
584
+ description: 'Bulk upsert: items without ID are created, items with ID are updated.'
585
+ }),
586
+ ApiResponseDto(responseDtoClass, true, 'bulk'),
587
+ ApiBody({
588
+ type: createDtoClass,
589
+ isArray: true
590
+ }),
591
+ UseInterceptors(SetCreatedByOnBody, SetUpdateByOnBody, Slug),
592
+ _ts_param(0, Body()),
593
+ _ts_param(1, CurrentUser()),
594
+ _ts_metadata("design:type", Function),
595
+ _ts_metadata("design:paramtypes", [
596
+ Array,
597
+ Object
598
+ ]),
599
+ _ts_metadata("design:returntype", Promise)
600
+ ], ApiController.prototype, "bulkUpsert", null);
601
+ _ts_decorate([
602
+ createEndpointDecorators(security.getByFilter, 'getByFilter', options.enabledEndpoints),
603
+ Version(VERSION_NEUTRAL),
604
+ Post('get-by-filter'),
605
+ HttpCode(HttpStatus.OK),
606
+ ApiOperation({
607
+ summary: 'Get single item by filter',
608
+ description: 'Retrieves a single item matching the provided filter criteria. Returns first match if multiple exist.'
609
+ }),
610
+ ApiBody({
611
+ schema: {
612
+ type: 'object',
613
+ description: 'Filter criteria'
614
+ }
615
+ }),
616
+ ApiResponseDto(responseDtoClass),
617
+ _ts_param(0, Body()),
618
+ _ts_param(1, CurrentUser()),
619
+ _ts_metadata("design:type", Function),
620
+ _ts_metadata("design:paramtypes", [
621
+ typeof Record === "undefined" ? Object : Record,
622
+ Object
623
+ ]),
624
+ _ts_metadata("design:returntype", Promise)
625
+ ], ApiController.prototype, "getByFilter", null);
626
+ _ts_decorate([
627
+ createEndpointDecorators(security.getAll, 'getAll', options.enabledEndpoints),
335
628
  Version(VERSION_NEUTRAL),
336
629
  Post('get-all'),
337
630
  HttpCode(HttpStatus.OK),
338
631
  ApiOperation({
339
632
  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
- `
633
+ description: 'Supports filter, pagination, sort, select, withDeleted, and q (search) params'
350
634
  }),
351
635
  ApiQuery({
352
636
  name: 'q',
@@ -369,21 +653,13 @@ Retrieves items with support for:
369
653
  _ts_metadata("design:returntype", Promise)
370
654
  ], ApiController.prototype, "getAll", null);
371
655
  _ts_decorate([
372
- createSecurityDecorators(security.delete),
656
+ createEndpointDecorators(security.delete, 'delete', options.enabledEndpoints),
373
657
  Version(VERSION_NEUTRAL),
374
658
  Post('delete'),
375
659
  HttpCode(HttpStatus.OK),
376
660
  ApiOperation({
377
661
  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
- `
662
+ description: 'Types: delete (soft), restore, permanent. Supports batch IDs.'
387
663
  }),
388
664
  ApiResponse({
389
665
  status: 200,