@flusys/nestjs-shared 1.1.0 → 1.2.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +139 -1103
- package/cjs/classes/api-controller.class.js +320 -29
- package/cjs/classes/api-service.class.js +219 -64
- package/cjs/classes/handle-custome-db.js +37 -0
- package/cjs/classes/index.js +1 -2
- package/cjs/classes/winston.logger.class.js +103 -70
- package/cjs/constants/index.js +1 -4
- package/cjs/constants/message-keys.js +68 -0
- package/cjs/constants/permissions.js +114 -11
- package/cjs/decorators/api-response.decorator.js +1 -2
- package/cjs/decorators/index.js +1 -1
- package/cjs/decorators/log-action.decorator.js +149 -0
- package/cjs/decorators/require-permission.decorator.js +5 -5
- package/cjs/dtos/get-by-ids.dto.js +65 -0
- package/cjs/dtos/identity-response.dto.js +4 -8
- package/cjs/dtos/index.js +1 -0
- package/cjs/dtos/response-payload.dto.js +72 -0
- package/cjs/entities/identity.js +8 -2
- package/cjs/entities/index.js +1 -0
- package/cjs/entities/raw-type.js +4 -0
- package/cjs/entities/user-root.js +2 -1
- package/cjs/enums/index.js +20 -0
- package/cjs/enums/notification-type.enum.js +17 -0
- package/cjs/enums/participant-status.enum.js +17 -0
- package/cjs/enums/recurrence-type.enum.js +18 -0
- package/cjs/exceptions/base-app.exception.js +151 -0
- package/cjs/exceptions/index.js +1 -0
- package/cjs/exceptions/permission.exception.js +13 -9
- package/cjs/filters/global-exception.filter.js +279 -0
- package/cjs/filters/index.js +18 -0
- package/cjs/guards/jwt-auth.guard.js +4 -1
- package/cjs/guards/permission.guard.js +71 -77
- package/cjs/index.js +2 -0
- package/cjs/interceptors/idempotency.interceptor.js +1 -1
- package/cjs/interceptors/index.js +0 -1
- package/cjs/interfaces/event-manager-adapter.interface.js +11 -0
- package/cjs/interfaces/index.js +2 -1
- package/cjs/interfaces/notification-adapter.interface.js +11 -0
- package/cjs/interfaces/permission.interface.js +1 -1
- package/cjs/middlewares/logger.middleware.js +83 -26
- package/cjs/modules/datasource/index.js +0 -1
- package/cjs/modules/datasource/multi-tenant-datasource.service.js +46 -85
- package/cjs/modules/utils/utils.service.js +4 -20
- package/cjs/utils/date-time.util.js +94 -0
- package/cjs/utils/index.js +1 -1
- package/cjs/utils/query-helpers.util.js +8 -1
- package/cjs/utils/request.util.js +25 -0
- package/classes/api-controller.class.d.ts +14 -6
- package/classes/api-service.class.d.ts +25 -22
- package/classes/handle-custome-db.d.ts +3 -0
- package/classes/index.d.ts +1 -2
- package/classes/winston.logger.class.d.ts +1 -0
- package/constants/index.d.ts +1 -1
- package/constants/message-keys.d.ts +45 -0
- package/constants/permissions.d.ts +127 -0
- package/decorators/api-response.decorator.d.ts +1 -1
- package/decorators/current-user.decorator.d.ts +1 -1
- package/decorators/index.d.ts +1 -1
- package/decorators/log-action.decorator.d.ts +8 -0
- package/decorators/require-permission.decorator.d.ts +2 -2
- package/dtos/get-by-ids.dto.d.ts +4 -0
- package/dtos/index.d.ts +1 -0
- package/dtos/response-payload.dto.d.ts +8 -0
- package/entities/index.d.ts +1 -0
- package/entities/raw-type.d.ts +1 -0
- package/enums/index.d.ts +3 -0
- package/enums/notification-type.enum.d.ts +6 -0
- package/enums/participant-status.enum.d.ts +6 -0
- package/enums/recurrence-type.enum.d.ts +7 -0
- package/exceptions/base-app.exception.d.ts +41 -0
- package/exceptions/index.d.ts +1 -0
- package/exceptions/permission.exception.d.ts +2 -2
- package/fesm/classes/api-controller.class.js +321 -30
- package/fesm/classes/api-service.class.js +222 -67
- package/fesm/classes/handle-custome-db.js +19 -0
- package/fesm/classes/index.js +1 -2
- package/fesm/classes/winston.logger.class.js +100 -68
- package/fesm/constants/index.js +2 -1
- package/fesm/constants/message-keys.js +49 -0
- package/fesm/constants/permissions.js +86 -14
- package/fesm/decorators/api-response.decorator.js +2 -20
- package/fesm/decorators/index.js +1 -1
- package/fesm/decorators/log-action.decorator.js +139 -0
- package/fesm/decorators/require-permission.decorator.js +17 -65
- package/fesm/dtos/get-by-ids.dto.js +55 -0
- package/fesm/dtos/identity-response.dto.js +4 -8
- package/fesm/dtos/index.js +1 -0
- package/fesm/dtos/response-payload.dto.js +72 -0
- package/fesm/entities/identity.js +8 -2
- package/fesm/entities/index.js +1 -0
- package/fesm/entities/raw-type.js +1 -0
- package/fesm/entities/user-root.js +2 -1
- package/fesm/enums/index.js +3 -0
- package/fesm/enums/notification-type.enum.js +7 -0
- package/fesm/enums/participant-status.enum.js +7 -0
- package/fesm/enums/recurrence-type.enum.js +8 -0
- package/fesm/exceptions/base-app.exception.js +115 -0
- package/fesm/exceptions/index.js +1 -0
- package/fesm/exceptions/permission.exception.js +16 -18
- package/fesm/filters/global-exception.filter.js +269 -0
- package/fesm/filters/index.js +1 -0
- package/fesm/guards/jwt-auth.guard.js +5 -2
- package/fesm/guards/permission.guard.js +73 -79
- package/fesm/index.js +2 -0
- package/fesm/interceptors/idempotency.interceptor.js +2 -2
- package/fesm/interceptors/index.js +0 -1
- package/fesm/interfaces/datasource.interface.js +1 -20
- package/fesm/interfaces/event-manager-adapter.interface.js +1 -0
- package/fesm/interfaces/index.js +2 -1
- package/fesm/interfaces/notification-adapter.interface.js +1 -0
- package/fesm/interfaces/permission.interface.js +1 -3
- package/fesm/middlewares/logger.middleware.js +83 -26
- package/fesm/modules/datasource/index.js +0 -1
- package/fesm/modules/datasource/multi-tenant-datasource.service.js +47 -86
- package/fesm/modules/utils/utils.service.js +5 -21
- package/fesm/utils/date-time.util.js +70 -0
- package/fesm/utils/index.js +1 -1
- package/fesm/utils/query-helpers.util.js +8 -1
- package/fesm/utils/request.util.js +22 -0
- package/filters/global-exception.filter.d.ts +9 -0
- package/filters/index.d.ts +1 -0
- package/guards/permission.guard.d.ts +3 -7
- package/index.d.ts +2 -0
- package/interceptors/idempotency.interceptor.d.ts +1 -1
- package/interceptors/index.d.ts +0 -1
- package/interfaces/api.interface.d.ts +2 -0
- package/interfaces/event-manager-adapter.interface.d.ts +43 -0
- package/interfaces/index.d.ts +2 -1
- package/interfaces/notification-adapter.interface.d.ts +21 -0
- package/interfaces/permission.interface.d.ts +13 -8
- package/modules/datasource/index.d.ts +0 -1
- package/modules/datasource/multi-tenant-datasource.service.d.ts +5 -15
- package/modules/utils/utils.service.d.ts +0 -1
- package/package.json +21 -14
- package/utils/date-time.util.d.ts +8 -0
- package/utils/index.d.ts +1 -1
- package/utils/request.util.d.ts +2 -0
- package/cjs/classes/request-scoped-api.service.js +0 -61
- package/cjs/classes/winston-logger-adapter.class.js +0 -104
- package/cjs/decorators/sanitize-html.decorator.js +0 -36
- package/cjs/interceptors/query-performance.interceptor.js +0 -66
- package/cjs/interfaces/logger.interface.js +0 -7
- package/cjs/modules/datasource/datasource.module.js +0 -117
- package/cjs/utils/error-handler.util.js +0 -90
- package/classes/request-scoped-api.service.d.ts +0 -14
- package/classes/winston-logger-adapter.class.d.ts +0 -22
- package/decorators/sanitize-html.decorator.d.ts +0 -2
- package/fesm/classes/request-scoped-api.service.js +0 -51
- package/fesm/classes/winston-logger-adapter.class.js +0 -107
- package/fesm/decorators/sanitize-html.decorator.js +0 -45
- package/fesm/interceptors/query-performance.interceptor.js +0 -56
- package/fesm/interfaces/logger.interface.js +0 -4
- package/fesm/modules/datasource/datasource.module.js +0 -107
- package/fesm/utils/error-handler.util.js +0 -82
- package/interceptors/query-performance.interceptor.d.ts +0 -8
- package/interfaces/logger.interface.d.ts +0 -7
- package/modules/datasource/datasource.module.d.ts +0 -19
- package/utils/error-handler.util.d.ts +0 -19
|
@@ -54,18 +54,15 @@ function _ts_decorate(decorators, target, key, desc) {
|
|
|
54
54
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
55
55
|
}
|
|
56
56
|
// Configuration
|
|
57
|
-
const
|
|
57
|
+
const logConfig = _config.envConfig.getLogConfig();
|
|
58
|
+
const IS_DEBUG = logConfig.level === 'debug';
|
|
59
|
+
const DISABLE_HTTP_LOGGING = logConfig.disableHttpLogging;
|
|
58
60
|
const TENANT_ID_HEADER = 'x-tenant-id';
|
|
59
61
|
const EXCLUDED_PATHS = [
|
|
60
62
|
'/health',
|
|
61
63
|
'/metrics',
|
|
62
64
|
'/favicon.ico'
|
|
63
65
|
];
|
|
64
|
-
const EXCLUDED_HEADERS = [
|
|
65
|
-
'authorization',
|
|
66
|
-
'cookie',
|
|
67
|
-
'x-api-key'
|
|
68
|
-
];
|
|
69
66
|
const MAX_BODY_LOG_SIZE = 1000;
|
|
70
67
|
const requestContext = new _async_hooks.AsyncLocalStorage();
|
|
71
68
|
const getRequestId = ()=>requestContext.getStore()?.requestId;
|
|
@@ -73,17 +70,71 @@ const getTenantId = ()=>requestContext.getStore()?.tenantId;
|
|
|
73
70
|
const getUserId = ()=>requestContext.getStore()?.userId;
|
|
74
71
|
const getCompanyId = ()=>requestContext.getStore()?.companyId;
|
|
75
72
|
// Helper Functions
|
|
76
|
-
function
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
73
|
+
function summarizeRequestBody(body) {
|
|
74
|
+
if (!body) return undefined;
|
|
75
|
+
const parsed = typeof body === 'string' ? tryParseJson(body) : body;
|
|
76
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
77
|
+
const str = String(body);
|
|
78
|
+
return str.length > MAX_BODY_LOG_SIZE ? str.substring(0, MAX_BODY_LOG_SIZE) + '...' : str;
|
|
79
|
+
}
|
|
80
|
+
// For API request bodies, provide a concise summary
|
|
81
|
+
const summary = {};
|
|
82
|
+
// Pagination info
|
|
83
|
+
if (parsed.pagination) {
|
|
84
|
+
summary.page = parsed.pagination.currentPage;
|
|
85
|
+
summary.pageSize = parsed.pagination.pageSize;
|
|
86
|
+
}
|
|
87
|
+
// Filter keys only (not values - may contain sensitive data)
|
|
88
|
+
if (parsed.filter && typeof parsed.filter === 'object') {
|
|
89
|
+
const filterKeys = Object.keys(parsed.filter);
|
|
90
|
+
if (filterKeys.length > 0) summary.filterKeys = filterKeys;
|
|
91
|
+
}
|
|
92
|
+
// Select fields count
|
|
93
|
+
if (Array.isArray(parsed.select) && parsed.select.length > 0) {
|
|
94
|
+
summary.selectCount = parsed.select.length;
|
|
95
|
+
}
|
|
96
|
+
// Sort keys
|
|
97
|
+
if (parsed.sort && typeof parsed.sort === 'object') {
|
|
98
|
+
const sortKeys = Object.keys(parsed.sort);
|
|
99
|
+
if (sortKeys.length > 0) summary.sortKeys = sortKeys;
|
|
80
100
|
}
|
|
81
|
-
|
|
101
|
+
// ID for single item operations
|
|
102
|
+
if (parsed.id) summary.id = parsed.id;
|
|
103
|
+
// IDs for bulk operations
|
|
104
|
+
if (Array.isArray(parsed.ids)) summary.idsCount = parsed.ids.length;
|
|
105
|
+
return Object.keys(summary).length > 0 ? JSON.stringify(summary) : '{}';
|
|
82
106
|
}
|
|
83
|
-
function
|
|
107
|
+
function summarizeResponseBody(body) {
|
|
84
108
|
if (!body) return undefined;
|
|
85
|
-
const
|
|
86
|
-
|
|
109
|
+
const parsed = typeof body === 'string' ? tryParseJson(body) : body;
|
|
110
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
111
|
+
const str = String(body);
|
|
112
|
+
return str.length > MAX_BODY_LOG_SIZE ? str.substring(0, MAX_BODY_LOG_SIZE) + '...' : str;
|
|
113
|
+
}
|
|
114
|
+
const summary = {};
|
|
115
|
+
if ('success' in parsed) summary.success = parsed.success;
|
|
116
|
+
if ('message' in parsed) summary.message = parsed.message;
|
|
117
|
+
if ('code' in parsed) summary.code = parsed.code;
|
|
118
|
+
if (Array.isArray(parsed.data)) {
|
|
119
|
+
summary.dataCount = parsed.data.length;
|
|
120
|
+
} else if (parsed.data && typeof parsed.data === 'object') {
|
|
121
|
+
summary.hasData = true;
|
|
122
|
+
}
|
|
123
|
+
if (parsed.meta) {
|
|
124
|
+
summary.total = parsed.meta.total;
|
|
125
|
+
summary.page = parsed.meta.page;
|
|
126
|
+
}
|
|
127
|
+
if (parsed.errors) {
|
|
128
|
+
summary.errorsCount = Array.isArray(parsed.errors) ? parsed.errors.length : 1;
|
|
129
|
+
}
|
|
130
|
+
return JSON.stringify(summary);
|
|
131
|
+
}
|
|
132
|
+
function tryParseJson(str) {
|
|
133
|
+
try {
|
|
134
|
+
return JSON.parse(str);
|
|
135
|
+
} catch {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
87
138
|
}
|
|
88
139
|
function getClientIp(req) {
|
|
89
140
|
return req.headers['x-forwarded-for']?.split(',')[0] || req.socket?.remoteAddress || 'unknown';
|
|
@@ -100,7 +151,7 @@ let LoggerMiddleware = class LoggerMiddleware {
|
|
|
100
151
|
startTime
|
|
101
152
|
};
|
|
102
153
|
requestContext.run(context, ()=>{
|
|
103
|
-
const shouldSkipLogging = EXCLUDED_PATHS.some((path)=>req.originalUrl.startsWith(path));
|
|
154
|
+
const shouldSkipLogging = DISABLE_HTTP_LOGGING || EXCLUDED_PATHS.some((path)=>req.originalUrl.startsWith(path));
|
|
104
155
|
if (!shouldSkipLogging) {
|
|
105
156
|
this.logRequest(req, requestId, tenantId);
|
|
106
157
|
this.setupResponseLogging(req, res, startTime, requestId, tenantId);
|
|
@@ -141,20 +192,22 @@ let LoggerMiddleware = class LoggerMiddleware {
|
|
|
141
192
|
});
|
|
142
193
|
}
|
|
143
194
|
logRequest(req, requestId, tenantId) {
|
|
195
|
+
const ip = getClientIp(req);
|
|
196
|
+
const contentType = req.headers['content-type'] || 'none';
|
|
197
|
+
// Build descriptive message like: Started processing request "POST" "/api/users" from "192.168.1.1"
|
|
198
|
+
const message = `Started processing request "${req.method}" "${req.originalUrl}" from "${ip}"`;
|
|
144
199
|
const logData = {
|
|
145
200
|
...this.buildBaseLogData(req, requestId, tenantId),
|
|
146
201
|
query: Object.keys(req.query).length > 0 ? req.query : undefined,
|
|
147
|
-
ip
|
|
202
|
+
ip,
|
|
148
203
|
userAgent: req.headers['user-agent'],
|
|
149
|
-
contentType
|
|
204
|
+
contentType,
|
|
150
205
|
contentLength: req.headers['content-length']
|
|
151
206
|
};
|
|
152
207
|
if (IS_DEBUG) {
|
|
153
|
-
logData.
|
|
154
|
-
logData.body = truncateBody(req.body);
|
|
155
|
-
logData.params = Object.keys(req.params).length > 0 ? req.params : undefined;
|
|
208
|
+
logData.body = summarizeRequestBody(req.body);
|
|
156
209
|
}
|
|
157
|
-
this.logger.info(
|
|
210
|
+
this.logger.info(message, logData);
|
|
158
211
|
}
|
|
159
212
|
logResponse(req, res, startTime, body, requestId, tenantId) {
|
|
160
213
|
const duration = Date.now() - startTime;
|
|
@@ -162,6 +215,10 @@ let LoggerMiddleware = class LoggerMiddleware {
|
|
|
162
215
|
const level = statusCode >= 500 ? 'error' : statusCode >= 400 ? 'warn' : 'info';
|
|
163
216
|
const userId = getUserId();
|
|
164
217
|
const companyId = getCompanyId();
|
|
218
|
+
const contentType = res.getHeader('content-type') || 'none';
|
|
219
|
+
const contentLength = res.getHeader('content-length') || 'null';
|
|
220
|
+
// Build descriptive message like: Request finished "HTTP/1.1" "POST" "/api/users" - 200 "application/json" 534.29ms
|
|
221
|
+
const message = `Request finished "HTTP/1.1" "${req.method}" "${req.originalUrl}" - ${statusCode} ${contentLength} "${contentType}" ${duration.toFixed(2)}ms`;
|
|
165
222
|
const logData = {
|
|
166
223
|
...this.buildBaseLogData(req, requestId, tenantId),
|
|
167
224
|
userId,
|
|
@@ -170,15 +227,15 @@ let LoggerMiddleware = class LoggerMiddleware {
|
|
|
170
227
|
statusMessage: res.statusMessage,
|
|
171
228
|
duration: `${duration}ms`,
|
|
172
229
|
durationMs: duration,
|
|
173
|
-
contentType
|
|
174
|
-
contentLength
|
|
230
|
+
contentType,
|
|
231
|
+
contentLength
|
|
175
232
|
};
|
|
176
233
|
if (statusCode >= 400 || IS_DEBUG) {
|
|
177
|
-
logData.responseBody =
|
|
234
|
+
logData.responseBody = summarizeResponseBody(body);
|
|
178
235
|
}
|
|
179
|
-
this.logger.log(level,
|
|
236
|
+
this.logger.log(level, message, logData);
|
|
180
237
|
if (duration > 3000 && statusCode < 400) {
|
|
181
|
-
this.logger.warn(
|
|
238
|
+
this.logger.warn(`Slow request detected: "${req.method}" "${req.originalUrl}" took ${duration}ms (threshold: 3000ms)`, {
|
|
182
239
|
...this.buildBaseLogData(req, requestId, tenantId),
|
|
183
240
|
userId,
|
|
184
241
|
companyId,
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", {
|
|
3
3
|
value: true
|
|
4
4
|
});
|
|
5
|
-
_export_star(require("./datasource.module"), exports);
|
|
6
5
|
_export_star(require("./multi-tenant-datasource.service"), exports);
|
|
7
6
|
function _export_star(from, to) {
|
|
8
7
|
Object.keys(from).forEach(function(k) {
|
|
@@ -10,6 +10,7 @@ Object.defineProperty(exports, "MultiTenantDataSourceService", {
|
|
|
10
10
|
});
|
|
11
11
|
const _nestjscore = require("@flusys/nestjs-core");
|
|
12
12
|
const _common = require("@nestjs/common");
|
|
13
|
+
const _constants = require("../../constants");
|
|
13
14
|
const _core = require("@nestjs/core");
|
|
14
15
|
const _express = require("express");
|
|
15
16
|
const _typeorm = require("typeorm");
|
|
@@ -42,21 +43,21 @@ function _ts_param(paramIndex, decorator) {
|
|
|
42
43
|
};
|
|
43
44
|
}
|
|
44
45
|
let MultiTenantDataSourceService = class MultiTenantDataSourceService {
|
|
46
|
+
get self() {
|
|
47
|
+
return this.constructor;
|
|
48
|
+
}
|
|
45
49
|
initializeFromOptions() {
|
|
46
50
|
if (!this.options) return;
|
|
47
|
-
if (!
|
|
51
|
+
if (!this.self.initialized) {
|
|
48
52
|
this.registerTenants();
|
|
49
|
-
|
|
53
|
+
this.self.initialized = true;
|
|
50
54
|
}
|
|
51
55
|
}
|
|
52
56
|
registerTenants() {
|
|
53
57
|
this.options?.tenants?.forEach((tenant)=>{
|
|
54
|
-
|
|
58
|
+
this.self.tenantsRegistry.set(tenant.id, tenant);
|
|
55
59
|
});
|
|
56
60
|
}
|
|
57
|
-
setTenantHeader(header) {
|
|
58
|
-
this.tenantHeader = header;
|
|
59
|
-
}
|
|
60
61
|
getDatabaseMode() {
|
|
61
62
|
return this.options?.bootstrapAppConfig?.databaseMode ?? 'single';
|
|
62
63
|
}
|
|
@@ -68,7 +69,10 @@ let MultiTenantDataSourceService = class MultiTenantDataSourceService {
|
|
|
68
69
|
const tenantId = this.request.headers[this.tenantHeader];
|
|
69
70
|
if (!tenantId) return null;
|
|
70
71
|
if (!/^[a-zA-Z0-9_-]+$/.test(tenantId)) {
|
|
71
|
-
throw new _common.BadRequestException(
|
|
72
|
+
throw new _common.BadRequestException({
|
|
73
|
+
message: 'Invalid tenant ID format',
|
|
74
|
+
messageKey: _constants.SYSTEM_MESSAGES.INVALID_TENANT_ID
|
|
75
|
+
});
|
|
72
76
|
}
|
|
73
77
|
return tenantId;
|
|
74
78
|
}
|
|
@@ -77,124 +81,78 @@ let MultiTenantDataSourceService = class MultiTenantDataSourceService {
|
|
|
77
81
|
return tenantId ? this.getTenant(tenantId) : null;
|
|
78
82
|
}
|
|
79
83
|
getTenant(tenantId) {
|
|
80
|
-
return
|
|
81
|
-
}
|
|
82
|
-
getAllTenants() {
|
|
83
|
-
return Array.from(MultiTenantDataSourceService.tenantsRegistry.values());
|
|
84
|
-
}
|
|
85
|
-
getActiveTenants() {
|
|
86
|
-
return this.getAllTenants();
|
|
84
|
+
return this.self.tenantsRegistry.get(tenantId) ?? null;
|
|
87
85
|
}
|
|
88
86
|
async getDataSource() {
|
|
89
87
|
return this.isMultiTenant() ? this.getTenantDataSource() : this.getSingleDataSource();
|
|
90
88
|
}
|
|
91
|
-
async getDataSourceForTenant(tenantId) {
|
|
92
|
-
const tenant = this.getTenant(tenantId);
|
|
93
|
-
if (!tenant) throw new Error(`Tenant '${tenantId}' not found`);
|
|
94
|
-
return this.getOrCreateTenantConnection(tenant);
|
|
95
|
-
}
|
|
96
|
-
setDataSource(dataSource) {
|
|
97
|
-
MultiTenantDataSourceService.singleDataSource = dataSource;
|
|
98
|
-
}
|
|
99
89
|
async getRepository(entity) {
|
|
100
90
|
const dataSource = await this.getDataSource();
|
|
101
91
|
return dataSource.getRepository(entity);
|
|
102
92
|
}
|
|
103
|
-
async withTenant(tenantId, callback) {
|
|
104
|
-
const dataSource = await this.getDataSourceForTenant(tenantId);
|
|
105
|
-
return callback(dataSource);
|
|
106
|
-
}
|
|
107
|
-
async forAllTenants(callback) {
|
|
108
|
-
const results = new Map();
|
|
109
|
-
for (const tenant of this.getActiveTenants()){
|
|
110
|
-
try {
|
|
111
|
-
const dataSource = await this.getOrCreateTenantConnection(tenant);
|
|
112
|
-
results.set(tenant.id, await callback(tenant, dataSource));
|
|
113
|
-
} catch (error) {
|
|
114
|
-
this.logger.error(`Error processing tenant ${tenant.id}: ${error}`);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
return results;
|
|
118
|
-
}
|
|
119
|
-
registerTenant(tenant) {
|
|
120
|
-
MultiTenantDataSourceService.tenantsRegistry.set(tenant.id, tenant);
|
|
121
|
-
}
|
|
122
|
-
async removeTenant(tenantId) {
|
|
123
|
-
await this.closeTenantConnection(tenantId);
|
|
124
|
-
MultiTenantDataSourceService.tenantsRegistry.delete(tenantId);
|
|
125
|
-
}
|
|
126
93
|
async closeTenantConnection(tenantId) {
|
|
127
|
-
const connection =
|
|
94
|
+
const connection = this.self.tenantConnections.get(tenantId);
|
|
128
95
|
if (connection?.isInitialized) {
|
|
129
96
|
await connection.destroy();
|
|
130
|
-
|
|
131
|
-
this.logger.log(`Closed connection for tenant: ${tenantId}`);
|
|
97
|
+
this.self.tenantConnections.delete(tenantId);
|
|
132
98
|
}
|
|
133
99
|
}
|
|
134
100
|
async onModuleDestroy() {
|
|
135
|
-
for (const [tenantId] of
|
|
101
|
+
for (const [tenantId] of this.self.tenantConnections){
|
|
136
102
|
await this.closeTenantConnection(tenantId);
|
|
137
103
|
}
|
|
138
104
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
MultiTenantDataSourceService.singleConnectionLock = null;
|
|
143
|
-
MultiTenantDataSourceService.tenantConnections.clear();
|
|
144
|
-
MultiTenantDataSourceService.tenantsRegistry.clear();
|
|
145
|
-
MultiTenantDataSourceService.connectionLocks.clear();
|
|
146
|
-
}
|
|
147
|
-
// Protected Methods (for subclasses)
|
|
148
|
-
/**
|
|
149
|
-
* Get single database DataSource with connection locking to prevent race conditions
|
|
150
|
-
*/ async getSingleDataSource() {
|
|
151
|
-
// Return existing initialized connection
|
|
152
|
-
if (MultiTenantDataSourceService.singleDataSource?.isInitialized) {
|
|
153
|
-
return MultiTenantDataSourceService.singleDataSource;
|
|
105
|
+
async getSingleDataSource() {
|
|
106
|
+
if (this.self.singleDataSource?.isInitialized) {
|
|
107
|
+
return this.self.singleDataSource;
|
|
154
108
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
return MultiTenantDataSourceService.singleConnectionLock;
|
|
109
|
+
if (this.self.singleConnectionLock) {
|
|
110
|
+
return this.self.singleConnectionLock;
|
|
158
111
|
}
|
|
159
112
|
const config = this.getDefaultDatabaseConfig();
|
|
160
113
|
if (!config) {
|
|
161
|
-
throw new
|
|
114
|
+
throw new _common.InternalServerErrorException({
|
|
115
|
+
message: 'No database config available. Provide defaultDatabaseConfig or tenantDefaultDatabaseConfig.',
|
|
116
|
+
messageKey: _constants.SYSTEM_MESSAGES.DATABASE_CONFIG_NOT_AVAILABLE
|
|
117
|
+
});
|
|
162
118
|
}
|
|
163
|
-
// Create connection with lock to prevent race conditions
|
|
164
119
|
const connectionPromise = this.createDataSourceFromConfig(config);
|
|
165
|
-
|
|
120
|
+
this.self.singleConnectionLock = connectionPromise;
|
|
166
121
|
try {
|
|
167
122
|
const dataSource = await connectionPromise;
|
|
168
|
-
|
|
123
|
+
this.self.singleDataSource = dataSource;
|
|
169
124
|
return dataSource;
|
|
170
125
|
} finally{
|
|
171
|
-
|
|
126
|
+
this.self.singleConnectionLock = null;
|
|
172
127
|
}
|
|
173
128
|
}
|
|
174
|
-
|
|
175
|
-
* Get tenant-specific DataSource
|
|
176
|
-
*/ async getTenantDataSource() {
|
|
129
|
+
async getTenantDataSource() {
|
|
177
130
|
const tenant = this.getCurrentTenant();
|
|
178
131
|
if (!tenant) {
|
|
179
|
-
throw new
|
|
132
|
+
throw new _common.BadRequestException({
|
|
133
|
+
message: `Tenant not found. Ensure '${this.tenantHeader}' header is set.`,
|
|
134
|
+
messageKey: _constants.SYSTEM_MESSAGES.TENANT_HEADER_REQUIRED,
|
|
135
|
+
messageVariables: {
|
|
136
|
+
header: this.tenantHeader
|
|
137
|
+
}
|
|
138
|
+
});
|
|
180
139
|
}
|
|
181
140
|
return this.getOrCreateTenantConnection(tenant);
|
|
182
141
|
}
|
|
183
142
|
async getOrCreateTenantConnection(tenant) {
|
|
184
|
-
const existing =
|
|
143
|
+
const existing = this.self.tenantConnections.get(tenant.id);
|
|
185
144
|
if (existing?.isInitialized) return existing;
|
|
186
|
-
const pendingConnection =
|
|
145
|
+
const pendingConnection = this.self.connectionLocks.get(tenant.id);
|
|
187
146
|
if (pendingConnection) return pendingConnection;
|
|
188
147
|
const config = this.buildTenantDatabaseConfig(tenant);
|
|
189
148
|
const connectionPromise = this.createDataSourceFromConfig(config);
|
|
190
|
-
|
|
149
|
+
this.self.connectionLocks.set(tenant.id, connectionPromise);
|
|
191
150
|
try {
|
|
192
151
|
const dataSource = await connectionPromise;
|
|
193
|
-
|
|
194
|
-
this.logger.log(`Created connection for tenant: ${tenant.id}`);
|
|
152
|
+
this.self.tenantConnections.set(tenant.id, dataSource);
|
|
195
153
|
return dataSource;
|
|
196
154
|
} finally{
|
|
197
|
-
|
|
155
|
+
this.self.connectionLocks.delete(tenant.id);
|
|
198
156
|
}
|
|
199
157
|
}
|
|
200
158
|
getDefaultDatabaseConfig() {
|
|
@@ -202,7 +160,12 @@ let MultiTenantDataSourceService = class MultiTenantDataSourceService {
|
|
|
202
160
|
}
|
|
203
161
|
buildTenantDatabaseConfig(tenant) {
|
|
204
162
|
const defaultConfig = this.getDefaultDatabaseConfig();
|
|
205
|
-
if (!defaultConfig)
|
|
163
|
+
if (!defaultConfig) {
|
|
164
|
+
throw new _common.InternalServerErrorException({
|
|
165
|
+
message: 'No default database config for multi-tenant mode.',
|
|
166
|
+
messageKey: _constants.SYSTEM_MESSAGES.DATABASE_CONFIG_NOT_AVAILABLE
|
|
167
|
+
});
|
|
168
|
+
}
|
|
206
169
|
return {
|
|
207
170
|
type: defaultConfig.type,
|
|
208
171
|
host: tenant.host ?? defaultConfig.host,
|
|
@@ -230,11 +193,9 @@ let MultiTenantDataSourceService = class MultiTenantDataSourceService {
|
|
|
230
193
|
constructor(options, request){
|
|
231
194
|
_define_property(this, "options", void 0);
|
|
232
195
|
_define_property(this, "request", void 0);
|
|
233
|
-
_define_property(this, "logger", void 0);
|
|
234
196
|
_define_property(this, "tenantHeader", void 0);
|
|
235
197
|
this.options = options;
|
|
236
198
|
this.request = request;
|
|
237
|
-
this.logger = new _common.Logger(this.constructor.name);
|
|
238
199
|
this.tenantHeader = _nestjscore.DEFAULT_TENANT_HEADER;
|
|
239
200
|
this.initializeFromOptions();
|
|
240
201
|
}
|
|
@@ -9,19 +9,6 @@ Object.defineProperty(exports, "UtilsService", {
|
|
|
9
9
|
}
|
|
10
10
|
});
|
|
11
11
|
const _common = require("@nestjs/common");
|
|
12
|
-
function _define_property(obj, key, value) {
|
|
13
|
-
if (key in obj) {
|
|
14
|
-
Object.defineProperty(obj, key, {
|
|
15
|
-
value: value,
|
|
16
|
-
enumerable: true,
|
|
17
|
-
configurable: true,
|
|
18
|
-
writable: true
|
|
19
|
-
});
|
|
20
|
-
} else {
|
|
21
|
-
obj[key] = value;
|
|
22
|
-
}
|
|
23
|
-
return obj;
|
|
24
|
-
}
|
|
25
12
|
function _ts_decorate(decorators, target, key, desc) {
|
|
26
13
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
27
14
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -47,8 +34,8 @@ let UtilsService = class UtilsService {
|
|
|
47
34
|
const keys = await cacheManager.get(trackingKey) || [];
|
|
48
35
|
if (!keys.includes(cacheKey)) keys.push(cacheKey);
|
|
49
36
|
await cacheManager.set(trackingKey, keys);
|
|
50
|
-
} catch
|
|
51
|
-
|
|
37
|
+
} catch {
|
|
38
|
+
// Silent failure - cache tracking is non-critical
|
|
52
39
|
}
|
|
53
40
|
}
|
|
54
41
|
/**
|
|
@@ -59,8 +46,8 @@ let UtilsService = class UtilsService {
|
|
|
59
46
|
const keys = await cacheManager.get(trackingKey) || [];
|
|
60
47
|
await Promise.allSettled(keys.map((key)=>cacheManager.del(key)));
|
|
61
48
|
await cacheManager.del(trackingKey);
|
|
62
|
-
} catch
|
|
63
|
-
|
|
49
|
+
} catch {
|
|
50
|
+
// Silent failure - cache invalidation is non-critical
|
|
64
51
|
}
|
|
65
52
|
}
|
|
66
53
|
// ---------------- STRING HELPERS ----------------
|
|
@@ -83,9 +70,6 @@ let UtilsService = class UtilsService {
|
|
|
83
70
|
const prefix = this.buildTenantPrefix(tenantId);
|
|
84
71
|
return entityId ? `${prefix}entity_${entityName}_id_${entityId}_keys` : `${prefix}entity_${entityName}_keys`;
|
|
85
72
|
}
|
|
86
|
-
constructor(){
|
|
87
|
-
_define_property(this, "logger", new _common.Logger(UtilsService.name));
|
|
88
|
-
}
|
|
89
73
|
};
|
|
90
74
|
UtilsService = _ts_decorate([
|
|
91
75
|
(0, _common.Injectable)()
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
function _export(target, all) {
|
|
6
|
+
for(var name in all)Object.defineProperty(target, name, {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
get: Object.getOwnPropertyDescriptor(all, name).get
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
_export(exports, {
|
|
12
|
+
get formatInTimezone () {
|
|
13
|
+
return formatInTimezone;
|
|
14
|
+
},
|
|
15
|
+
get getTodayInTimezone () {
|
|
16
|
+
return getTodayInTimezone;
|
|
17
|
+
},
|
|
18
|
+
get localToUtc () {
|
|
19
|
+
return localToUtc;
|
|
20
|
+
},
|
|
21
|
+
get utcToLocal () {
|
|
22
|
+
return utcToLocal;
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
function getOffsetMinutes(utcDate, timezone) {
|
|
26
|
+
const extract = (tz)=>{
|
|
27
|
+
const parts = new Intl.DateTimeFormat('en-US', {
|
|
28
|
+
timeZone: tz,
|
|
29
|
+
year: 'numeric',
|
|
30
|
+
month: '2-digit',
|
|
31
|
+
day: '2-digit',
|
|
32
|
+
hour: '2-digit',
|
|
33
|
+
minute: '2-digit',
|
|
34
|
+
second: '2-digit',
|
|
35
|
+
hour12: false
|
|
36
|
+
}).formatToParts(utcDate);
|
|
37
|
+
const p = Object.fromEntries(parts.filter((x)=>x.type !== 'literal').map((x)=>[
|
|
38
|
+
x.type,
|
|
39
|
+
parseInt(x.value)
|
|
40
|
+
]));
|
|
41
|
+
return Date.UTC(p['year'], p['month'] - 1, p['day'], p['hour'], p['minute'], p['second']);
|
|
42
|
+
};
|
|
43
|
+
return (extract(timezone) - extract('UTC')) / 60_000;
|
|
44
|
+
}
|
|
45
|
+
function localToUtc(dateStr, timeStr, timezone) {
|
|
46
|
+
const [year, month, day] = dateStr.split('-').map(Number);
|
|
47
|
+
const [hours, minutes] = timeStr.split(':').map(Number);
|
|
48
|
+
const approx = new Date(Date.UTC(year, month - 1, day, hours, minutes, 0));
|
|
49
|
+
const offset = getOffsetMinutes(approx, timezone);
|
|
50
|
+
const utcDate = new Date(approx.getTime() - offset * 60_000);
|
|
51
|
+
// DST boundary guard: re-check offset after adjustment
|
|
52
|
+
const finalOffset = getOffsetMinutes(utcDate, timezone);
|
|
53
|
+
return finalOffset !== offset ? new Date(approx.getTime() - finalOffset * 60_000) : utcDate;
|
|
54
|
+
}
|
|
55
|
+
function utcToLocal(utcDate, timezone) {
|
|
56
|
+
const d = typeof utcDate === 'string' ? new Date(utcDate) : utcDate;
|
|
57
|
+
const parts = new Intl.DateTimeFormat('en-CA', {
|
|
58
|
+
timeZone: timezone,
|
|
59
|
+
year: 'numeric',
|
|
60
|
+
month: '2-digit',
|
|
61
|
+
day: '2-digit',
|
|
62
|
+
hour: '2-digit',
|
|
63
|
+
minute: '2-digit',
|
|
64
|
+
hour12: false
|
|
65
|
+
}).formatToParts(d);
|
|
66
|
+
const p = Object.fromEntries(parts.filter((x)=>x.type !== 'literal').map((x)=>[
|
|
67
|
+
x.type,
|
|
68
|
+
x.value
|
|
69
|
+
]));
|
|
70
|
+
// en-CA formats midnight hour as '24' — normalize to '00'
|
|
71
|
+
const hour = p['hour'] === '24' ? '00' : p['hour'];
|
|
72
|
+
return {
|
|
73
|
+
date: `${p['year']}-${p['month']}-${p['day']}`,
|
|
74
|
+
time: `${hour}:${p['minute']}`,
|
|
75
|
+
display: new Intl.DateTimeFormat('en-US', {
|
|
76
|
+
timeZone: timezone,
|
|
77
|
+
dateStyle: 'medium',
|
|
78
|
+
timeStyle: 'short'
|
|
79
|
+
}).format(d)
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function formatInTimezone(utcDate, timezone, options = {
|
|
83
|
+
dateStyle: 'medium',
|
|
84
|
+
timeStyle: 'short'
|
|
85
|
+
}) {
|
|
86
|
+
const d = typeof utcDate === 'string' ? new Date(utcDate) : utcDate;
|
|
87
|
+
return new Intl.DateTimeFormat('en-US', {
|
|
88
|
+
...options,
|
|
89
|
+
timeZone: timezone
|
|
90
|
+
}).format(d);
|
|
91
|
+
}
|
|
92
|
+
function getTodayInTimezone(timezone) {
|
|
93
|
+
return utcToLocal(new Date(), timezone).date;
|
|
94
|
+
}
|
package/cjs/utils/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", {
|
|
3
3
|
value: true
|
|
4
4
|
});
|
|
5
|
-
_export_star(require("./
|
|
5
|
+
_export_star(require("./date-time.util"), exports);
|
|
6
6
|
_export_star(require("./html-sanitizer.util"), exports);
|
|
7
7
|
_export_star(require("./query-helpers.util"), exports);
|
|
8
8
|
_export_star(require("./request.util"), exports);
|
|
@@ -23,6 +23,7 @@ _export(exports, {
|
|
|
23
23
|
}
|
|
24
24
|
});
|
|
25
25
|
const _common = require("@nestjs/common");
|
|
26
|
+
const _constants = require("../constants");
|
|
26
27
|
function applyCompanyFilter(query, config, user) {
|
|
27
28
|
const columnName = config.columnName ?? 'companyId';
|
|
28
29
|
if (config.isCompanyFeatureEnabled && user?.companyId) {
|
|
@@ -47,7 +48,13 @@ function hasCompanyId(entity) {
|
|
|
47
48
|
function validateCompanyOwnership(entity, user, isCompanyFeatureEnabled, entityName) {
|
|
48
49
|
if (isCompanyFeatureEnabled && user?.companyId && hasCompanyId(entity)) {
|
|
49
50
|
if (entity.companyId && entity.companyId !== user.companyId) {
|
|
50
|
-
throw new _common.BadRequestException(
|
|
51
|
+
throw new _common.BadRequestException({
|
|
52
|
+
message: `${entityName} belongs to another company`,
|
|
53
|
+
messageKey: _constants.AUTH_MESSAGES.ENTITY_BELOG_ANOTHER_COMPANY,
|
|
54
|
+
messageVariables: {
|
|
55
|
+
entity: entityName
|
|
56
|
+
}
|
|
57
|
+
});
|
|
51
58
|
}
|
|
52
59
|
}
|
|
53
60
|
}
|
|
@@ -12,6 +12,12 @@ _export(exports, {
|
|
|
12
12
|
get buildCookieOptions () {
|
|
13
13
|
return buildCookieOptions;
|
|
14
14
|
},
|
|
15
|
+
get buildFrontendUrl () {
|
|
16
|
+
return buildFrontendUrl;
|
|
17
|
+
},
|
|
18
|
+
get extractFrontendUrl () {
|
|
19
|
+
return extractFrontendUrl;
|
|
20
|
+
},
|
|
15
21
|
get isBrowserRequest () {
|
|
16
22
|
return isBrowserRequest;
|
|
17
23
|
},
|
|
@@ -69,3 +75,22 @@ function parseDurationToMs(duration, defaultMs = TIME_UNIT_MS.w) {
|
|
|
69
75
|
const unit = match[2];
|
|
70
76
|
return value * (TIME_UNIT_MS[unit] ?? TIME_UNIT_MS.d);
|
|
71
77
|
}
|
|
78
|
+
function extractFrontendUrl(req) {
|
|
79
|
+
const allowedOrigins = _config.envConfig.getOrigins();
|
|
80
|
+
const origin = (req.get('origin') ?? req.get('referer') ?? '').trim();
|
|
81
|
+
if (origin && allowedOrigins.length) {
|
|
82
|
+
const matched = allowedOrigins.find((u)=>origin.startsWith(u));
|
|
83
|
+
if (matched) {
|
|
84
|
+
return matched;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const host = req.get('host');
|
|
88
|
+
if (!host) {
|
|
89
|
+
throw new Error('Unable to determine frontend URL from request');
|
|
90
|
+
}
|
|
91
|
+
return `${req.protocol}://${host}`;
|
|
92
|
+
}
|
|
93
|
+
function buildFrontendUrl(req, path) {
|
|
94
|
+
const baseUrl = extractFrontendUrl(req);
|
|
95
|
+
return `${baseUrl}${path}`;
|
|
96
|
+
}
|
|
@@ -1,30 +1,38 @@
|
|
|
1
|
-
import { BulkResponseDto, DeleteDto, FilterAndPaginationDto, GetByIdBodyDto, ListResponseDto, MessageResponseDto, SingleResponseDto } from '../dtos';
|
|
2
|
-
import { Identity } from '../entities';
|
|
3
|
-
import { ILoggedUserInfo, IService, PermissionCondition, PermissionOperator } from '../interfaces';
|
|
4
1
|
import { Type } from '@nestjs/common';
|
|
5
|
-
|
|
2
|
+
import { BulkResponseDto, DeleteDto, FilterAndPaginationDto, GetByIdBodyDto, GetByIdsDto, ListResponseDto, MessageResponseDto, SingleResponseDto } from '../dtos';
|
|
3
|
+
import { Identity } from '../entities';
|
|
4
|
+
import { ILoggedUserInfo, IPermissionLogic, IService } from '../interfaces';
|
|
5
|
+
export type ApiEndpoint = 'insert' | 'insertMany' | 'getById' | 'getByIds' | 'getAll' | 'bulkUpsert' | 'getByFilter' | 'update' | 'updateMany' | 'delete';
|
|
6
6
|
export type SecurityLevel = 'public' | 'jwt' | 'permission';
|
|
7
7
|
export interface EndpointSecurity {
|
|
8
8
|
level: SecurityLevel;
|
|
9
9
|
permissions?: string[];
|
|
10
|
-
operator?:
|
|
11
|
-
|
|
10
|
+
operator?: 'AND' | 'OR';
|
|
11
|
+
logic?: IPermissionLogic;
|
|
12
12
|
}
|
|
13
13
|
export type ApiSecurityConfig = {
|
|
14
14
|
[K in ApiEndpoint]?: EndpointSecurity | SecurityLevel;
|
|
15
15
|
};
|
|
16
16
|
export interface ApiControllerOptions {
|
|
17
17
|
security?: ApiSecurityConfig | EndpointSecurity | SecurityLevel;
|
|
18
|
+
entityName?: string;
|
|
19
|
+
enabledEndpoints?: ApiEndpoint[] | 'all';
|
|
20
|
+
excludeExtraneousValues?: boolean;
|
|
18
21
|
}
|
|
19
22
|
export declare function createApiController<CreateDtoT extends object, UpdateDtoT extends {
|
|
20
23
|
id: string;
|
|
21
24
|
}, ResponseDtoT extends object, InterfaceT extends Identity, ServiceT extends IService<CreateDtoT, UpdateDtoT, InterfaceT>>(createDtoClass: Type<CreateDtoT>, updateDtoClass: Type<UpdateDtoT>, responseDtoClass: Type<ResponseDtoT>, options?: ApiControllerOptions): abstract new (service: ServiceT) => {
|
|
25
|
+
readonly enabledEndpoints: ApiEndpoint[] | "all";
|
|
22
26
|
service: ServiceT;
|
|
27
|
+
isEnabled(endpoint: ApiEndpoint): boolean;
|
|
23
28
|
insert(addDto: CreateDtoT, user: ILoggedUserInfo | null): Promise<SingleResponseDto<ResponseDtoT>>;
|
|
24
29
|
insertMany(addDto: CreateDtoT[], user: ILoggedUserInfo | null): Promise<BulkResponseDto<ResponseDtoT>>;
|
|
25
30
|
getById(id: string, body: GetByIdBodyDto, user: ILoggedUserInfo | null): Promise<SingleResponseDto<ResponseDtoT>>;
|
|
31
|
+
getByIds(body: GetByIdsDto, user: ILoggedUserInfo | null): Promise<ListResponseDto<ResponseDtoT>>;
|
|
26
32
|
update(updateDto: UpdateDtoT, user: ILoggedUserInfo | null): Promise<SingleResponseDto<ResponseDtoT>>;
|
|
27
33
|
updateMany(updateDtos: UpdateDtoT[], user: ILoggedUserInfo | null): Promise<BulkResponseDto<ResponseDtoT>>;
|
|
34
|
+
bulkUpsert(dtos: (CreateDtoT | UpdateDtoT)[], user: ILoggedUserInfo | null): Promise<BulkResponseDto<ResponseDtoT>>;
|
|
35
|
+
getByFilter(filter: Record<string, any>, user: ILoggedUserInfo | null): Promise<SingleResponseDto<ResponseDtoT>>;
|
|
28
36
|
getAll(filterAndPaginationDto: FilterAndPaginationDto, user: ILoggedUserInfo | null, search?: string): Promise<ListResponseDto<ResponseDtoT>>;
|
|
29
37
|
delete(deleteDto: DeleteDto, user: ILoggedUserInfo | null): Promise<MessageResponseDto>;
|
|
30
38
|
};
|