@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.
- package/README.md +146 -1330
- package/cjs/classes/api-controller.class.js +325 -49
- package/cjs/classes/api-service.class.js +271 -149
- package/cjs/classes/handle-custome-db.js +37 -0
- package/cjs/classes/index.js +2 -2
- package/cjs/classes/winston.logger.class.js +103 -70
- package/cjs/constants/index.js +15 -4
- package/cjs/constants/message-keys.js +68 -0
- package/cjs/constants/permissions.js +287 -0
- package/cjs/decorators/api-response.decorator.js +2 -3
- package/cjs/decorators/index.js +1 -0
- package/cjs/decorators/log-action.decorator.js +149 -0
- package/cjs/decorators/require-permission.decorator.js +5 -5
- package/cjs/dtos/delete.dto.js +10 -0
- package/cjs/dtos/filter-and-pagination.dto.js +24 -34
- package/cjs/dtos/get-by-ids.dto.js +65 -0
- package/cjs/dtos/identity-response.dto.js +4 -8
- package/cjs/dtos/index.js +1 -0
- package/cjs/dtos/pagination.dto.js +4 -8
- package/cjs/dtos/response-payload.dto.js +68 -112
- package/cjs/entities/identity.js +12 -6
- package/cjs/entities/index.js +1 -0
- package/cjs/entities/raw-type.js +4 -0
- package/cjs/entities/user-root.js +15 -15
- package/cjs/enums/index.js +20 -0
- package/cjs/enums/notification-type.enum.js +17 -0
- package/cjs/enums/participant-status.enum.js +17 -0
- package/cjs/enums/recurrence-type.enum.js +18 -0
- package/cjs/exceptions/base-app.exception.js +151 -0
- package/cjs/exceptions/index.js +1 -0
- package/cjs/exceptions/permission.exception.js +13 -9
- package/cjs/filters/global-exception.filter.js +279 -0
- package/cjs/filters/index.js +18 -0
- package/cjs/guards/jwt-auth.guard.js +4 -1
- package/cjs/guards/permission.guard.js +88 -148
- package/cjs/index.js +2 -0
- package/cjs/interceptors/idempotency.interceptor.js +1 -1
- package/cjs/interceptors/index.js +1 -4
- package/cjs/interceptors/set-user-field-on-body.interceptor.js +60 -0
- package/cjs/interceptors/slug.interceptor.js +30 -9
- package/cjs/interfaces/datasource.interface.js +4 -0
- package/cjs/interfaces/event-manager-adapter.interface.js +11 -0
- package/cjs/interfaces/index.js +4 -2
- package/cjs/interfaces/module-config.interface.js +4 -0
- package/cjs/interfaces/notification-adapter.interface.js +11 -0
- package/cjs/interfaces/permission.interface.js +1 -1
- package/cjs/middlewares/logger.middleware.js +133 -115
- package/cjs/modules/cache/cache.module.js +3 -3
- package/cjs/modules/datasource/index.js +0 -1
- package/cjs/modules/datasource/multi-tenant-datasource.service.js +60 -183
- package/cjs/modules/utils/utils.service.js +27 -206
- package/cjs/utils/date-time.util.js +94 -0
- package/cjs/utils/html-sanitizer.util.js +64 -0
- package/cjs/utils/index.js +5 -1
- package/cjs/utils/query-helpers.util.js +60 -0
- package/cjs/utils/request.util.js +96 -0
- package/cjs/utils/string.util.js +63 -0
- package/classes/api-controller.class.d.ts +16 -8
- package/classes/api-service.class.d.ts +30 -25
- package/classes/handle-custome-db.d.ts +3 -0
- package/classes/index.d.ts +2 -2
- package/classes/winston.logger.class.d.ts +1 -0
- package/constants/index.d.ts +2 -1
- package/constants/message-keys.d.ts +45 -0
- package/constants/permissions.d.ts +306 -0
- package/decorators/api-response.decorator.d.ts +1 -1
- package/decorators/current-user.decorator.d.ts +1 -1
- package/decorators/index.d.ts +1 -0
- package/decorators/log-action.decorator.d.ts +8 -0
- package/decorators/require-permission.decorator.d.ts +2 -2
- package/dtos/delete.dto.d.ts +1 -0
- package/dtos/filter-and-pagination.dto.d.ts +0 -2
- package/dtos/get-by-ids.dto.d.ts +4 -0
- package/dtos/index.d.ts +1 -0
- package/dtos/response-payload.dto.d.ts +8 -20
- package/entities/index.d.ts +1 -0
- package/entities/raw-type.d.ts +1 -0
- package/enums/index.d.ts +3 -0
- package/enums/notification-type.enum.d.ts +6 -0
- package/enums/participant-status.enum.d.ts +6 -0
- package/enums/recurrence-type.enum.d.ts +7 -0
- package/exceptions/base-app.exception.d.ts +41 -0
- package/exceptions/index.d.ts +1 -0
- package/exceptions/permission.exception.d.ts +2 -2
- package/fesm/classes/api-controller.class.js +326 -50
- package/fesm/classes/api-service.class.js +274 -152
- package/fesm/classes/handle-custome-db.js +19 -0
- package/fesm/classes/index.js +3 -2
- package/fesm/classes/winston.logger.class.js +100 -68
- package/fesm/constants/index.js +4 -1
- package/fesm/constants/message-keys.js +49 -0
- package/fesm/constants/permissions.js +200 -0
- package/fesm/decorators/api-response.decorator.js +3 -21
- package/fesm/decorators/index.js +1 -0
- package/fesm/decorators/log-action.decorator.js +139 -0
- package/fesm/decorators/require-permission.decorator.js +17 -65
- package/fesm/dtos/delete.dto.js +12 -2
- package/fesm/dtos/filter-and-pagination.dto.js +26 -47
- package/fesm/dtos/get-by-ids.dto.js +55 -0
- package/fesm/dtos/identity-response.dto.js +4 -8
- package/fesm/dtos/index.js +1 -0
- package/fesm/dtos/pagination.dto.js +4 -8
- package/fesm/dtos/response-payload.dto.js +68 -103
- package/fesm/entities/identity.js +12 -6
- package/fesm/entities/index.js +1 -0
- package/fesm/entities/raw-type.js +1 -0
- package/fesm/entities/user-root.js +15 -15
- package/fesm/enums/index.js +3 -0
- package/fesm/enums/notification-type.enum.js +7 -0
- package/fesm/enums/participant-status.enum.js +7 -0
- package/fesm/enums/recurrence-type.enum.js +8 -0
- package/fesm/exceptions/base-app.exception.js +115 -0
- package/fesm/exceptions/index.js +1 -0
- package/fesm/exceptions/permission.exception.js +16 -18
- package/fesm/filters/global-exception.filter.js +269 -0
- package/fesm/filters/index.js +1 -0
- package/fesm/guards/jwt-auth.guard.js +5 -2
- package/fesm/guards/permission.guard.js +90 -150
- package/fesm/index.js +2 -0
- package/fesm/interceptors/idempotency.interceptor.js +2 -2
- package/fesm/interceptors/index.js +1 -4
- package/fesm/interceptors/set-user-field-on-body.interceptor.js +39 -0
- package/fesm/interceptors/slug.interceptor.js +31 -10
- package/fesm/interfaces/datasource.interface.js +1 -0
- package/fesm/interfaces/event-manager-adapter.interface.js +1 -0
- package/fesm/interfaces/index.js +4 -2
- package/fesm/interfaces/module-config.interface.js +5 -0
- package/fesm/interfaces/notification-adapter.interface.js +1 -0
- package/fesm/interfaces/permission.interface.js +1 -3
- package/fesm/middlewares/logger.middleware.js +133 -109
- package/fesm/modules/cache/cache.module.js +2 -2
- package/fesm/modules/datasource/index.js +0 -1
- package/fesm/modules/datasource/multi-tenant-datasource.service.js +61 -184
- package/fesm/modules/utils/utils.service.js +27 -206
- package/fesm/utils/date-time.util.js +70 -0
- package/fesm/utils/html-sanitizer.util.js +69 -0
- package/fesm/utils/index.js +5 -1
- package/fesm/utils/query-helpers.util.js +85 -0
- package/fesm/utils/request.util.js +81 -0
- package/fesm/utils/string.util.js +71 -0
- package/filters/global-exception.filter.d.ts +9 -0
- package/filters/index.d.ts +1 -0
- package/guards/permission.guard.d.ts +4 -6
- package/index.d.ts +2 -0
- package/interceptors/idempotency.interceptor.d.ts +1 -1
- package/interceptors/index.d.ts +1 -4
- package/interceptors/set-user-field-on-body.interceptor.d.ts +5 -0
- package/interceptors/slug.interceptor.d.ts +2 -1
- package/interfaces/api.interface.d.ts +4 -2
- package/interfaces/datasource.interface.d.ts +5 -0
- package/interfaces/event-manager-adapter.interface.d.ts +43 -0
- package/interfaces/identity.interface.d.ts +4 -4
- package/interfaces/index.d.ts +4 -2
- package/interfaces/logged-user-info.interface.d.ts +0 -2
- package/interfaces/module-config.interface.d.ts +6 -0
- package/interfaces/notification-adapter.interface.d.ts +21 -0
- package/interfaces/permission.interface.d.ts +13 -9
- package/middlewares/logger.middleware.d.ts +2 -2
- package/modules/datasource/index.d.ts +0 -1
- package/modules/datasource/multi-tenant-datasource.service.d.ts +5 -16
- package/modules/utils/utils.service.d.ts +3 -14
- package/package.json +21 -14
- package/utils/date-time.util.d.ts +8 -0
- package/utils/html-sanitizer.util.d.ts +2 -0
- package/utils/index.d.ts +5 -1
- package/utils/query-helpers.util.d.ts +16 -0
- package/utils/request.util.d.ts +6 -0
- package/utils/string.util.d.ts +2 -0
- package/cjs/classes/request-scoped-api.service.js +0 -61
- package/cjs/classes/winston-logger-adapter.class.js +0 -121
- package/cjs/interceptors/query-performance.interceptor.js +0 -66
- package/cjs/interceptors/set-create-by-on-body.interceptor.js +0 -40
- package/cjs/interceptors/set-delete-by-on-body.interceptor.js +0 -40
- package/cjs/interceptors/set-update-by-on-body.interceptor.js +0 -40
- package/cjs/interfaces/base-query.interface.js +0 -6
- package/cjs/interfaces/logger.interface.js +0 -7
- package/cjs/modules/datasource/datasource.module.js +0 -120
- package/cjs/utils/error-handler.util.js +0 -67
- package/classes/request-scoped-api.service.d.ts +0 -13
- package/classes/winston-logger-adapter.class.d.ts +0 -20
- package/fesm/classes/request-scoped-api.service.js +0 -51
- package/fesm/classes/winston-logger-adapter.class.js +0 -124
- package/fesm/interceptors/query-performance.interceptor.js +0 -56
- package/fesm/interceptors/set-create-by-on-body.interceptor.js +0 -30
- package/fesm/interceptors/set-delete-by-on-body.interceptor.js +0 -30
- package/fesm/interceptors/set-update-by-on-body.interceptor.js +0 -30
- package/fesm/interfaces/base-query.interface.js +0 -3
- package/fesm/interfaces/logger.interface.js +0 -4
- package/fesm/modules/datasource/datasource.module.js +0 -110
- package/fesm/utils/error-handler.util.js +0 -59
- package/interceptors/query-performance.interceptor.d.ts +0 -8
- package/interceptors/set-create-by-on-body.interceptor.d.ts +0 -5
- package/interceptors/set-delete-by-on-body.interceptor.d.ts +0 -5
- package/interceptors/set-update-by-on-body.interceptor.d.ts +0 -5
- package/interfaces/base-query.interface.d.ts +0 -7
- package/interfaces/logger.interface.d.ts +0 -7
- package/modules/datasource/datasource.module.d.ts +0 -18
- 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
|
|
66
|
-
if (security.
|
|
67
|
-
decorators.push(
|
|
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 === '
|
|
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: '
|
|
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 =
|
|
162
|
+
const data = toDto(responseDtoClass, entity);
|
|
113
163
|
return {
|
|
114
164
|
success: true,
|
|
115
|
-
message:
|
|
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)=>
|
|
182
|
+
const data = entities.map((item)=>toDto(responseDtoClass, item));
|
|
122
183
|
return {
|
|
123
184
|
success: true,
|
|
124
|
-
message: `${data.length}
|
|
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 =
|
|
210
|
+
const data = toDto(responseDtoClass, entity);
|
|
136
211
|
return {
|
|
137
212
|
success: true,
|
|
138
|
-
message:
|
|
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 =
|
|
257
|
+
const data = toDto(responseDtoClass, entity);
|
|
145
258
|
return {
|
|
146
259
|
success: true,
|
|
147
|
-
message:
|
|
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 =
|
|
277
|
+
const data = entities.map((item)=>toDto(responseDtoClass, item));
|
|
154
278
|
return {
|
|
155
279
|
success: true,
|
|
156
|
-
message: `${data.length}
|
|
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 =
|
|
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:
|
|
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' ? '
|
|
404
|
+
const action = deleteDto.type === 'restore' ? 'restore' : 'delete';
|
|
189
405
|
return {
|
|
190
406
|
success: true,
|
|
191
|
-
message: `${count}
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|