@flusys/nestjs-shared 3.0.0 → 4.0.0-rc
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +160 -80
- package/cjs/classes/api-controller.class.js +26 -8
- package/cjs/classes/api-service.class.js +100 -17
- package/cjs/classes/winston-logger-adapter.class.js +15 -20
- package/cjs/classes/winston.logger.class.js +103 -70
- package/cjs/constants/index.js +1 -0
- package/cjs/constants/message-keys.js +80 -0
- package/cjs/constants/permissions.js +65 -11
- package/cjs/decorators/index.js +1 -0
- package/cjs/decorators/log-action.decorator.js +149 -0
- package/cjs/dtos/response-payload.dto.js +72 -0
- 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 +145 -0
- package/cjs/exceptions/index.js +1 -0
- package/cjs/exceptions/permission.exception.js +12 -8
- package/cjs/filters/global-exception.filter.js +167 -0
- package/cjs/filters/index.js +18 -0
- package/cjs/guards/jwt-auth.guard.js +4 -1
- package/cjs/guards/permission.guard.js +6 -13
- 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 -0
- package/cjs/interfaces/logger.interface.js +1 -4
- package/cjs/interfaces/notification-adapter.interface.js +11 -0
- package/cjs/middlewares/logger.middleware.js +83 -26
- package/cjs/modules/datasource/multi-tenant-datasource.service.js +33 -11
- package/cjs/modules/utils/utils.service.js +4 -20
- package/cjs/utils/index.js +0 -1
- package/cjs/utils/query-helpers.util.js +8 -1
- package/classes/api-controller.class.d.ts +1 -0
- package/classes/api-service.class.d.ts +5 -10
- package/classes/winston-logger-adapter.class.d.ts +12 -11
- package/classes/winston.logger.class.d.ts +1 -0
- package/constants/index.d.ts +1 -0
- package/constants/message-keys.d.ts +81 -0
- package/constants/permissions.d.ts +72 -0
- package/decorators/index.d.ts +1 -0
- package/decorators/log-action.decorator.d.ts +8 -0
- package/dtos/response-payload.dto.d.ts +8 -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 +1 -1
- package/fesm/classes/api-controller.class.js +26 -8
- package/fesm/classes/api-service.class.js +101 -18
- package/fesm/classes/winston-logger-adapter.class.js +18 -44
- package/fesm/classes/winston.logger.class.js +100 -68
- package/fesm/constants/index.js +2 -0
- package/fesm/constants/message-keys.js +59 -0
- package/fesm/constants/permissions.js +51 -14
- package/fesm/decorators/index.js +1 -0
- package/fesm/decorators/log-action.decorator.js +139 -0
- package/fesm/dtos/response-payload.dto.js +72 -0
- 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 +109 -0
- package/fesm/exceptions/index.js +1 -0
- package/fesm/exceptions/permission.exception.js +15 -17
- package/fesm/filters/global-exception.filter.js +157 -0
- package/fesm/filters/index.js +1 -0
- package/fesm/guards/jwt-auth.guard.js +5 -2
- package/fesm/guards/permission.guard.js +8 -15
- package/fesm/index.js +2 -0
- package/fesm/interceptors/idempotency.interceptor.js +2 -2
- package/fesm/interceptors/index.js +0 -1
- package/fesm/interfaces/event-manager-adapter.interface.js +1 -0
- package/fesm/interfaces/index.js +2 -0
- package/fesm/interfaces/logger.interface.js +1 -4
- package/fesm/interfaces/notification-adapter.interface.js +1 -0
- package/fesm/middlewares/logger.middleware.js +83 -26
- package/fesm/modules/datasource/multi-tenant-datasource.service.js +34 -12
- package/fesm/modules/utils/utils.service.js +5 -21
- package/fesm/utils/index.js +0 -1
- package/fesm/utils/query-helpers.util.js +8 -1
- package/filters/global-exception.filter.d.ts +10 -0
- package/filters/index.d.ts +1 -0
- package/guards/permission.guard.d.ts +1 -3
- package/index.d.ts +2 -0
- package/interceptors/index.d.ts +0 -1
- package/interfaces/event-manager-adapter.interface.d.ts +43 -0
- package/interfaces/index.d.ts +2 -0
- package/interfaces/logger.interface.d.ts +5 -5
- package/interfaces/notification-adapter.interface.d.ts +22 -0
- package/modules/datasource/multi-tenant-datasource.service.d.ts +1 -2
- package/modules/utils/utils.service.d.ts +0 -1
- package/package.json +7 -2
- package/utils/index.d.ts +0 -1
- package/cjs/interceptors/query-performance.interceptor.js +0 -66
- package/cjs/utils/error-handler.util.js +0 -90
- package/fesm/interceptors/query-performance.interceptor.js +0 -56
- package/fesm/utils/error-handler.util.js +0 -82
- package/interceptors/query-performance.interceptor.d.ts +0 -8
- package/utils/error-handler.util.d.ts +0 -19
|
@@ -26,7 +26,8 @@ function _ts_param(paramIndex, decorator) {
|
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
28
|
import { DEFAULT_TENANT_HEADER, IDataSourceServiceOptions, MODULE_OPTIONS } from '@flusys/nestjs-core';
|
|
29
|
-
import { BadRequestException, Inject, Injectable,
|
|
29
|
+
import { BadRequestException, Inject, Injectable, InternalServerErrorException, Optional, Scope } from '@nestjs/common';
|
|
30
|
+
import { SYSTEM_MESSAGES } from '../../constants';
|
|
30
31
|
import { REQUEST } from '@nestjs/core';
|
|
31
32
|
import { Request } from 'express';
|
|
32
33
|
import { DataSource } from 'typeorm';
|
|
@@ -58,7 +59,10 @@ export class MultiTenantDataSourceService {
|
|
|
58
59
|
const tenantId = this.request.headers[this.tenantHeader];
|
|
59
60
|
if (!tenantId) return null;
|
|
60
61
|
if (!/^[a-zA-Z0-9_-]+$/.test(tenantId)) {
|
|
61
|
-
throw new BadRequestException(
|
|
62
|
+
throw new BadRequestException({
|
|
63
|
+
message: 'Invalid tenant ID format',
|
|
64
|
+
messageKey: SYSTEM_MESSAGES.INVALID_TENANT_ID
|
|
65
|
+
});
|
|
62
66
|
}
|
|
63
67
|
return tenantId;
|
|
64
68
|
}
|
|
@@ -80,7 +84,15 @@ export class MultiTenantDataSourceService {
|
|
|
80
84
|
}
|
|
81
85
|
async getDataSourceForTenant(tenantId) {
|
|
82
86
|
const tenant = this.getTenant(tenantId);
|
|
83
|
-
if (!tenant)
|
|
87
|
+
if (!tenant) {
|
|
88
|
+
throw new BadRequestException({
|
|
89
|
+
message: `Tenant '${tenantId}' not found`,
|
|
90
|
+
messageKey: SYSTEM_MESSAGES.TENANT_NOT_FOUND,
|
|
91
|
+
messageParams: {
|
|
92
|
+
tenantId
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
}
|
|
84
96
|
return this.getOrCreateTenantConnection(tenant);
|
|
85
97
|
}
|
|
86
98
|
setDataSource(dataSource) {
|
|
@@ -100,8 +112,8 @@ export class MultiTenantDataSourceService {
|
|
|
100
112
|
try {
|
|
101
113
|
const dataSource = await this.getOrCreateTenantConnection(tenant);
|
|
102
114
|
results.set(tenant.id, await callback(tenant, dataSource));
|
|
103
|
-
} catch
|
|
104
|
-
|
|
115
|
+
} catch {
|
|
116
|
+
// Silent failure for individual tenant
|
|
105
117
|
}
|
|
106
118
|
}
|
|
107
119
|
return results;
|
|
@@ -118,7 +130,6 @@ export class MultiTenantDataSourceService {
|
|
|
118
130
|
if (connection?.isInitialized) {
|
|
119
131
|
await connection.destroy();
|
|
120
132
|
MultiTenantDataSourceService.tenantConnections.delete(tenantId);
|
|
121
|
-
this.logger.log(`Closed connection for tenant: ${tenantId}`);
|
|
122
133
|
}
|
|
123
134
|
}
|
|
124
135
|
async onModuleDestroy() {
|
|
@@ -148,7 +159,10 @@ export class MultiTenantDataSourceService {
|
|
|
148
159
|
}
|
|
149
160
|
const config = this.getDefaultDatabaseConfig();
|
|
150
161
|
if (!config) {
|
|
151
|
-
throw new
|
|
162
|
+
throw new InternalServerErrorException({
|
|
163
|
+
message: 'No database config available. Provide defaultDatabaseConfig or tenantDefaultDatabaseConfig.',
|
|
164
|
+
messageKey: SYSTEM_MESSAGES.DATABASE_CONFIG_NOT_AVAILABLE
|
|
165
|
+
});
|
|
152
166
|
}
|
|
153
167
|
// Create connection with lock to prevent race conditions
|
|
154
168
|
const connectionPromise = this.createDataSourceFromConfig(config);
|
|
@@ -166,7 +180,13 @@ export class MultiTenantDataSourceService {
|
|
|
166
180
|
*/ async getTenantDataSource() {
|
|
167
181
|
const tenant = this.getCurrentTenant();
|
|
168
182
|
if (!tenant) {
|
|
169
|
-
throw new
|
|
183
|
+
throw new BadRequestException({
|
|
184
|
+
message: `Tenant not found. Ensure '${this.tenantHeader}' header is set.`,
|
|
185
|
+
messageKey: SYSTEM_MESSAGES.TENANT_HEADER_REQUIRED,
|
|
186
|
+
messageParams: {
|
|
187
|
+
header: this.tenantHeader
|
|
188
|
+
}
|
|
189
|
+
});
|
|
170
190
|
}
|
|
171
191
|
return this.getOrCreateTenantConnection(tenant);
|
|
172
192
|
}
|
|
@@ -181,7 +201,6 @@ export class MultiTenantDataSourceService {
|
|
|
181
201
|
try {
|
|
182
202
|
const dataSource = await connectionPromise;
|
|
183
203
|
MultiTenantDataSourceService.tenantConnections.set(tenant.id, dataSource);
|
|
184
|
-
this.logger.log(`Created connection for tenant: ${tenant.id}`);
|
|
185
204
|
return dataSource;
|
|
186
205
|
} finally{
|
|
187
206
|
MultiTenantDataSourceService.connectionLocks.delete(tenant.id);
|
|
@@ -192,7 +211,12 @@ export class MultiTenantDataSourceService {
|
|
|
192
211
|
}
|
|
193
212
|
buildTenantDatabaseConfig(tenant) {
|
|
194
213
|
const defaultConfig = this.getDefaultDatabaseConfig();
|
|
195
|
-
if (!defaultConfig)
|
|
214
|
+
if (!defaultConfig) {
|
|
215
|
+
throw new InternalServerErrorException({
|
|
216
|
+
message: 'No default database config for multi-tenant mode.',
|
|
217
|
+
messageKey: SYSTEM_MESSAGES.DATABASE_CONFIG_NOT_AVAILABLE
|
|
218
|
+
});
|
|
219
|
+
}
|
|
196
220
|
return {
|
|
197
221
|
type: defaultConfig.type,
|
|
198
222
|
host: tenant.host ?? defaultConfig.host,
|
|
@@ -220,11 +244,9 @@ export class MultiTenantDataSourceService {
|
|
|
220
244
|
constructor(options, request){
|
|
221
245
|
_define_property(this, "options", void 0);
|
|
222
246
|
_define_property(this, "request", void 0);
|
|
223
|
-
_define_property(this, "logger", void 0);
|
|
224
247
|
_define_property(this, "tenantHeader", void 0);
|
|
225
248
|
this.options = options;
|
|
226
249
|
this.request = request;
|
|
227
|
-
this.logger = new Logger(this.constructor.name);
|
|
228
250
|
this.tenantHeader = DEFAULT_TENANT_HEADER;
|
|
229
251
|
this.initializeFromOptions();
|
|
230
252
|
}
|
|
@@ -1,23 +1,10 @@
|
|
|
1
|
-
function _define_property(obj, key, value) {
|
|
2
|
-
if (key in obj) {
|
|
3
|
-
Object.defineProperty(obj, key, {
|
|
4
|
-
value: value,
|
|
5
|
-
enumerable: true,
|
|
6
|
-
configurable: true,
|
|
7
|
-
writable: true
|
|
8
|
-
});
|
|
9
|
-
} else {
|
|
10
|
-
obj[key] = value;
|
|
11
|
-
}
|
|
12
|
-
return obj;
|
|
13
|
-
}
|
|
14
1
|
function _ts_decorate(decorators, target, key, desc) {
|
|
15
2
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
16
3
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
17
4
|
else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
18
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
19
6
|
}
|
|
20
|
-
import { Injectable
|
|
7
|
+
import { Injectable } from '@nestjs/common';
|
|
21
8
|
export class UtilsService {
|
|
22
9
|
// ---------------- CACHE HELPERS ----------------
|
|
23
10
|
/**
|
|
@@ -37,8 +24,8 @@ export class UtilsService {
|
|
|
37
24
|
const keys = await cacheManager.get(trackingKey) || [];
|
|
38
25
|
if (!keys.includes(cacheKey)) keys.push(cacheKey);
|
|
39
26
|
await cacheManager.set(trackingKey, keys);
|
|
40
|
-
} catch
|
|
41
|
-
|
|
27
|
+
} catch {
|
|
28
|
+
// Silent failure - cache tracking is non-critical
|
|
42
29
|
}
|
|
43
30
|
}
|
|
44
31
|
/**
|
|
@@ -49,8 +36,8 @@ export class UtilsService {
|
|
|
49
36
|
const keys = await cacheManager.get(trackingKey) || [];
|
|
50
37
|
await Promise.allSettled(keys.map((key)=>cacheManager.del(key)));
|
|
51
38
|
await cacheManager.del(trackingKey);
|
|
52
|
-
} catch
|
|
53
|
-
|
|
39
|
+
} catch {
|
|
40
|
+
// Silent failure - cache invalidation is non-critical
|
|
54
41
|
}
|
|
55
42
|
}
|
|
56
43
|
// ---------------- STRING HELPERS ----------------
|
|
@@ -73,9 +60,6 @@ export class UtilsService {
|
|
|
73
60
|
const prefix = this.buildTenantPrefix(tenantId);
|
|
74
61
|
return entityId ? `${prefix}entity_${entityName}_id_${entityId}_keys` : `${prefix}entity_${entityName}_keys`;
|
|
75
62
|
}
|
|
76
|
-
constructor(){
|
|
77
|
-
_define_property(this, "logger", new Logger(UtilsService.name));
|
|
78
|
-
}
|
|
79
63
|
}
|
|
80
64
|
UtilsService = _ts_decorate([
|
|
81
65
|
Injectable()
|
package/fesm/utils/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { BadRequestException } from '@nestjs/common';
|
|
2
|
+
import { AUTH_MESSAGES } from '../constants';
|
|
2
3
|
/**
|
|
3
4
|
* Apply company filter to a query builder if company feature is enabled.
|
|
4
5
|
* Centralizes the common pattern of filtering by user's company.
|
|
@@ -72,7 +73,13 @@ import { BadRequestException } from '@nestjs/common';
|
|
|
72
73
|
*/ export function validateCompanyOwnership(entity, user, isCompanyFeatureEnabled, entityName) {
|
|
73
74
|
if (isCompanyFeatureEnabled && user?.companyId && hasCompanyId(entity)) {
|
|
74
75
|
if (entity.companyId && entity.companyId !== user.companyId) {
|
|
75
|
-
throw new BadRequestException(
|
|
76
|
+
throw new BadRequestException({
|
|
77
|
+
message: `${entityName} belongs to another company`,
|
|
78
|
+
messageKey: AUTH_MESSAGES.COMPANY_NO_ACCESS,
|
|
79
|
+
messageParams: {
|
|
80
|
+
entity: entityName
|
|
81
|
+
}
|
|
82
|
+
});
|
|
76
83
|
}
|
|
77
84
|
}
|
|
78
85
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ArgumentsHost, ExceptionFilter } from '@nestjs/common';
|
|
2
|
+
export declare class GlobalExceptionFilter implements ExceptionFilter {
|
|
3
|
+
private readonly logger;
|
|
4
|
+
catch(exception: unknown, host: ArgumentsHost): void;
|
|
5
|
+
private buildErrorResponse;
|
|
6
|
+
private extractHttpExceptionDetails;
|
|
7
|
+
private getStatusCode;
|
|
8
|
+
private getStack;
|
|
9
|
+
private buildLogMessage;
|
|
10
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './global-exception.filter';
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import { CanActivate, ExecutionContext } from '@nestjs/common';
|
|
2
2
|
import { Reflector } from '@nestjs/core';
|
|
3
3
|
import { HybridCache } from '../classes/hybrid-cache.class';
|
|
4
|
-
import { ILogger } from '../interfaces/logger.interface';
|
|
5
4
|
import { PermissionGuardConfig } from '../interfaces/permission.interface';
|
|
6
5
|
export declare class PermissionGuard implements CanActivate {
|
|
7
6
|
private readonly reflector;
|
|
8
7
|
private readonly cache?;
|
|
9
8
|
private readonly config;
|
|
10
|
-
|
|
11
|
-
constructor(reflector: Reflector, cache?: HybridCache, config?: PermissionGuardConfig, logger?: ILogger);
|
|
9
|
+
constructor(reflector: Reflector, cache?: HybridCache, config?: PermissionGuardConfig);
|
|
12
10
|
canActivate(context: ExecutionContext): Promise<boolean>;
|
|
13
11
|
private normalizeToLogicNode;
|
|
14
12
|
private evaluateLogicNode;
|
package/index.d.ts
CHANGED
|
@@ -3,7 +3,9 @@ export * from './constants';
|
|
|
3
3
|
export * from './decorators';
|
|
4
4
|
export * from './dtos';
|
|
5
5
|
export * from './entities';
|
|
6
|
+
export * from './enums';
|
|
6
7
|
export * from './exceptions';
|
|
8
|
+
export * from './filters';
|
|
7
9
|
export * from './guards';
|
|
8
10
|
export * from './interceptors';
|
|
9
11
|
export * from './interfaces';
|
package/interceptors/index.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
export * from './delete-empty-id-from-body.interceptor';
|
|
2
2
|
export * from './idempotency.interceptor';
|
|
3
|
-
export * from './query-performance.interceptor';
|
|
4
3
|
export * from './response-meta.interceptor';
|
|
5
4
|
export * from './set-user-field-on-body.interceptor';
|
|
6
5
|
export * from './slug.interceptor';
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { ParticipantStatus } from '../enums/participant-status.enum';
|
|
2
|
+
import { RecurrenceType } from '../enums/recurrence-type.enum';
|
|
3
|
+
export interface IEventManagerAdapter {
|
|
4
|
+
createEvent(options: CreateEventOptions): Promise<EventResult>;
|
|
5
|
+
addParticipants(eventId: string, userIds: string[], companyId?: string): Promise<void>;
|
|
6
|
+
removeParticipant(eventId: string, userId: string): Promise<void>;
|
|
7
|
+
updateParticipantStatus(participantId: string, status: ParticipantStatus): Promise<void>;
|
|
8
|
+
getEventsForUser?(userId: string, startDate: Date, endDate: Date, companyId?: string): Promise<EventResult[]>;
|
|
9
|
+
getEventById?(eventId: string): Promise<EventResult | null>;
|
|
10
|
+
}
|
|
11
|
+
export interface CreateEventOptions {
|
|
12
|
+
title: string;
|
|
13
|
+
description?: string;
|
|
14
|
+
meetingLink?: string;
|
|
15
|
+
startDateTime: Date;
|
|
16
|
+
endDateTime: Date;
|
|
17
|
+
isAllDay?: boolean;
|
|
18
|
+
recurrenceType?: RecurrenceType;
|
|
19
|
+
recurrenceEndDate?: Date;
|
|
20
|
+
color?: string;
|
|
21
|
+
metadata?: Record<string, unknown>;
|
|
22
|
+
participantIds?: string[];
|
|
23
|
+
organizerId?: string;
|
|
24
|
+
companyId?: string;
|
|
25
|
+
}
|
|
26
|
+
export interface EventResult {
|
|
27
|
+
id: string;
|
|
28
|
+
title: string;
|
|
29
|
+
description?: string | null;
|
|
30
|
+
meetingLink?: string | null;
|
|
31
|
+
startDateTime: Date;
|
|
32
|
+
endDateTime: Date;
|
|
33
|
+
isAllDay: boolean;
|
|
34
|
+
recurrenceType: string;
|
|
35
|
+
recurrenceEndDate?: Date | null;
|
|
36
|
+
color: string;
|
|
37
|
+
isActive: boolean;
|
|
38
|
+
metadata?: Record<string, unknown> | null;
|
|
39
|
+
companyId?: string | null;
|
|
40
|
+
createdAt: Date;
|
|
41
|
+
updatedAt: Date;
|
|
42
|
+
}
|
|
43
|
+
export declare const EVENT_MANAGER_ADAPTER = "EVENT_MANAGER_ADAPTER";
|
package/interfaces/index.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
export * from './api.interface';
|
|
2
2
|
export * from './datasource.interface';
|
|
3
|
+
export * from './event-manager-adapter.interface';
|
|
3
4
|
export * from './identity.interface';
|
|
4
5
|
export * from './logged-user-info.interface';
|
|
5
6
|
export * from './logger.interface';
|
|
6
7
|
export * from './module-config.interface';
|
|
8
|
+
export * from './notification-adapter.interface';
|
|
7
9
|
export * from './permission.interface';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export interface ILogger {
|
|
2
|
-
log(message: string, context?: string, ...args:
|
|
3
|
-
error(message: string, trace?: string, context?: string, ...args:
|
|
4
|
-
warn(message: string, context?: string, ...args:
|
|
5
|
-
debug(message: string, context?: string, ...args:
|
|
6
|
-
verbose(message: string, context?: string, ...args:
|
|
2
|
+
log(message: string, context?: string, ...args: unknown[]): void;
|
|
3
|
+
error(message: string, trace?: string, context?: string, ...args: unknown[]): void;
|
|
4
|
+
warn(message: string, context?: string, ...args: unknown[]): void;
|
|
5
|
+
debug(message: string, context?: string, ...args: unknown[]): void;
|
|
6
|
+
verbose(message: string, context?: string, ...args: unknown[]): void;
|
|
7
7
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { NotificationType } from '../enums/notification-type.enum';
|
|
2
|
+
export interface INotificationAdapter {
|
|
3
|
+
send(options: NotificationSendOptions): Promise<void>;
|
|
4
|
+
sendToMany(options: NotificationBulkSendOptions): Promise<void>;
|
|
5
|
+
broadcastToCompany?(companyId: string, title: string, message?: string, type?: NotificationType, data?: Record<string, unknown>): Promise<void>;
|
|
6
|
+
isUserOnline?(userId: string): boolean;
|
|
7
|
+
}
|
|
8
|
+
interface NotificationBaseOptions {
|
|
9
|
+
title: string;
|
|
10
|
+
message?: string;
|
|
11
|
+
type?: NotificationType;
|
|
12
|
+
data?: Record<string, unknown>;
|
|
13
|
+
companyId?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface NotificationSendOptions extends NotificationBaseOptions {
|
|
16
|
+
userId: string;
|
|
17
|
+
}
|
|
18
|
+
export interface NotificationBulkSendOptions extends NotificationBaseOptions {
|
|
19
|
+
userIds: string[];
|
|
20
|
+
}
|
|
21
|
+
export declare const NOTIFICATION_ADAPTER = "NOTIFICATION_ADAPTER";
|
|
22
|
+
export {};
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { DatabaseMode, IDatabaseConfig, IDataSourceServiceOptions, ITenantDatabaseConfig } from '@flusys/nestjs-core';
|
|
2
|
-
import {
|
|
2
|
+
import { OnModuleDestroy } from '@nestjs/common';
|
|
3
3
|
import { Request } from 'express';
|
|
4
4
|
import { DataSource, EntityTarget, ObjectLiteral, Repository } from 'typeorm';
|
|
5
5
|
export declare class MultiTenantDataSourceService implements OnModuleDestroy {
|
|
6
6
|
protected readonly options?: IDataSourceServiceOptions;
|
|
7
7
|
protected readonly request?: Request;
|
|
8
|
-
protected readonly logger: Logger;
|
|
9
8
|
protected static readonly tenantConnections: Map<string, DataSource>;
|
|
10
9
|
protected static singleDataSource: DataSource | null;
|
|
11
10
|
protected static readonly tenantsRegistry: Map<string, ITenantDatabaseConfig>;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { HybridCache } from '../../classes/hybrid-cache.class';
|
|
2
2
|
export declare class UtilsService {
|
|
3
|
-
private readonly logger;
|
|
4
3
|
getCacheKey(entityName: string, params: any, entityId?: string, tenantId?: string): string;
|
|
5
4
|
trackCacheKey(cacheKey: string, entityName: string, cacheManager: HybridCache, entityId?: string, tenantId?: string): Promise<void>;
|
|
6
5
|
clearCache(entityName: string, cacheManager: HybridCache, entityId?: string, tenantId?: string): Promise<void>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flusys/nestjs-shared",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0-rc",
|
|
4
4
|
"description": "Common shared utilities for Flusys NestJS applications",
|
|
5
5
|
"main": "cjs/index.js",
|
|
6
6
|
"module": "fesm/index.js",
|
|
@@ -46,6 +46,11 @@
|
|
|
46
46
|
"import": "./fesm/entities/index.js",
|
|
47
47
|
"require": "./cjs/entities/index.js"
|
|
48
48
|
},
|
|
49
|
+
"./enums": {
|
|
50
|
+
"types": "./enums/index.d.ts",
|
|
51
|
+
"import": "./fesm/enums/index.js",
|
|
52
|
+
"require": "./cjs/enums/index.js"
|
|
53
|
+
},
|
|
49
54
|
"./exceptions": {
|
|
50
55
|
"types": "./exceptions/index.d.ts",
|
|
51
56
|
"import": "./fesm/exceptions/index.js",
|
|
@@ -105,6 +110,6 @@
|
|
|
105
110
|
"winston-daily-rotate-file": "^5.0.0"
|
|
106
111
|
},
|
|
107
112
|
"dependencies": {
|
|
108
|
-
"@flusys/nestjs-core": "
|
|
113
|
+
"@flusys/nestjs-core": "4.0.0-rc"
|
|
109
114
|
}
|
|
110
115
|
}
|
package/utils/index.d.ts
CHANGED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", {
|
|
3
|
-
value: true
|
|
4
|
-
});
|
|
5
|
-
Object.defineProperty(exports, "QueryPerformanceInterceptor", {
|
|
6
|
-
enumerable: true,
|
|
7
|
-
get: function() {
|
|
8
|
-
return QueryPerformanceInterceptor;
|
|
9
|
-
}
|
|
10
|
-
});
|
|
11
|
-
const _common = require("@nestjs/common");
|
|
12
|
-
const _operators = require("rxjs/operators");
|
|
13
|
-
function _define_property(obj, key, value) {
|
|
14
|
-
if (key in obj) {
|
|
15
|
-
Object.defineProperty(obj, key, {
|
|
16
|
-
value: value,
|
|
17
|
-
enumerable: true,
|
|
18
|
-
configurable: true,
|
|
19
|
-
writable: true
|
|
20
|
-
});
|
|
21
|
-
} else {
|
|
22
|
-
obj[key] = value;
|
|
23
|
-
}
|
|
24
|
-
return obj;
|
|
25
|
-
}
|
|
26
|
-
function _ts_decorate(decorators, target, key, desc) {
|
|
27
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
28
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
29
|
-
else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
30
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
31
|
-
}
|
|
32
|
-
function _ts_metadata(k, v) {
|
|
33
|
-
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
34
|
-
}
|
|
35
|
-
let QueryPerformanceInterceptor = class QueryPerformanceInterceptor {
|
|
36
|
-
intercept(context, next) {
|
|
37
|
-
const request = context.switchToHttp().getRequest();
|
|
38
|
-
const { method, url } = request;
|
|
39
|
-
const handler = context.getHandler().name;
|
|
40
|
-
const controller = context.getClass().name;
|
|
41
|
-
const startTime = Date.now();
|
|
42
|
-
return next.handle().pipe((0, _operators.tap)(()=>{
|
|
43
|
-
const executionTime = Date.now() - startTime;
|
|
44
|
-
if (executionTime > this.threshold) {
|
|
45
|
-
this.logger.warn(`Slow request detected: ${method} ${url} | ` + `Controller: ${controller}.${handler} | ` + `Execution time: ${executionTime}ms`);
|
|
46
|
-
} else {
|
|
47
|
-
// Optional: Log all requests in debug mode
|
|
48
|
-
this.logger.debug(`${method} ${url} | ${controller}.${handler} | ${executionTime}ms`);
|
|
49
|
-
}
|
|
50
|
-
}));
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* @param threshold - Time threshold in milliseconds (default: 1000ms)
|
|
54
|
-
*/ constructor(threshold = 1000){
|
|
55
|
-
_define_property(this, "logger", new _common.Logger(QueryPerformanceInterceptor.name));
|
|
56
|
-
_define_property(this, "threshold", void 0);
|
|
57
|
-
this.threshold = threshold;
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
QueryPerformanceInterceptor = _ts_decorate([
|
|
61
|
-
(0, _common.Injectable)(),
|
|
62
|
-
_ts_metadata("design:type", Function),
|
|
63
|
-
_ts_metadata("design:paramtypes", [
|
|
64
|
-
Number
|
|
65
|
-
])
|
|
66
|
-
], QueryPerformanceInterceptor);
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", {
|
|
3
|
-
value: true
|
|
4
|
-
});
|
|
5
|
-
Object.defineProperty(exports, "ErrorHandler", {
|
|
6
|
-
enumerable: true,
|
|
7
|
-
get: function() {
|
|
8
|
-
return ErrorHandler;
|
|
9
|
-
}
|
|
10
|
-
});
|
|
11
|
-
/** Sensitive keys that should be redacted from logs */ const SENSITIVE_KEYS = [
|
|
12
|
-
'password',
|
|
13
|
-
'secret',
|
|
14
|
-
'token',
|
|
15
|
-
'apiKey',
|
|
16
|
-
'credential',
|
|
17
|
-
'authorization'
|
|
18
|
-
];
|
|
19
|
-
let ErrorHandler = class ErrorHandler {
|
|
20
|
-
/**
|
|
21
|
-
* Safely extract error message from unknown error.
|
|
22
|
-
*/ static getErrorMessage(error) {
|
|
23
|
-
if (error instanceof Error) {
|
|
24
|
-
return error.message;
|
|
25
|
-
}
|
|
26
|
-
if (typeof error === 'string') {
|
|
27
|
-
return error;
|
|
28
|
-
}
|
|
29
|
-
return 'Unknown error occurred';
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Sanitize context data to redact sensitive fields from logs.
|
|
33
|
-
*/ static sanitizeContextForLogging(context) {
|
|
34
|
-
const sanitized = {};
|
|
35
|
-
for (const [key, value] of Object.entries(context)){
|
|
36
|
-
const isSensitive = SENSITIVE_KEYS.some((sk)=>key.toLowerCase().includes(sk.toLowerCase()));
|
|
37
|
-
if (isSensitive) {
|
|
38
|
-
sanitized[key] = '[REDACTED]';
|
|
39
|
-
} else if (Array.isArray(value)) {
|
|
40
|
-
sanitized[key] = value.map((item)=>typeof item === 'object' && item !== null ? this.sanitizeContextForLogging(item) : item);
|
|
41
|
-
} else if (typeof value === 'object' && value !== null) {
|
|
42
|
-
sanitized[key] = this.sanitizeContextForLogging(value);
|
|
43
|
-
} else {
|
|
44
|
-
sanitized[key] = value;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
return sanitized;
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
|
-
* Create error context object for internal logging.
|
|
51
|
-
*/ static createErrorContext(error, context) {
|
|
52
|
-
const errorContext = {
|
|
53
|
-
error: {
|
|
54
|
-
message: this.getErrorMessage(error)
|
|
55
|
-
}
|
|
56
|
-
};
|
|
57
|
-
if (error instanceof Error) {
|
|
58
|
-
errorContext.error.stack = error.stack;
|
|
59
|
-
errorContext.error.name = error.name;
|
|
60
|
-
}
|
|
61
|
-
if (context && Object.keys(context).length > 0) {
|
|
62
|
-
errorContext.context = this.sanitizeContextForLogging(context);
|
|
63
|
-
}
|
|
64
|
-
return errorContext;
|
|
65
|
-
}
|
|
66
|
-
/**
|
|
67
|
-
* Log error with consistent format.
|
|
68
|
-
*/ static logError(logger, error, operation, context) {
|
|
69
|
-
const errorContext = this.createErrorContext(error, {
|
|
70
|
-
operation,
|
|
71
|
-
...context
|
|
72
|
-
});
|
|
73
|
-
const errorMessage = `Failed to ${operation}: ${errorContext.error.message}`;
|
|
74
|
-
logger.error(errorMessage, errorContext.error.stack, errorContext);
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* Re-throw error with proper type checking.
|
|
78
|
-
*/ static rethrowError(error) {
|
|
79
|
-
if (error instanceof Error) {
|
|
80
|
-
throw error;
|
|
81
|
-
}
|
|
82
|
-
throw new Error(`Unexpected error: ${String(error)}`);
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* Log error and re-throw (common pattern).
|
|
86
|
-
*/ static logAndRethrow(logger, error, operation, context) {
|
|
87
|
-
this.logError(logger, error, operation, context);
|
|
88
|
-
this.rethrowError(error);
|
|
89
|
-
}
|
|
90
|
-
};
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
function _define_property(obj, key, value) {
|
|
2
|
-
if (key in obj) {
|
|
3
|
-
Object.defineProperty(obj, key, {
|
|
4
|
-
value: value,
|
|
5
|
-
enumerable: true,
|
|
6
|
-
configurable: true,
|
|
7
|
-
writable: true
|
|
8
|
-
});
|
|
9
|
-
} else {
|
|
10
|
-
obj[key] = value;
|
|
11
|
-
}
|
|
12
|
-
return obj;
|
|
13
|
-
}
|
|
14
|
-
function _ts_decorate(decorators, target, key, desc) {
|
|
15
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
16
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
17
|
-
else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
18
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
19
|
-
}
|
|
20
|
-
function _ts_metadata(k, v) {
|
|
21
|
-
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
22
|
-
}
|
|
23
|
-
import { Injectable, Logger } from '@nestjs/common';
|
|
24
|
-
import { tap } from 'rxjs/operators';
|
|
25
|
-
export class QueryPerformanceInterceptor {
|
|
26
|
-
intercept(context, next) {
|
|
27
|
-
const request = context.switchToHttp().getRequest();
|
|
28
|
-
const { method, url } = request;
|
|
29
|
-
const handler = context.getHandler().name;
|
|
30
|
-
const controller = context.getClass().name;
|
|
31
|
-
const startTime = Date.now();
|
|
32
|
-
return next.handle().pipe(tap(()=>{
|
|
33
|
-
const executionTime = Date.now() - startTime;
|
|
34
|
-
if (executionTime > this.threshold) {
|
|
35
|
-
this.logger.warn(`Slow request detected: ${method} ${url} | ` + `Controller: ${controller}.${handler} | ` + `Execution time: ${executionTime}ms`);
|
|
36
|
-
} else {
|
|
37
|
-
// Optional: Log all requests in debug mode
|
|
38
|
-
this.logger.debug(`${method} ${url} | ${controller}.${handler} | ${executionTime}ms`);
|
|
39
|
-
}
|
|
40
|
-
}));
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* @param threshold - Time threshold in milliseconds (default: 1000ms)
|
|
44
|
-
*/ constructor(threshold = 1000){
|
|
45
|
-
_define_property(this, "logger", new Logger(QueryPerformanceInterceptor.name));
|
|
46
|
-
_define_property(this, "threshold", void 0);
|
|
47
|
-
this.threshold = threshold;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
QueryPerformanceInterceptor = _ts_decorate([
|
|
51
|
-
Injectable(),
|
|
52
|
-
_ts_metadata("design:type", Function),
|
|
53
|
-
_ts_metadata("design:paramtypes", [
|
|
54
|
-
Number
|
|
55
|
-
])
|
|
56
|
-
], QueryPerformanceInterceptor);
|