@vritti/api-sdk 0.1.3 → 0.1.4
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/dist/index.cjs +696 -686
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +659 -649
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -106,7 +106,7 @@ function _ts_param(paramIndex, decorator) {
|
|
|
106
106
|
};
|
|
107
107
|
}
|
|
108
108
|
__name(_ts_param, "_ts_param");
|
|
109
|
-
var
|
|
109
|
+
var RequestService = class {
|
|
110
110
|
static {
|
|
111
111
|
__name(this, "RequestService");
|
|
112
112
|
}
|
|
@@ -175,7 +175,7 @@ var RequestService2 = class {
|
|
|
175
175
|
return this.request.headers || {};
|
|
176
176
|
}
|
|
177
177
|
};
|
|
178
|
-
|
|
178
|
+
RequestService = _ts_decorate([
|
|
179
179
|
Injectable({
|
|
180
180
|
scope: Scope.REQUEST
|
|
181
181
|
}),
|
|
@@ -184,7 +184,7 @@ RequestService2 = _ts_decorate([
|
|
|
184
184
|
_ts_metadata("design:paramtypes", [
|
|
185
185
|
typeof FastifyRequest === "undefined" ? Object : FastifyRequest
|
|
186
186
|
])
|
|
187
|
-
],
|
|
187
|
+
], RequestService);
|
|
188
188
|
|
|
189
189
|
// src/request/request.module.ts
|
|
190
190
|
function _ts_decorate2(decorators, target, key, desc) {
|
|
@@ -203,16 +203,256 @@ RequestModule = _ts_decorate2([
|
|
|
203
203
|
Global(),
|
|
204
204
|
Module({
|
|
205
205
|
providers: [
|
|
206
|
-
|
|
206
|
+
RequestService
|
|
207
207
|
],
|
|
208
208
|
exports: [
|
|
209
|
-
|
|
209
|
+
RequestService
|
|
210
210
|
]
|
|
211
211
|
})
|
|
212
212
|
], RequestModule);
|
|
213
213
|
|
|
214
214
|
// src/auth/guards/vritti-auth.guard.ts
|
|
215
|
-
import { Injectable as
|
|
215
|
+
import { Injectable as Injectable3, Logger as Logger3, Scope as Scope2, UnauthorizedException } from "@nestjs/common";
|
|
216
|
+
import { ConfigService } from "@nestjs/config";
|
|
217
|
+
import { Reflector } from "@nestjs/core";
|
|
218
|
+
import { JwtService } from "@nestjs/jwt";
|
|
219
|
+
|
|
220
|
+
// src/database/services/primary-database.service.ts
|
|
221
|
+
import { Inject as Inject2, Injectable as Injectable2, InternalServerErrorException, Logger as Logger2 } from "@nestjs/common";
|
|
222
|
+
import { eq, or } from "drizzle-orm";
|
|
223
|
+
import { drizzle } from "drizzle-orm/node-postgres";
|
|
224
|
+
import { Pool } from "pg";
|
|
225
|
+
|
|
226
|
+
// src/database/constants.ts
|
|
227
|
+
var DATABASE_MODULE_OPTIONS = Symbol("DATABASE_MODULE_OPTIONS");
|
|
228
|
+
|
|
229
|
+
// src/database/services/primary-database.service.ts
|
|
230
|
+
function _ts_decorate3(decorators, target, key, desc) {
|
|
231
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
232
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
233
|
+
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;
|
|
234
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
235
|
+
}
|
|
236
|
+
__name(_ts_decorate3, "_ts_decorate");
|
|
237
|
+
function _ts_metadata2(k, v) {
|
|
238
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
239
|
+
}
|
|
240
|
+
__name(_ts_metadata2, "_ts_metadata");
|
|
241
|
+
function _ts_param2(paramIndex, decorator) {
|
|
242
|
+
return function(target, key) {
|
|
243
|
+
decorator(target, key, paramIndex);
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
__name(_ts_param2, "_ts_param");
|
|
247
|
+
var PrimaryDatabaseService = class _PrimaryDatabaseService {
|
|
248
|
+
static {
|
|
249
|
+
__name(this, "PrimaryDatabaseService");
|
|
250
|
+
}
|
|
251
|
+
options;
|
|
252
|
+
logger = new Logger2(_PrimaryDatabaseService.name);
|
|
253
|
+
/** PostgreSQL connection pool */
|
|
254
|
+
pool = null;
|
|
255
|
+
/** Drizzle database instance */
|
|
256
|
+
db = null;
|
|
257
|
+
/** In-memory cache: Map<tenantIdentifier, TenantConfig> */
|
|
258
|
+
tenantConfigCache = /* @__PURE__ */ new Map();
|
|
259
|
+
/** Cache TTL in milliseconds */
|
|
260
|
+
cacheTTL;
|
|
261
|
+
constructor(options) {
|
|
262
|
+
this.options = options;
|
|
263
|
+
this.cacheTTL = options.connectionCacheTTL || 3e5;
|
|
264
|
+
}
|
|
265
|
+
async onModuleInit() {
|
|
266
|
+
if (this.options.primaryDb) {
|
|
267
|
+
await this.initializeDrizzleClient();
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Initialize connection to primary database using Drizzle
|
|
272
|
+
*/
|
|
273
|
+
async initializeDrizzleClient() {
|
|
274
|
+
try {
|
|
275
|
+
const databaseUrl = this.buildPrimaryDbUrl();
|
|
276
|
+
this.pool = new Pool({
|
|
277
|
+
connectionString: databaseUrl,
|
|
278
|
+
max: this.options.maxConnections || 10
|
|
279
|
+
});
|
|
280
|
+
this.logger.debug(`Schema keys passed to drizzle: [${Object.keys(this.options.drizzleSchema || {}).join(", ")}]`);
|
|
281
|
+
this.logger.debug(`Relations keys passed to drizzle: [${Object.keys(this.options.drizzleRelations || {}).join(", ")}]`);
|
|
282
|
+
this.db = drizzle({
|
|
283
|
+
client: this.pool,
|
|
284
|
+
schema: this.options.drizzleSchema,
|
|
285
|
+
relations: this.options.drizzleRelations
|
|
286
|
+
});
|
|
287
|
+
this.logger.debug(`Drizzle query keys after init: [${Object.keys(this.db.query || {}).join(", ")}]`);
|
|
288
|
+
await this.pool.query("SELECT 1");
|
|
289
|
+
this.logger.log("Connected to primary database (tenant registry)");
|
|
290
|
+
} catch (error) {
|
|
291
|
+
this.logger.error("Failed to connect to primary database", error);
|
|
292
|
+
throw new InternalServerErrorException("Failed to initialize tenant registry");
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Build connection URL from primary database properties
|
|
297
|
+
*/
|
|
298
|
+
buildPrimaryDbUrl() {
|
|
299
|
+
if (!this.options.primaryDb) {
|
|
300
|
+
throw new Error("Primary database configuration not provided");
|
|
301
|
+
}
|
|
302
|
+
const { host, port = 5432, username, password, database, schema = "public", sslMode = "require" } = this.options.primaryDb;
|
|
303
|
+
let url = `postgresql://${username}:${encodeURIComponent(password)}@${host}:${port}/${database}`;
|
|
304
|
+
const params = new URLSearchParams();
|
|
305
|
+
if (schema) {
|
|
306
|
+
params.set("schema", schema);
|
|
307
|
+
}
|
|
308
|
+
params.set("sslmode", sslMode);
|
|
309
|
+
const queryString = params.toString();
|
|
310
|
+
if (queryString) {
|
|
311
|
+
url += `?${queryString}`;
|
|
312
|
+
}
|
|
313
|
+
this.logger.debug(`Primary DB connection URL: ${this.maskPassword(url)}`);
|
|
314
|
+
return url;
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Mask password in connection URL for logging
|
|
318
|
+
*/
|
|
319
|
+
maskPassword(url) {
|
|
320
|
+
return url.replace(/:([^@]+)@/, ":****@");
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Get tenant configuration by identifier (ID or subdomain)
|
|
324
|
+
*
|
|
325
|
+
* @param tenantIdentifier Tenant ID or subdomain
|
|
326
|
+
* @returns Tenant configuration or null if not found
|
|
327
|
+
*/
|
|
328
|
+
async getTenantInfo(tenantIdentifier) {
|
|
329
|
+
const cached = this.tenantConfigCache.get(tenantIdentifier);
|
|
330
|
+
if (cached) {
|
|
331
|
+
this.logger.debug(`Cache hit for tenant: ${tenantIdentifier}`);
|
|
332
|
+
return cached;
|
|
333
|
+
}
|
|
334
|
+
try {
|
|
335
|
+
if (!this.db) {
|
|
336
|
+
throw new Error("Primary database client not initialized");
|
|
337
|
+
}
|
|
338
|
+
this.logger.debug(`Querying primary database for tenant: ${tenantIdentifier}`);
|
|
339
|
+
const schema = this.options.drizzleSchema;
|
|
340
|
+
const { tenants, tenantDatabaseConfigs } = schema;
|
|
341
|
+
const result = await this.db.select().from(tenants).leftJoin(tenantDatabaseConfigs, eq(tenants.id, tenantDatabaseConfigs.tenantId)).where(or(eq(tenants.id, tenantIdentifier), eq(tenants.subdomain, tenantIdentifier))).limit(1);
|
|
342
|
+
if (!result.length) {
|
|
343
|
+
this.logger.warn(`Tenant not found: ${tenantIdentifier}`);
|
|
344
|
+
return null;
|
|
345
|
+
}
|
|
346
|
+
const row = result[0];
|
|
347
|
+
const tenant = row.tenants;
|
|
348
|
+
const config = row.tenant_database_configs;
|
|
349
|
+
if (tenant.status !== "ACTIVE") {
|
|
350
|
+
this.logger.warn(`Tenant not active: ${tenantIdentifier}`);
|
|
351
|
+
return null;
|
|
352
|
+
}
|
|
353
|
+
const info = {
|
|
354
|
+
id: tenant.id,
|
|
355
|
+
subdomain: tenant.subdomain,
|
|
356
|
+
type: tenant.dbType,
|
|
357
|
+
status: tenant.status,
|
|
358
|
+
// For SHARED tenants: schema name
|
|
359
|
+
schemaName: config?.dbSchema || void 0,
|
|
360
|
+
// For DEDICATED tenants: database configuration from TenantDatabaseConfig table
|
|
361
|
+
databaseName: config?.dbName || void 0,
|
|
362
|
+
databaseHost: config?.dbHost || void 0,
|
|
363
|
+
databasePort: config?.dbPort || void 0,
|
|
364
|
+
databaseUsername: config?.dbUsername ? this.decrypt(config.dbUsername) : void 0,
|
|
365
|
+
databasePassword: config?.dbPassword ? this.decrypt(config.dbPassword) : void 0,
|
|
366
|
+
databaseSslMode: config?.dbSslMode || void 0,
|
|
367
|
+
connectionPoolSize: config?.connectionPoolSize || void 0
|
|
368
|
+
};
|
|
369
|
+
this.cacheInfo(info);
|
|
370
|
+
return info;
|
|
371
|
+
} catch (error) {
|
|
372
|
+
this.logger.error(`Failed to fetch tenant info: ${tenantIdentifier}`, error);
|
|
373
|
+
throw new InternalServerErrorException("Failed to resolve tenant");
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Cache tenant information with TTL
|
|
378
|
+
*/
|
|
379
|
+
cacheInfo(info) {
|
|
380
|
+
this.tenantConfigCache.set(info.id, info);
|
|
381
|
+
this.tenantConfigCache.set(info.subdomain, info);
|
|
382
|
+
setTimeout(() => {
|
|
383
|
+
this.tenantConfigCache.delete(info.id);
|
|
384
|
+
this.tenantConfigCache.delete(info.subdomain);
|
|
385
|
+
this.logger.debug(`Cache expired for tenant: ${info.subdomain}`);
|
|
386
|
+
}, this.cacheTTL);
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Clear cached tenant information
|
|
390
|
+
*
|
|
391
|
+
* Useful when tenant settings are updated and cache needs to be invalidated
|
|
392
|
+
*
|
|
393
|
+
* @param tenantIdentifier Tenant ID or subdomain
|
|
394
|
+
*/
|
|
395
|
+
clearTenantCache(tenantIdentifier) {
|
|
396
|
+
const config = this.tenantConfigCache.get(tenantIdentifier);
|
|
397
|
+
if (config) {
|
|
398
|
+
this.tenantConfigCache.delete(config.id);
|
|
399
|
+
this.tenantConfigCache.delete(config.subdomain);
|
|
400
|
+
this.logger.log(`Cleared cache for tenant: ${tenantIdentifier}`);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Clear all cached tenant configurations
|
|
405
|
+
*/
|
|
406
|
+
clearAllCaches() {
|
|
407
|
+
const size = this.tenantConfigCache.size;
|
|
408
|
+
this.tenantConfigCache.clear();
|
|
409
|
+
this.logger.log(`Cleared ${size} cached tenant configs`);
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Get the Drizzle database instance for the primary database.
|
|
413
|
+
* This is a synchronous property that returns the initialized Drizzle client.
|
|
414
|
+
*
|
|
415
|
+
* @returns Primary database Drizzle instance
|
|
416
|
+
* @throws Error if primary database client is not initialized
|
|
417
|
+
*/
|
|
418
|
+
get drizzleClient() {
|
|
419
|
+
if (!this.db) {
|
|
420
|
+
throw new Error("Primary database client not initialized");
|
|
421
|
+
}
|
|
422
|
+
return this.db;
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Get the Drizzle schema
|
|
426
|
+
*/
|
|
427
|
+
get schema() {
|
|
428
|
+
return this.options.drizzleSchema;
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Decrypt database credentials
|
|
432
|
+
*
|
|
433
|
+
* Override this method to implement your encryption strategy
|
|
434
|
+
*
|
|
435
|
+
* @param encrypted Encrypted value
|
|
436
|
+
* @returns Decrypted value
|
|
437
|
+
*/
|
|
438
|
+
decrypt(encrypted) {
|
|
439
|
+
return encrypted;
|
|
440
|
+
}
|
|
441
|
+
async onModuleDestroy() {
|
|
442
|
+
if (this.pool) {
|
|
443
|
+
await this.pool.end();
|
|
444
|
+
this.logger.log("Disconnected from primary database");
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
};
|
|
448
|
+
PrimaryDatabaseService = _ts_decorate3([
|
|
449
|
+
Injectable2(),
|
|
450
|
+
_ts_param2(0, Inject2(DATABASE_MODULE_OPTIONS)),
|
|
451
|
+
_ts_metadata2("design:type", Function),
|
|
452
|
+
_ts_metadata2("design:paramtypes", [
|
|
453
|
+
typeof DatabaseModuleOptions === "undefined" ? Object : DatabaseModuleOptions
|
|
454
|
+
])
|
|
455
|
+
], PrimaryDatabaseService);
|
|
216
456
|
|
|
217
457
|
// src/auth/utils/token-hash.util.ts
|
|
218
458
|
import * as crypto from "crypto";
|
|
@@ -228,17 +468,17 @@ function verifyTokenHash(token, expectedHash) {
|
|
|
228
468
|
__name(verifyTokenHash, "verifyTokenHash");
|
|
229
469
|
|
|
230
470
|
// src/auth/guards/vritti-auth.guard.ts
|
|
231
|
-
function
|
|
471
|
+
function _ts_decorate4(decorators, target, key, desc) {
|
|
232
472
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
233
473
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
234
474
|
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;
|
|
235
475
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
236
476
|
}
|
|
237
|
-
__name(
|
|
238
|
-
function
|
|
477
|
+
__name(_ts_decorate4, "_ts_decorate");
|
|
478
|
+
function _ts_metadata3(k, v) {
|
|
239
479
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
240
480
|
}
|
|
241
|
-
__name(
|
|
481
|
+
__name(_ts_metadata3, "_ts_metadata");
|
|
242
482
|
var VrittiAuthGuard = class _VrittiAuthGuard {
|
|
243
483
|
static {
|
|
244
484
|
__name(this, "VrittiAuthGuard");
|
|
@@ -248,7 +488,7 @@ var VrittiAuthGuard = class _VrittiAuthGuard {
|
|
|
248
488
|
jwtService;
|
|
249
489
|
primaryDatabase;
|
|
250
490
|
requestService;
|
|
251
|
-
logger = new
|
|
491
|
+
logger = new Logger3(_VrittiAuthGuard.name);
|
|
252
492
|
constructor(reflector, _configService, jwtService, primaryDatabase, requestService) {
|
|
253
493
|
this.reflector = reflector;
|
|
254
494
|
this._configService = _configService;
|
|
@@ -404,12 +644,12 @@ var VrittiAuthGuard = class _VrittiAuthGuard {
|
|
|
404
644
|
this.logger.debug("Token binding validated successfully");
|
|
405
645
|
}
|
|
406
646
|
};
|
|
407
|
-
VrittiAuthGuard =
|
|
408
|
-
|
|
647
|
+
VrittiAuthGuard = _ts_decorate4([
|
|
648
|
+
Injectable3({
|
|
409
649
|
scope: Scope2.REQUEST
|
|
410
650
|
}),
|
|
411
|
-
|
|
412
|
-
|
|
651
|
+
_ts_metadata3("design:type", Function),
|
|
652
|
+
_ts_metadata3("design:paramtypes", [
|
|
413
653
|
typeof Reflector === "undefined" ? Object : Reflector,
|
|
414
654
|
typeof ConfigService === "undefined" ? Object : ConfigService,
|
|
415
655
|
typeof JwtService === "undefined" ? Object : JwtService,
|
|
@@ -419,13 +659,13 @@ VrittiAuthGuard = _ts_decorate3([
|
|
|
419
659
|
], VrittiAuthGuard);
|
|
420
660
|
|
|
421
661
|
// src/auth/auth-config.module.ts
|
|
422
|
-
function
|
|
662
|
+
function _ts_decorate5(decorators, target, key, desc) {
|
|
423
663
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
424
664
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
425
665
|
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;
|
|
426
666
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
427
667
|
}
|
|
428
|
-
__name(
|
|
668
|
+
__name(_ts_decorate5, "_ts_decorate");
|
|
429
669
|
var AuthConfigModule = class _AuthConfigModule {
|
|
430
670
|
static {
|
|
431
671
|
__name(this, "AuthConfigModule");
|
|
@@ -473,7 +713,7 @@ var AuthConfigModule = class _AuthConfigModule {
|
|
|
473
713
|
};
|
|
474
714
|
}
|
|
475
715
|
};
|
|
476
|
-
AuthConfigModule =
|
|
716
|
+
AuthConfigModule = _ts_decorate5([
|
|
477
717
|
Global2(),
|
|
478
718
|
Module2({})
|
|
479
719
|
], AuthConfigModule);
|
|
@@ -490,29 +730,114 @@ var Public = /* @__PURE__ */ __name(() => SetMetadata2("isPublic", true), "Publi
|
|
|
490
730
|
import { Global as Global3, Module as Module3 } from "@nestjs/common";
|
|
491
731
|
import { APP_INTERCEPTOR } from "@nestjs/core";
|
|
492
732
|
|
|
493
|
-
// src/database/constants.ts
|
|
494
|
-
var DATABASE_MODULE_OPTIONS = Symbol("DATABASE_MODULE_OPTIONS");
|
|
495
|
-
|
|
496
733
|
// src/database/interceptors/message-tenant-context.interceptor.ts
|
|
497
|
-
import { Injectable as
|
|
734
|
+
import { Injectable as Injectable5, Logger as Logger4, Scope as Scope4 } from "@nestjs/common";
|
|
498
735
|
import { tap } from "rxjs/operators";
|
|
499
|
-
|
|
736
|
+
|
|
737
|
+
// src/database/services/tenant-context.service.ts
|
|
738
|
+
import { Injectable as Injectable4, Scope as Scope3, UnauthorizedException as UnauthorizedException2 } from "@nestjs/common";
|
|
739
|
+
function _ts_decorate6(decorators, target, key, desc) {
|
|
500
740
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
501
741
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
502
742
|
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;
|
|
503
743
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
504
744
|
}
|
|
505
|
-
__name(
|
|
506
|
-
|
|
745
|
+
__name(_ts_decorate6, "_ts_decorate");
|
|
746
|
+
var TenantContextService = class {
|
|
747
|
+
static {
|
|
748
|
+
__name(this, "TenantContextService");
|
|
749
|
+
}
|
|
750
|
+
tenantInfo = null;
|
|
751
|
+
/**
|
|
752
|
+
* Set tenant information for this request/message
|
|
753
|
+
*
|
|
754
|
+
* This is typically called by:
|
|
755
|
+
* - TenantContextInterceptor (for HTTP requests in gateway)
|
|
756
|
+
* - MessageTenantContextInterceptor (for RabbitMQ messages in microservices)
|
|
757
|
+
* - Manual context setup in message handlers
|
|
758
|
+
*
|
|
759
|
+
* @param tenantInfo Complete tenant information
|
|
760
|
+
* @throws Error if tenant context is already set (prevents accidental overwrites)
|
|
761
|
+
*/
|
|
762
|
+
setTenant(tenantInfo) {
|
|
763
|
+
if (this.tenantInfo) {
|
|
764
|
+
throw new Error("Tenant context already set for this request");
|
|
765
|
+
}
|
|
766
|
+
this.tenantInfo = tenantInfo;
|
|
767
|
+
}
|
|
768
|
+
/**
|
|
769
|
+
* Get tenant information for this request/message
|
|
770
|
+
*
|
|
771
|
+
* @returns Tenant information
|
|
772
|
+
* @throws UnauthorizedException if tenant context hasn't been set
|
|
773
|
+
*/
|
|
774
|
+
getTenant() {
|
|
775
|
+
if (!this.tenantInfo) {
|
|
776
|
+
throw new UnauthorizedException2("Tenant context not set");
|
|
777
|
+
}
|
|
778
|
+
return this.tenantInfo;
|
|
779
|
+
}
|
|
780
|
+
/**
|
|
781
|
+
* Check if tenant context has been set
|
|
782
|
+
*
|
|
783
|
+
* @returns true if tenant context is available
|
|
784
|
+
*/
|
|
785
|
+
hasTenant() {
|
|
786
|
+
return this.tenantInfo !== null;
|
|
787
|
+
}
|
|
788
|
+
/**
|
|
789
|
+
* Clear tenant context
|
|
790
|
+
*
|
|
791
|
+
* This is useful for cleanup in RabbitMQ message handlers
|
|
792
|
+
* after the message has been processed.
|
|
793
|
+
*
|
|
794
|
+
* HTTP requests don't need manual cleanup as the service
|
|
795
|
+
* instance is destroyed when the request ends.
|
|
796
|
+
*/
|
|
797
|
+
clearTenant() {
|
|
798
|
+
this.tenantInfo = null;
|
|
799
|
+
}
|
|
800
|
+
/**
|
|
801
|
+
* Get tenant ID safely (returns null if not set)
|
|
802
|
+
*
|
|
803
|
+
* @returns Tenant ID or null
|
|
804
|
+
*/
|
|
805
|
+
getTenantIdSafe() {
|
|
806
|
+
return this.tenantInfo?.id ?? null;
|
|
807
|
+
}
|
|
808
|
+
/**
|
|
809
|
+
* Get tenant subdomain safely (returns null if not set)
|
|
810
|
+
*
|
|
811
|
+
* @returns Tenant subdomain or null
|
|
812
|
+
*/
|
|
813
|
+
getTenantSubdomainSafe() {
|
|
814
|
+
return this.tenantInfo?.subdomain ?? null;
|
|
815
|
+
}
|
|
816
|
+
};
|
|
817
|
+
TenantContextService = _ts_decorate6([
|
|
818
|
+
Injectable4({
|
|
819
|
+
scope: Scope3.REQUEST
|
|
820
|
+
})
|
|
821
|
+
], TenantContextService);
|
|
822
|
+
|
|
823
|
+
// src/database/interceptors/message-tenant-context.interceptor.ts
|
|
824
|
+
function _ts_decorate7(decorators, target, key, desc) {
|
|
825
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
826
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
827
|
+
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;
|
|
828
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
829
|
+
}
|
|
830
|
+
__name(_ts_decorate7, "_ts_decorate");
|
|
831
|
+
function _ts_metadata4(k, v) {
|
|
507
832
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
508
833
|
}
|
|
509
|
-
__name(
|
|
834
|
+
__name(_ts_metadata4, "_ts_metadata");
|
|
510
835
|
var MessageTenantContextInterceptor = class _MessageTenantContextInterceptor {
|
|
511
836
|
static {
|
|
512
837
|
__name(this, "MessageTenantContextInterceptor");
|
|
513
838
|
}
|
|
514
839
|
tenantContext;
|
|
515
|
-
logger = new
|
|
840
|
+
logger = new Logger4(_MessageTenantContextInterceptor.name);
|
|
516
841
|
constructor(tenantContext) {
|
|
517
842
|
this.tenantContext = tenantContext;
|
|
518
843
|
}
|
|
@@ -557,29 +882,30 @@ var MessageTenantContextInterceptor = class _MessageTenantContextInterceptor {
|
|
|
557
882
|
}
|
|
558
883
|
}
|
|
559
884
|
};
|
|
560
|
-
MessageTenantContextInterceptor =
|
|
561
|
-
|
|
562
|
-
scope:
|
|
885
|
+
MessageTenantContextInterceptor = _ts_decorate7([
|
|
886
|
+
Injectable5({
|
|
887
|
+
scope: Scope4.REQUEST
|
|
563
888
|
}),
|
|
564
|
-
|
|
565
|
-
|
|
889
|
+
_ts_metadata4("design:type", Function),
|
|
890
|
+
_ts_metadata4("design:paramtypes", [
|
|
566
891
|
typeof TenantContextService === "undefined" ? Object : TenantContextService
|
|
567
892
|
])
|
|
568
893
|
], MessageTenantContextInterceptor);
|
|
569
894
|
|
|
570
895
|
// src/database/interceptors/tenant-context.interceptor.ts
|
|
571
|
-
import { Injectable as
|
|
572
|
-
|
|
896
|
+
import { Injectable as Injectable6, Logger as Logger5, Scope as Scope5, UnauthorizedException as UnauthorizedException3 } from "@nestjs/common";
|
|
897
|
+
import { Reflector as Reflector2 } from "@nestjs/core";
|
|
898
|
+
function _ts_decorate8(decorators, target, key, desc) {
|
|
573
899
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
574
900
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
575
901
|
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;
|
|
576
902
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
577
903
|
}
|
|
578
|
-
__name(
|
|
579
|
-
function
|
|
904
|
+
__name(_ts_decorate8, "_ts_decorate");
|
|
905
|
+
function _ts_metadata5(k, v) {
|
|
580
906
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
581
907
|
}
|
|
582
|
-
__name(
|
|
908
|
+
__name(_ts_metadata5, "_ts_metadata");
|
|
583
909
|
var TenantContextInterceptor = class _TenantContextInterceptor {
|
|
584
910
|
static {
|
|
585
911
|
__name(this, "TenantContextInterceptor");
|
|
@@ -588,7 +914,7 @@ var TenantContextInterceptor = class _TenantContextInterceptor {
|
|
|
588
914
|
tenantContext;
|
|
589
915
|
primaryDatabase;
|
|
590
916
|
requestService;
|
|
591
|
-
logger = new
|
|
917
|
+
logger = new Logger5(_TenantContextInterceptor.name);
|
|
592
918
|
constructor(reflector, tenantContext, primaryDatabase, requestService) {
|
|
593
919
|
this.reflector = reflector;
|
|
594
920
|
this.tenantContext = tenantContext;
|
|
@@ -609,7 +935,7 @@ var TenantContextInterceptor = class _TenantContextInterceptor {
|
|
|
609
935
|
return next.handle();
|
|
610
936
|
}
|
|
611
937
|
if (!tenantIdentifier) {
|
|
612
|
-
throw new
|
|
938
|
+
throw new UnauthorizedException3("Tenant identifier not found in request");
|
|
613
939
|
}
|
|
614
940
|
this.logger.debug(`Tenant identifier extracted: ${tenantIdentifier}`);
|
|
615
941
|
if (tenantIdentifier === "cloud") {
|
|
@@ -619,353 +945,35 @@ var TenantContextInterceptor = class _TenantContextInterceptor {
|
|
|
619
945
|
const tenantInfo = await this.primaryDatabase.getTenantInfo(tenantIdentifier);
|
|
620
946
|
if (!tenantInfo) {
|
|
621
947
|
this.logger.warn(`Invalid tenant: ${tenantIdentifier}`);
|
|
622
|
-
throw new
|
|
948
|
+
throw new UnauthorizedException3("Invalid tenant");
|
|
623
949
|
}
|
|
624
950
|
if (tenantInfo.status !== "ACTIVE") {
|
|
625
|
-
this.logger.warn(`Tenant ${tenantIdentifier} has status: ${tenantInfo.status}`);
|
|
626
|
-
throw new
|
|
627
|
-
}
|
|
628
|
-
this.logger.debug(`Tenant config loaded: ${tenantInfo.subdomain} (${tenantInfo.type})`);
|
|
629
|
-
this.tenantContext.setTenant(tenantInfo);
|
|
630
|
-
request.tenant = tenantInfo;
|
|
631
|
-
this.logger.log(`Tenant context set: ${tenantInfo.subdomain}`);
|
|
632
|
-
} catch (error) {
|
|
633
|
-
this.logger.error("Failed to set tenant context", error);
|
|
634
|
-
throw error;
|
|
635
|
-
}
|
|
636
|
-
return next.handle();
|
|
637
|
-
}
|
|
638
|
-
};
|
|
639
|
-
TenantContextInterceptor = _ts_decorate6([
|
|
640
|
-
Injectable4({
|
|
641
|
-
scope: Scope4.REQUEST
|
|
642
|
-
}),
|
|
643
|
-
_ts_metadata4("design:type", Function),
|
|
644
|
-
_ts_metadata4("design:paramtypes", [
|
|
645
|
-
typeof Reflector === "undefined" ? Object : Reflector,
|
|
646
|
-
typeof TenantContextService === "undefined" ? Object : TenantContextService,
|
|
647
|
-
typeof PrimaryDatabaseService === "undefined" ? Object : PrimaryDatabaseService,
|
|
648
|
-
typeof RequestService === "undefined" ? Object : RequestService
|
|
649
|
-
])
|
|
650
|
-
], TenantContextInterceptor);
|
|
651
|
-
|
|
652
|
-
// src/database/services/primary-database.service.ts
|
|
653
|
-
import { Inject as Inject2, Injectable as Injectable5, InternalServerErrorException, Logger as Logger5 } from "@nestjs/common";
|
|
654
|
-
import { eq, or } from "drizzle-orm";
|
|
655
|
-
import { drizzle } from "drizzle-orm/node-postgres";
|
|
656
|
-
import { Pool } from "pg";
|
|
657
|
-
function _ts_decorate7(decorators, target, key, desc) {
|
|
658
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
659
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
660
|
-
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;
|
|
661
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
662
|
-
}
|
|
663
|
-
__name(_ts_decorate7, "_ts_decorate");
|
|
664
|
-
function _ts_metadata5(k, v) {
|
|
665
|
-
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
666
|
-
}
|
|
667
|
-
__name(_ts_metadata5, "_ts_metadata");
|
|
668
|
-
function _ts_param2(paramIndex, decorator) {
|
|
669
|
-
return function(target, key) {
|
|
670
|
-
decorator(target, key, paramIndex);
|
|
671
|
-
};
|
|
672
|
-
}
|
|
673
|
-
__name(_ts_param2, "_ts_param");
|
|
674
|
-
var PrimaryDatabaseService2 = class _PrimaryDatabaseService {
|
|
675
|
-
static {
|
|
676
|
-
__name(this, "PrimaryDatabaseService");
|
|
677
|
-
}
|
|
678
|
-
options;
|
|
679
|
-
logger = new Logger5(_PrimaryDatabaseService.name);
|
|
680
|
-
/** PostgreSQL connection pool */
|
|
681
|
-
pool = null;
|
|
682
|
-
/** Drizzle database instance */
|
|
683
|
-
db = null;
|
|
684
|
-
/** In-memory cache: Map<tenantIdentifier, TenantConfig> */
|
|
685
|
-
tenantConfigCache = /* @__PURE__ */ new Map();
|
|
686
|
-
/** Cache TTL in milliseconds */
|
|
687
|
-
cacheTTL;
|
|
688
|
-
constructor(options) {
|
|
689
|
-
this.options = options;
|
|
690
|
-
this.cacheTTL = options.connectionCacheTTL || 3e5;
|
|
691
|
-
}
|
|
692
|
-
async onModuleInit() {
|
|
693
|
-
if (this.options.primaryDb) {
|
|
694
|
-
await this.initializeDrizzleClient();
|
|
695
|
-
}
|
|
696
|
-
}
|
|
697
|
-
/**
|
|
698
|
-
* Initialize connection to primary database using Drizzle
|
|
699
|
-
*/
|
|
700
|
-
async initializeDrizzleClient() {
|
|
701
|
-
try {
|
|
702
|
-
const databaseUrl = this.buildPrimaryDbUrl();
|
|
703
|
-
this.pool = new Pool({
|
|
704
|
-
connectionString: databaseUrl,
|
|
705
|
-
max: this.options.maxConnections || 10
|
|
706
|
-
});
|
|
707
|
-
this.logger.debug(`Schema keys passed to drizzle: [${Object.keys(this.options.drizzleSchema || {}).join(", ")}]`);
|
|
708
|
-
this.logger.debug(`Relations keys passed to drizzle: [${Object.keys(this.options.drizzleRelations || {}).join(", ")}]`);
|
|
709
|
-
this.db = drizzle({
|
|
710
|
-
client: this.pool,
|
|
711
|
-
schema: this.options.drizzleSchema,
|
|
712
|
-
relations: this.options.drizzleRelations
|
|
713
|
-
});
|
|
714
|
-
this.logger.debug(`Drizzle query keys after init: [${Object.keys(this.db.query || {}).join(", ")}]`);
|
|
715
|
-
await this.pool.query("SELECT 1");
|
|
716
|
-
this.logger.log("Connected to primary database (tenant registry)");
|
|
717
|
-
} catch (error) {
|
|
718
|
-
this.logger.error("Failed to connect to primary database", error);
|
|
719
|
-
throw new InternalServerErrorException("Failed to initialize tenant registry");
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
/**
|
|
723
|
-
* Build connection URL from primary database properties
|
|
724
|
-
*/
|
|
725
|
-
buildPrimaryDbUrl() {
|
|
726
|
-
if (!this.options.primaryDb) {
|
|
727
|
-
throw new Error("Primary database configuration not provided");
|
|
728
|
-
}
|
|
729
|
-
const { host, port = 5432, username, password, database, schema = "public", sslMode = "require" } = this.options.primaryDb;
|
|
730
|
-
let url = `postgresql://${username}:${encodeURIComponent(password)}@${host}:${port}/${database}`;
|
|
731
|
-
const params = new URLSearchParams();
|
|
732
|
-
if (schema) {
|
|
733
|
-
params.set("schema", schema);
|
|
734
|
-
}
|
|
735
|
-
params.set("sslmode", sslMode);
|
|
736
|
-
const queryString = params.toString();
|
|
737
|
-
if (queryString) {
|
|
738
|
-
url += `?${queryString}`;
|
|
739
|
-
}
|
|
740
|
-
this.logger.debug(`Primary DB connection URL: ${this.maskPassword(url)}`);
|
|
741
|
-
return url;
|
|
742
|
-
}
|
|
743
|
-
/**
|
|
744
|
-
* Mask password in connection URL for logging
|
|
745
|
-
*/
|
|
746
|
-
maskPassword(url) {
|
|
747
|
-
return url.replace(/:([^@]+)@/, ":****@");
|
|
748
|
-
}
|
|
749
|
-
/**
|
|
750
|
-
* Get tenant configuration by identifier (ID or subdomain)
|
|
751
|
-
*
|
|
752
|
-
* @param tenantIdentifier Tenant ID or subdomain
|
|
753
|
-
* @returns Tenant configuration or null if not found
|
|
754
|
-
*/
|
|
755
|
-
async getTenantInfo(tenantIdentifier) {
|
|
756
|
-
const cached = this.tenantConfigCache.get(tenantIdentifier);
|
|
757
|
-
if (cached) {
|
|
758
|
-
this.logger.debug(`Cache hit for tenant: ${tenantIdentifier}`);
|
|
759
|
-
return cached;
|
|
760
|
-
}
|
|
761
|
-
try {
|
|
762
|
-
if (!this.db) {
|
|
763
|
-
throw new Error("Primary database client not initialized");
|
|
764
|
-
}
|
|
765
|
-
this.logger.debug(`Querying primary database for tenant: ${tenantIdentifier}`);
|
|
766
|
-
const schema = this.options.drizzleSchema;
|
|
767
|
-
const { tenants, tenantDatabaseConfigs } = schema;
|
|
768
|
-
const result = await this.db.select().from(tenants).leftJoin(tenantDatabaseConfigs, eq(tenants.id, tenantDatabaseConfigs.tenantId)).where(or(eq(tenants.id, tenantIdentifier), eq(tenants.subdomain, tenantIdentifier))).limit(1);
|
|
769
|
-
if (!result.length) {
|
|
770
|
-
this.logger.warn(`Tenant not found: ${tenantIdentifier}`);
|
|
771
|
-
return null;
|
|
772
|
-
}
|
|
773
|
-
const row = result[0];
|
|
774
|
-
const tenant = row.tenants;
|
|
775
|
-
const config = row.tenant_database_configs;
|
|
776
|
-
if (tenant.status !== "ACTIVE") {
|
|
777
|
-
this.logger.warn(`Tenant not active: ${tenantIdentifier}`);
|
|
778
|
-
return null;
|
|
779
|
-
}
|
|
780
|
-
const info = {
|
|
781
|
-
id: tenant.id,
|
|
782
|
-
subdomain: tenant.subdomain,
|
|
783
|
-
type: tenant.dbType,
|
|
784
|
-
status: tenant.status,
|
|
785
|
-
// For SHARED tenants: schema name
|
|
786
|
-
schemaName: config?.dbSchema || void 0,
|
|
787
|
-
// For DEDICATED tenants: database configuration from TenantDatabaseConfig table
|
|
788
|
-
databaseName: config?.dbName || void 0,
|
|
789
|
-
databaseHost: config?.dbHost || void 0,
|
|
790
|
-
databasePort: config?.dbPort || void 0,
|
|
791
|
-
databaseUsername: config?.dbUsername ? this.decrypt(config.dbUsername) : void 0,
|
|
792
|
-
databasePassword: config?.dbPassword ? this.decrypt(config.dbPassword) : void 0,
|
|
793
|
-
databaseSslMode: config?.dbSslMode || void 0,
|
|
794
|
-
connectionPoolSize: config?.connectionPoolSize || void 0
|
|
795
|
-
};
|
|
796
|
-
this.cacheInfo(info);
|
|
797
|
-
return info;
|
|
798
|
-
} catch (error) {
|
|
799
|
-
this.logger.error(`Failed to fetch tenant info: ${tenantIdentifier}`, error);
|
|
800
|
-
throw new InternalServerErrorException("Failed to resolve tenant");
|
|
801
|
-
}
|
|
802
|
-
}
|
|
803
|
-
/**
|
|
804
|
-
* Cache tenant information with TTL
|
|
805
|
-
*/
|
|
806
|
-
cacheInfo(info) {
|
|
807
|
-
this.tenantConfigCache.set(info.id, info);
|
|
808
|
-
this.tenantConfigCache.set(info.subdomain, info);
|
|
809
|
-
setTimeout(() => {
|
|
810
|
-
this.tenantConfigCache.delete(info.id);
|
|
811
|
-
this.tenantConfigCache.delete(info.subdomain);
|
|
812
|
-
this.logger.debug(`Cache expired for tenant: ${info.subdomain}`);
|
|
813
|
-
}, this.cacheTTL);
|
|
814
|
-
}
|
|
815
|
-
/**
|
|
816
|
-
* Clear cached tenant information
|
|
817
|
-
*
|
|
818
|
-
* Useful when tenant settings are updated and cache needs to be invalidated
|
|
819
|
-
*
|
|
820
|
-
* @param tenantIdentifier Tenant ID or subdomain
|
|
821
|
-
*/
|
|
822
|
-
clearTenantCache(tenantIdentifier) {
|
|
823
|
-
const config = this.tenantConfigCache.get(tenantIdentifier);
|
|
824
|
-
if (config) {
|
|
825
|
-
this.tenantConfigCache.delete(config.id);
|
|
826
|
-
this.tenantConfigCache.delete(config.subdomain);
|
|
827
|
-
this.logger.log(`Cleared cache for tenant: ${tenantIdentifier}`);
|
|
828
|
-
}
|
|
829
|
-
}
|
|
830
|
-
/**
|
|
831
|
-
* Clear all cached tenant configurations
|
|
832
|
-
*/
|
|
833
|
-
clearAllCaches() {
|
|
834
|
-
const size = this.tenantConfigCache.size;
|
|
835
|
-
this.tenantConfigCache.clear();
|
|
836
|
-
this.logger.log(`Cleared ${size} cached tenant configs`);
|
|
837
|
-
}
|
|
838
|
-
/**
|
|
839
|
-
* Get the Drizzle database instance for the primary database.
|
|
840
|
-
* This is a synchronous property that returns the initialized Drizzle client.
|
|
841
|
-
*
|
|
842
|
-
* @returns Primary database Drizzle instance
|
|
843
|
-
* @throws Error if primary database client is not initialized
|
|
844
|
-
*/
|
|
845
|
-
get drizzleClient() {
|
|
846
|
-
if (!this.db) {
|
|
847
|
-
throw new Error("Primary database client not initialized");
|
|
848
|
-
}
|
|
849
|
-
return this.db;
|
|
850
|
-
}
|
|
851
|
-
/**
|
|
852
|
-
* Get the Drizzle schema
|
|
853
|
-
*/
|
|
854
|
-
get schema() {
|
|
855
|
-
return this.options.drizzleSchema;
|
|
856
|
-
}
|
|
857
|
-
/**
|
|
858
|
-
* Decrypt database credentials
|
|
859
|
-
*
|
|
860
|
-
* Override this method to implement your encryption strategy
|
|
861
|
-
*
|
|
862
|
-
* @param encrypted Encrypted value
|
|
863
|
-
* @returns Decrypted value
|
|
864
|
-
*/
|
|
865
|
-
decrypt(encrypted) {
|
|
866
|
-
return encrypted;
|
|
867
|
-
}
|
|
868
|
-
async onModuleDestroy() {
|
|
869
|
-
if (this.pool) {
|
|
870
|
-
await this.pool.end();
|
|
871
|
-
this.logger.log("Disconnected from primary database");
|
|
872
|
-
}
|
|
873
|
-
}
|
|
874
|
-
};
|
|
875
|
-
PrimaryDatabaseService2 = _ts_decorate7([
|
|
876
|
-
Injectable5(),
|
|
877
|
-
_ts_param2(0, Inject2(DATABASE_MODULE_OPTIONS)),
|
|
878
|
-
_ts_metadata5("design:type", Function),
|
|
879
|
-
_ts_metadata5("design:paramtypes", [
|
|
880
|
-
typeof DatabaseModuleOptions === "undefined" ? Object : DatabaseModuleOptions
|
|
881
|
-
])
|
|
882
|
-
], PrimaryDatabaseService2);
|
|
883
|
-
|
|
884
|
-
// src/database/services/tenant-context.service.ts
|
|
885
|
-
import { Injectable as Injectable6, Scope as Scope5, UnauthorizedException as UnauthorizedException3 } from "@nestjs/common";
|
|
886
|
-
function _ts_decorate8(decorators, target, key, desc) {
|
|
887
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
888
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
889
|
-
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;
|
|
890
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
891
|
-
}
|
|
892
|
-
__name(_ts_decorate8, "_ts_decorate");
|
|
893
|
-
var TenantContextService2 = class {
|
|
894
|
-
static {
|
|
895
|
-
__name(this, "TenantContextService");
|
|
896
|
-
}
|
|
897
|
-
tenantInfo = null;
|
|
898
|
-
/**
|
|
899
|
-
* Set tenant information for this request/message
|
|
900
|
-
*
|
|
901
|
-
* This is typically called by:
|
|
902
|
-
* - TenantContextInterceptor (for HTTP requests in gateway)
|
|
903
|
-
* - MessageTenantContextInterceptor (for RabbitMQ messages in microservices)
|
|
904
|
-
* - Manual context setup in message handlers
|
|
905
|
-
*
|
|
906
|
-
* @param tenantInfo Complete tenant information
|
|
907
|
-
* @throws Error if tenant context is already set (prevents accidental overwrites)
|
|
908
|
-
*/
|
|
909
|
-
setTenant(tenantInfo) {
|
|
910
|
-
if (this.tenantInfo) {
|
|
911
|
-
throw new Error("Tenant context already set for this request");
|
|
912
|
-
}
|
|
913
|
-
this.tenantInfo = tenantInfo;
|
|
914
|
-
}
|
|
915
|
-
/**
|
|
916
|
-
* Get tenant information for this request/message
|
|
917
|
-
*
|
|
918
|
-
* @returns Tenant information
|
|
919
|
-
* @throws UnauthorizedException if tenant context hasn't been set
|
|
920
|
-
*/
|
|
921
|
-
getTenant() {
|
|
922
|
-
if (!this.tenantInfo) {
|
|
923
|
-
throw new UnauthorizedException3("Tenant context not set");
|
|
924
|
-
}
|
|
925
|
-
return this.tenantInfo;
|
|
926
|
-
}
|
|
927
|
-
/**
|
|
928
|
-
* Check if tenant context has been set
|
|
929
|
-
*
|
|
930
|
-
* @returns true if tenant context is available
|
|
931
|
-
*/
|
|
932
|
-
hasTenant() {
|
|
933
|
-
return this.tenantInfo !== null;
|
|
934
|
-
}
|
|
935
|
-
/**
|
|
936
|
-
* Clear tenant context
|
|
937
|
-
*
|
|
938
|
-
* This is useful for cleanup in RabbitMQ message handlers
|
|
939
|
-
* after the message has been processed.
|
|
940
|
-
*
|
|
941
|
-
* HTTP requests don't need manual cleanup as the service
|
|
942
|
-
* instance is destroyed when the request ends.
|
|
943
|
-
*/
|
|
944
|
-
clearTenant() {
|
|
945
|
-
this.tenantInfo = null;
|
|
946
|
-
}
|
|
947
|
-
/**
|
|
948
|
-
* Get tenant ID safely (returns null if not set)
|
|
949
|
-
*
|
|
950
|
-
* @returns Tenant ID or null
|
|
951
|
-
*/
|
|
952
|
-
getTenantIdSafe() {
|
|
953
|
-
return this.tenantInfo?.id ?? null;
|
|
954
|
-
}
|
|
955
|
-
/**
|
|
956
|
-
* Get tenant subdomain safely (returns null if not set)
|
|
957
|
-
*
|
|
958
|
-
* @returns Tenant subdomain or null
|
|
959
|
-
*/
|
|
960
|
-
getTenantSubdomainSafe() {
|
|
961
|
-
return this.tenantInfo?.subdomain ?? null;
|
|
951
|
+
this.logger.warn(`Tenant ${tenantIdentifier} has status: ${tenantInfo.status}`);
|
|
952
|
+
throw new UnauthorizedException3(`Tenant is ${tenantInfo.status}`);
|
|
953
|
+
}
|
|
954
|
+
this.logger.debug(`Tenant config loaded: ${tenantInfo.subdomain} (${tenantInfo.type})`);
|
|
955
|
+
this.tenantContext.setTenant(tenantInfo);
|
|
956
|
+
request.tenant = tenantInfo;
|
|
957
|
+
this.logger.log(`Tenant context set: ${tenantInfo.subdomain}`);
|
|
958
|
+
} catch (error) {
|
|
959
|
+
this.logger.error("Failed to set tenant context", error);
|
|
960
|
+
throw error;
|
|
961
|
+
}
|
|
962
|
+
return next.handle();
|
|
962
963
|
}
|
|
963
964
|
};
|
|
964
|
-
|
|
965
|
+
TenantContextInterceptor = _ts_decorate8([
|
|
965
966
|
Injectable6({
|
|
966
967
|
scope: Scope5.REQUEST
|
|
967
|
-
})
|
|
968
|
-
|
|
968
|
+
}),
|
|
969
|
+
_ts_metadata5("design:type", Function),
|
|
970
|
+
_ts_metadata5("design:paramtypes", [
|
|
971
|
+
typeof Reflector2 === "undefined" ? Object : Reflector2,
|
|
972
|
+
typeof TenantContextService === "undefined" ? Object : TenantContextService,
|
|
973
|
+
typeof PrimaryDatabaseService === "undefined" ? Object : PrimaryDatabaseService,
|
|
974
|
+
typeof RequestService === "undefined" ? Object : RequestService
|
|
975
|
+
])
|
|
976
|
+
], TenantContextInterceptor);
|
|
969
977
|
|
|
970
978
|
// src/database/services/tenant-database.service.ts
|
|
971
979
|
import { Inject as Inject3, Injectable as Injectable7, InternalServerErrorException as InternalServerErrorException2, Logger as Logger6 } from "@nestjs/common";
|
|
@@ -1252,8 +1260,8 @@ var DatabaseModule = class _DatabaseModule {
|
|
|
1252
1260
|
};
|
|
1253
1261
|
const providers = [
|
|
1254
1262
|
asyncProvider,
|
|
1255
|
-
|
|
1256
|
-
|
|
1263
|
+
TenantContextService,
|
|
1264
|
+
PrimaryDatabaseService,
|
|
1257
1265
|
TenantDatabaseService
|
|
1258
1266
|
];
|
|
1259
1267
|
if (mode === "server") {
|
|
@@ -1275,8 +1283,8 @@ var DatabaseModule = class _DatabaseModule {
|
|
|
1275
1283
|
providers,
|
|
1276
1284
|
exports: [
|
|
1277
1285
|
TenantDatabaseService,
|
|
1278
|
-
|
|
1279
|
-
|
|
1286
|
+
TenantContextService,
|
|
1287
|
+
PrimaryDatabaseService,
|
|
1280
1288
|
asyncProvider
|
|
1281
1289
|
]
|
|
1282
1290
|
};
|
|
@@ -1291,7 +1299,7 @@ DatabaseModule = _ts_decorate10([
|
|
|
1291
1299
|
import { createParamDecorator } from "@nestjs/common";
|
|
1292
1300
|
var Tenant = createParamDecorator((_data, ctx) => {
|
|
1293
1301
|
const request = ctx.switchToHttp().getRequest();
|
|
1294
|
-
const tenantContext = request.app?.get?.(
|
|
1302
|
+
const tenantContext = request.app?.get?.(TenantContextService);
|
|
1295
1303
|
if (!tenantContext) {
|
|
1296
1304
|
throw new Error("TenantContextService not found.");
|
|
1297
1305
|
}
|
|
@@ -2452,268 +2460,75 @@ var HttpModule = class {
|
|
|
2452
2460
|
HttpModule = _ts_decorate13([
|
|
2453
2461
|
Module4({
|
|
2454
2462
|
providers: [
|
|
2455
|
-
CsrfGuard
|
|
2456
|
-
],
|
|
2457
|
-
exports: [
|
|
2458
|
-
CsrfGuard
|
|
2459
|
-
]
|
|
2460
|
-
})
|
|
2461
|
-
], HttpModule);
|
|
2462
|
-
|
|
2463
|
-
// src/logger/interceptors/http-logger.interceptor.ts
|
|
2464
|
-
import { Injectable as
|
|
2465
|
-
import { catchError, tap as tap2 } from "rxjs/operators";
|
|
2466
|
-
|
|
2467
|
-
// src/logger/utils/index.ts
|
|
2468
|
-
import { AsyncLocalStorage } from "async_hooks";
|
|
2469
|
-
import { randomUUID } from "crypto";
|
|
2470
|
-
var correlationStorage = new AsyncLocalStorage();
|
|
2471
|
-
function getCorrelationContext() {
|
|
2472
|
-
return correlationStorage.getStore();
|
|
2473
|
-
}
|
|
2474
|
-
__name(getCorrelationContext, "getCorrelationContext");
|
|
2475
|
-
function runWithCorrelationContext(context, callback) {
|
|
2476
|
-
return correlationStorage.run(context, callback);
|
|
2477
|
-
}
|
|
2478
|
-
__name(runWithCorrelationContext, "runWithCorrelationContext");
|
|
2479
|
-
function updateCorrelationContext(updates) {
|
|
2480
|
-
const context = correlationStorage.getStore();
|
|
2481
|
-
if (context) {
|
|
2482
|
-
Object.assign(context, updates);
|
|
2483
|
-
}
|
|
2484
|
-
}
|
|
2485
|
-
__name(updateCorrelationContext, "updateCorrelationContext");
|
|
2486
|
-
var DEFAULT_CORRELATION_HEADER = "x-correlation-id";
|
|
2487
|
-
function generateCorrelationId() {
|
|
2488
|
-
return randomUUID();
|
|
2489
|
-
}
|
|
2490
|
-
__name(generateCorrelationId, "generateCorrelationId");
|
|
2491
|
-
function addCorrelationIdToResponse(reply, correlationId, headerName = DEFAULT_CORRELATION_HEADER) {
|
|
2492
|
-
if (typeof reply.header === "function") {
|
|
2493
|
-
reply.header(headerName, correlationId);
|
|
2494
|
-
} else if (reply.raw && typeof reply.raw.setHeader === "function") {
|
|
2495
|
-
reply.raw.setHeader(headerName, correlationId);
|
|
2496
|
-
}
|
|
2497
|
-
}
|
|
2498
|
-
__name(addCorrelationIdToResponse, "addCorrelationIdToResponse");
|
|
2499
|
-
|
|
2500
|
-
// src/logger/interceptors/http-logger.interceptor.ts
|
|
2501
|
-
function _ts_decorate14(decorators, target, key, desc) {
|
|
2502
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
2503
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
2504
|
-
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;
|
|
2505
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
2506
|
-
}
|
|
2507
|
-
__name(_ts_decorate14, "_ts_decorate");
|
|
2508
|
-
function _ts_metadata7(k, v) {
|
|
2509
|
-
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
2510
|
-
}
|
|
2511
|
-
__name(_ts_metadata7, "_ts_metadata");
|
|
2512
|
-
function _ts_param4(paramIndex, decorator) {
|
|
2513
|
-
return function(target, key) {
|
|
2514
|
-
decorator(target, key, paramIndex);
|
|
2515
|
-
};
|
|
2516
|
-
}
|
|
2517
|
-
__name(_ts_param4, "_ts_param");
|
|
2518
|
-
var HttpLoggerInterceptor = class {
|
|
2519
|
-
static {
|
|
2520
|
-
__name(this, "HttpLoggerInterceptor");
|
|
2521
|
-
}
|
|
2522
|
-
logger;
|
|
2523
|
-
enableRequestLog;
|
|
2524
|
-
enableResponseLog;
|
|
2525
|
-
slowRequestThreshold;
|
|
2526
|
-
constructor(logger, options) {
|
|
2527
|
-
this.logger = logger;
|
|
2528
|
-
this.enableRequestLog = options?.enableRequestLog ?? true;
|
|
2529
|
-
this.enableResponseLog = options?.enableResponseLog ?? true;
|
|
2530
|
-
this.slowRequestThreshold = options?.slowRequestThreshold ?? 3e3;
|
|
2531
|
-
}
|
|
2532
|
-
intercept(context, next) {
|
|
2533
|
-
if (context.getType() !== "http") {
|
|
2534
|
-
return next.handle();
|
|
2535
|
-
}
|
|
2536
|
-
const httpContext = context.switchToHttp();
|
|
2537
|
-
const request = httpContext.getRequest();
|
|
2538
|
-
const response = httpContext.getResponse();
|
|
2539
|
-
const startTime = Date.now();
|
|
2540
|
-
if (this.enableRequestLog) {
|
|
2541
|
-
this.logRequest(request);
|
|
2542
|
-
}
|
|
2543
|
-
return next.handle().pipe(tap2(() => {
|
|
2544
|
-
if (this.enableResponseLog) {
|
|
2545
|
-
const duration = Date.now() - startTime;
|
|
2546
|
-
this.logResponse(request, response, duration);
|
|
2547
|
-
}
|
|
2548
|
-
}), catchError((error) => {
|
|
2549
|
-
const duration = Date.now() - startTime;
|
|
2550
|
-
this.logError(request, response, duration, error);
|
|
2551
|
-
throw error;
|
|
2552
|
-
}));
|
|
2553
|
-
}
|
|
2554
|
-
logRequest(request) {
|
|
2555
|
-
try {
|
|
2556
|
-
const correlationContext = getCorrelationContext();
|
|
2557
|
-
const metadata = {
|
|
2558
|
-
type: "http_request",
|
|
2559
|
-
method: request.method,
|
|
2560
|
-
url: request.url,
|
|
2561
|
-
correlationId: correlationContext?.correlationId,
|
|
2562
|
-
ip: request.ip,
|
|
2563
|
-
userAgent: request.headers["user-agent"]
|
|
2564
|
-
};
|
|
2565
|
-
this.logger.logWithMetadata("log", `Incoming ${request.method} ${request.url}`, metadata);
|
|
2566
|
-
} catch (error) {
|
|
2567
|
-
this.logger.error("Failed to log HTTP request", error.stack);
|
|
2568
|
-
}
|
|
2569
|
-
}
|
|
2570
|
-
logResponse(request, response, duration) {
|
|
2571
|
-
try {
|
|
2572
|
-
const correlationContext = getCorrelationContext();
|
|
2573
|
-
const statusCode = response.statusCode;
|
|
2574
|
-
const logLevel = statusCode >= 500 ? "error" : statusCode >= 400 ? "warn" : "log";
|
|
2575
|
-
const metadata = {
|
|
2576
|
-
type: "http_response",
|
|
2577
|
-
method: request.method,
|
|
2578
|
-
url: request.url,
|
|
2579
|
-
statusCode,
|
|
2580
|
-
duration,
|
|
2581
|
-
correlationId: correlationContext?.correlationId
|
|
2582
|
-
};
|
|
2583
|
-
if (duration > this.slowRequestThreshold) {
|
|
2584
|
-
metadata.slowRequest = true;
|
|
2585
|
-
}
|
|
2586
|
-
const message = metadata.slowRequest ? `SLOW ${request.method} ${request.url} ${statusCode} - ${duration}ms` : `${request.method} ${request.url} ${statusCode} - ${duration}ms`;
|
|
2587
|
-
this.logger.logWithMetadata(logLevel, message, metadata);
|
|
2588
|
-
} catch (error) {
|
|
2589
|
-
this.logger.error("Failed to log HTTP response", error.stack);
|
|
2590
|
-
}
|
|
2591
|
-
}
|
|
2592
|
-
logError(request, response, duration, error) {
|
|
2593
|
-
try {
|
|
2594
|
-
const correlationContext = getCorrelationContext();
|
|
2595
|
-
const statusCode = response.statusCode || 500;
|
|
2596
|
-
const metadata = {
|
|
2597
|
-
type: "http_error",
|
|
2598
|
-
method: request.method,
|
|
2599
|
-
url: request.url,
|
|
2600
|
-
statusCode,
|
|
2601
|
-
duration,
|
|
2602
|
-
correlationId: correlationContext?.correlationId,
|
|
2603
|
-
errorName: error?.name || "Error",
|
|
2604
|
-
errorMessage: error?.message || "Unknown error"
|
|
2605
|
-
};
|
|
2606
|
-
if (error?.stack) {
|
|
2607
|
-
metadata.trace = error.stack;
|
|
2608
|
-
}
|
|
2609
|
-
if (error?.response) {
|
|
2610
|
-
metadata.errorDetails = error.response;
|
|
2611
|
-
}
|
|
2612
|
-
const message = `ERROR ${request.method} ${request.url} ${statusCode} - ${error?.message || "Unknown error"}`;
|
|
2613
|
-
this.logger.logWithMetadata("error", message, metadata);
|
|
2614
|
-
} catch (loggingError) {
|
|
2615
|
-
this.logger.error("Failed to log HTTP error", loggingError.stack);
|
|
2616
|
-
}
|
|
2617
|
-
}
|
|
2618
|
-
};
|
|
2619
|
-
HttpLoggerInterceptor = _ts_decorate14([
|
|
2620
|
-
Injectable9(),
|
|
2621
|
-
_ts_param4(1, Optional()),
|
|
2622
|
-
_ts_metadata7("design:type", Function),
|
|
2623
|
-
_ts_metadata7("design:paramtypes", [
|
|
2624
|
-
typeof LoggerService === "undefined" ? Object : LoggerService,
|
|
2625
|
-
typeof HttpLoggerOptions === "undefined" ? Object : HttpLoggerOptions
|
|
2626
|
-
])
|
|
2627
|
-
], HttpLoggerInterceptor);
|
|
2628
|
-
|
|
2629
|
-
// src/logger/logger.module.ts
|
|
2630
|
-
import { Global as Global4, Logger as Logger11, Module as Module5 } from "@nestjs/common";
|
|
2631
|
-
|
|
2632
|
-
// src/logger/middleware/correlation-id.middleware.ts
|
|
2633
|
-
import { Injectable as Injectable10 } from "@nestjs/common";
|
|
2634
|
-
function _ts_decorate15(decorators, target, key, desc) {
|
|
2635
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
2636
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
2637
|
-
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;
|
|
2638
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
2639
|
-
}
|
|
2640
|
-
__name(_ts_decorate15, "_ts_decorate");
|
|
2641
|
-
function _ts_metadata8(k, v) {
|
|
2642
|
-
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
2643
|
-
}
|
|
2644
|
-
__name(_ts_metadata8, "_ts_metadata");
|
|
2645
|
-
var CorrelationIdMiddleware = class {
|
|
2646
|
-
static {
|
|
2647
|
-
__name(this, "CorrelationIdMiddleware");
|
|
2648
|
-
}
|
|
2649
|
-
includeInResponse;
|
|
2650
|
-
responseHeader;
|
|
2651
|
-
constructor(options = {}) {
|
|
2652
|
-
this.includeInResponse = options.includeInResponse ?? true;
|
|
2653
|
-
this.responseHeader = options.responseHeader ?? DEFAULT_CORRELATION_HEADER;
|
|
2654
|
-
}
|
|
2655
|
-
/**
|
|
2656
|
-
* Middleware handler for processing requests.
|
|
2657
|
-
*/
|
|
2658
|
-
use(_req, reply, next) {
|
|
2659
|
-
const correlationId = generateCorrelationId();
|
|
2660
|
-
if (this.includeInResponse) {
|
|
2661
|
-
addCorrelationIdToResponse(reply, correlationId, this.responseHeader);
|
|
2662
|
-
}
|
|
2663
|
-
runWithCorrelationContext({
|
|
2664
|
-
correlationId
|
|
2665
|
-
}, () => {
|
|
2666
|
-
next();
|
|
2667
|
-
});
|
|
2668
|
-
}
|
|
2669
|
-
/**
|
|
2670
|
-
* Fastify hook handler for onRequest.
|
|
2671
|
-
* This is an async function that returns a Promise, ensuring the AsyncLocalStorage
|
|
2672
|
-
* context persists throughout the entire request lifecycle.
|
|
2673
|
-
*/
|
|
2674
|
-
async onRequest(_req, reply) {
|
|
2675
|
-
const correlationId = generateCorrelationId();
|
|
2676
|
-
if (this.includeInResponse) {
|
|
2677
|
-
addCorrelationIdToResponse(reply, correlationId, this.responseHeader);
|
|
2678
|
-
}
|
|
2679
|
-
const store = correlationStorage.getStore();
|
|
2680
|
-
if (!store) {
|
|
2681
|
-
correlationStorage.enterWith({
|
|
2682
|
-
correlationId
|
|
2683
|
-
});
|
|
2684
|
-
}
|
|
2685
|
-
}
|
|
2686
|
-
};
|
|
2687
|
-
CorrelationIdMiddleware = _ts_decorate15([
|
|
2688
|
-
Injectable10(),
|
|
2689
|
-
_ts_metadata8("design:type", Function),
|
|
2690
|
-
_ts_metadata8("design:paramtypes", [
|
|
2691
|
-
typeof CorrelationIdMiddlewareOptions === "undefined" ? Object : CorrelationIdMiddlewareOptions
|
|
2692
|
-
])
|
|
2693
|
-
], CorrelationIdMiddleware);
|
|
2463
|
+
CsrfGuard
|
|
2464
|
+
],
|
|
2465
|
+
exports: [
|
|
2466
|
+
CsrfGuard
|
|
2467
|
+
]
|
|
2468
|
+
})
|
|
2469
|
+
], HttpModule);
|
|
2470
|
+
|
|
2471
|
+
// src/logger/interceptors/http-logger.interceptor.ts
|
|
2472
|
+
import { Injectable as Injectable10, Optional as Optional2 } from "@nestjs/common";
|
|
2473
|
+
import { catchError, tap as tap2 } from "rxjs/operators";
|
|
2694
2474
|
|
|
2695
2475
|
// src/logger/services/logger.service.ts
|
|
2696
|
-
import { Injectable as
|
|
2476
|
+
import { Injectable as Injectable9, Optional } from "@nestjs/common";
|
|
2697
2477
|
import { createLogger, format, transports } from "winston";
|
|
2698
2478
|
import DailyRotateFile from "winston-daily-rotate-file";
|
|
2699
|
-
|
|
2479
|
+
|
|
2480
|
+
// src/logger/utils/index.ts
|
|
2481
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
2482
|
+
import { randomUUID } from "crypto";
|
|
2483
|
+
var correlationStorage = new AsyncLocalStorage();
|
|
2484
|
+
function getCorrelationContext() {
|
|
2485
|
+
return correlationStorage.getStore();
|
|
2486
|
+
}
|
|
2487
|
+
__name(getCorrelationContext, "getCorrelationContext");
|
|
2488
|
+
function runWithCorrelationContext(context, callback) {
|
|
2489
|
+
return correlationStorage.run(context, callback);
|
|
2490
|
+
}
|
|
2491
|
+
__name(runWithCorrelationContext, "runWithCorrelationContext");
|
|
2492
|
+
function updateCorrelationContext(updates) {
|
|
2493
|
+
const context = correlationStorage.getStore();
|
|
2494
|
+
if (context) {
|
|
2495
|
+
Object.assign(context, updates);
|
|
2496
|
+
}
|
|
2497
|
+
}
|
|
2498
|
+
__name(updateCorrelationContext, "updateCorrelationContext");
|
|
2499
|
+
var DEFAULT_CORRELATION_HEADER = "x-correlation-id";
|
|
2500
|
+
function generateCorrelationId() {
|
|
2501
|
+
return randomUUID();
|
|
2502
|
+
}
|
|
2503
|
+
__name(generateCorrelationId, "generateCorrelationId");
|
|
2504
|
+
function addCorrelationIdToResponse(reply, correlationId, headerName = DEFAULT_CORRELATION_HEADER) {
|
|
2505
|
+
if (typeof reply.header === "function") {
|
|
2506
|
+
reply.header(headerName, correlationId);
|
|
2507
|
+
} else if (reply.raw && typeof reply.raw.setHeader === "function") {
|
|
2508
|
+
reply.raw.setHeader(headerName, correlationId);
|
|
2509
|
+
}
|
|
2510
|
+
}
|
|
2511
|
+
__name(addCorrelationIdToResponse, "addCorrelationIdToResponse");
|
|
2512
|
+
|
|
2513
|
+
// src/logger/services/logger.service.ts
|
|
2514
|
+
function _ts_decorate14(decorators, target, key, desc) {
|
|
2700
2515
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
2701
2516
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
2702
2517
|
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;
|
|
2703
2518
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
2704
2519
|
}
|
|
2705
|
-
__name(
|
|
2706
|
-
function
|
|
2520
|
+
__name(_ts_decorate14, "_ts_decorate");
|
|
2521
|
+
function _ts_metadata7(k, v) {
|
|
2707
2522
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
2708
2523
|
}
|
|
2709
|
-
__name(
|
|
2710
|
-
function
|
|
2524
|
+
__name(_ts_metadata7, "_ts_metadata");
|
|
2525
|
+
function _ts_param4(paramIndex, decorator) {
|
|
2711
2526
|
return function(target, key) {
|
|
2712
2527
|
decorator(target, key, paramIndex);
|
|
2713
2528
|
};
|
|
2714
2529
|
}
|
|
2715
|
-
__name(
|
|
2716
|
-
var
|
|
2530
|
+
__name(_ts_param4, "_ts_param");
|
|
2531
|
+
var LoggerService = class _LoggerService {
|
|
2717
2532
|
static {
|
|
2718
2533
|
__name(this, "LoggerService");
|
|
2719
2534
|
}
|
|
@@ -2914,16 +2729,211 @@ ${trace}`;
|
|
|
2914
2729
|
return childLogger;
|
|
2915
2730
|
}
|
|
2916
2731
|
};
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2732
|
+
LoggerService = _ts_decorate14([
|
|
2733
|
+
Injectable9(),
|
|
2734
|
+
_ts_param4(0, Optional()),
|
|
2735
|
+
_ts_param4(1, Optional()),
|
|
2736
|
+
_ts_metadata7("design:type", Function),
|
|
2737
|
+
_ts_metadata7("design:paramtypes", [
|
|
2738
|
+
typeof LoggerModuleOptions === "undefined" ? Object : LoggerModuleOptions,
|
|
2739
|
+
typeof Logger === "undefined" ? Object : Logger
|
|
2740
|
+
])
|
|
2741
|
+
], LoggerService);
|
|
2742
|
+
|
|
2743
|
+
// src/logger/interceptors/http-logger.interceptor.ts
|
|
2744
|
+
function _ts_decorate15(decorators, target, key, desc) {
|
|
2745
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
2746
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
2747
|
+
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;
|
|
2748
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
2749
|
+
}
|
|
2750
|
+
__name(_ts_decorate15, "_ts_decorate");
|
|
2751
|
+
function _ts_metadata8(k, v) {
|
|
2752
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
2753
|
+
}
|
|
2754
|
+
__name(_ts_metadata8, "_ts_metadata");
|
|
2755
|
+
function _ts_param5(paramIndex, decorator) {
|
|
2756
|
+
return function(target, key) {
|
|
2757
|
+
decorator(target, key, paramIndex);
|
|
2758
|
+
};
|
|
2759
|
+
}
|
|
2760
|
+
__name(_ts_param5, "_ts_param");
|
|
2761
|
+
var HttpLoggerInterceptor = class {
|
|
2762
|
+
static {
|
|
2763
|
+
__name(this, "HttpLoggerInterceptor");
|
|
2764
|
+
}
|
|
2765
|
+
logger;
|
|
2766
|
+
enableRequestLog;
|
|
2767
|
+
enableResponseLog;
|
|
2768
|
+
slowRequestThreshold;
|
|
2769
|
+
constructor(logger, options) {
|
|
2770
|
+
this.logger = logger;
|
|
2771
|
+
this.enableRequestLog = options?.enableRequestLog ?? true;
|
|
2772
|
+
this.enableResponseLog = options?.enableResponseLog ?? true;
|
|
2773
|
+
this.slowRequestThreshold = options?.slowRequestThreshold ?? 3e3;
|
|
2774
|
+
}
|
|
2775
|
+
intercept(context, next) {
|
|
2776
|
+
if (context.getType() !== "http") {
|
|
2777
|
+
return next.handle();
|
|
2778
|
+
}
|
|
2779
|
+
const httpContext = context.switchToHttp();
|
|
2780
|
+
const request = httpContext.getRequest();
|
|
2781
|
+
const response = httpContext.getResponse();
|
|
2782
|
+
const startTime = Date.now();
|
|
2783
|
+
if (this.enableRequestLog) {
|
|
2784
|
+
this.logRequest(request);
|
|
2785
|
+
}
|
|
2786
|
+
return next.handle().pipe(tap2(() => {
|
|
2787
|
+
if (this.enableResponseLog) {
|
|
2788
|
+
const duration = Date.now() - startTime;
|
|
2789
|
+
this.logResponse(request, response, duration);
|
|
2790
|
+
}
|
|
2791
|
+
}), catchError((error) => {
|
|
2792
|
+
const duration = Date.now() - startTime;
|
|
2793
|
+
this.logError(request, response, duration, error);
|
|
2794
|
+
throw error;
|
|
2795
|
+
}));
|
|
2796
|
+
}
|
|
2797
|
+
logRequest(request) {
|
|
2798
|
+
try {
|
|
2799
|
+
const correlationContext = getCorrelationContext();
|
|
2800
|
+
const metadata = {
|
|
2801
|
+
type: "http_request",
|
|
2802
|
+
method: request.method,
|
|
2803
|
+
url: request.url,
|
|
2804
|
+
correlationId: correlationContext?.correlationId,
|
|
2805
|
+
ip: request.ip,
|
|
2806
|
+
userAgent: request.headers["user-agent"]
|
|
2807
|
+
};
|
|
2808
|
+
this.logger.logWithMetadata("log", `Incoming ${request.method} ${request.url}`, metadata);
|
|
2809
|
+
} catch (error) {
|
|
2810
|
+
this.logger.error("Failed to log HTTP request", error.stack);
|
|
2811
|
+
}
|
|
2812
|
+
}
|
|
2813
|
+
logResponse(request, response, duration) {
|
|
2814
|
+
try {
|
|
2815
|
+
const correlationContext = getCorrelationContext();
|
|
2816
|
+
const statusCode = response.statusCode;
|
|
2817
|
+
const logLevel = statusCode >= 500 ? "error" : statusCode >= 400 ? "warn" : "log";
|
|
2818
|
+
const metadata = {
|
|
2819
|
+
type: "http_response",
|
|
2820
|
+
method: request.method,
|
|
2821
|
+
url: request.url,
|
|
2822
|
+
statusCode,
|
|
2823
|
+
duration,
|
|
2824
|
+
correlationId: correlationContext?.correlationId
|
|
2825
|
+
};
|
|
2826
|
+
if (duration > this.slowRequestThreshold) {
|
|
2827
|
+
metadata.slowRequest = true;
|
|
2828
|
+
}
|
|
2829
|
+
const message = metadata.slowRequest ? `SLOW ${request.method} ${request.url} ${statusCode} - ${duration}ms` : `${request.method} ${request.url} ${statusCode} - ${duration}ms`;
|
|
2830
|
+
this.logger.logWithMetadata(logLevel, message, metadata);
|
|
2831
|
+
} catch (error) {
|
|
2832
|
+
this.logger.error("Failed to log HTTP response", error.stack);
|
|
2833
|
+
}
|
|
2834
|
+
}
|
|
2835
|
+
logError(request, response, duration, error) {
|
|
2836
|
+
try {
|
|
2837
|
+
const correlationContext = getCorrelationContext();
|
|
2838
|
+
const statusCode = response.statusCode || 500;
|
|
2839
|
+
const metadata = {
|
|
2840
|
+
type: "http_error",
|
|
2841
|
+
method: request.method,
|
|
2842
|
+
url: request.url,
|
|
2843
|
+
statusCode,
|
|
2844
|
+
duration,
|
|
2845
|
+
correlationId: correlationContext?.correlationId,
|
|
2846
|
+
errorName: error?.name || "Error",
|
|
2847
|
+
errorMessage: error?.message || "Unknown error"
|
|
2848
|
+
};
|
|
2849
|
+
if (error?.stack) {
|
|
2850
|
+
metadata.trace = error.stack;
|
|
2851
|
+
}
|
|
2852
|
+
if (error?.response) {
|
|
2853
|
+
metadata.errorDetails = error.response;
|
|
2854
|
+
}
|
|
2855
|
+
const message = `ERROR ${request.method} ${request.url} ${statusCode} - ${error?.message || "Unknown error"}`;
|
|
2856
|
+
this.logger.logWithMetadata("error", message, metadata);
|
|
2857
|
+
} catch (loggingError) {
|
|
2858
|
+
this.logger.error("Failed to log HTTP error", loggingError.stack);
|
|
2859
|
+
}
|
|
2860
|
+
}
|
|
2861
|
+
};
|
|
2862
|
+
HttpLoggerInterceptor = _ts_decorate15([
|
|
2863
|
+
Injectable10(),
|
|
2920
2864
|
_ts_param5(1, Optional2()),
|
|
2865
|
+
_ts_metadata8("design:type", Function),
|
|
2866
|
+
_ts_metadata8("design:paramtypes", [
|
|
2867
|
+
typeof LoggerService === "undefined" ? Object : LoggerService,
|
|
2868
|
+
typeof HttpLoggerOptions === "undefined" ? Object : HttpLoggerOptions
|
|
2869
|
+
])
|
|
2870
|
+
], HttpLoggerInterceptor);
|
|
2871
|
+
|
|
2872
|
+
// src/logger/logger.module.ts
|
|
2873
|
+
import { Global as Global4, Logger as Logger11, Module as Module5 } from "@nestjs/common";
|
|
2874
|
+
|
|
2875
|
+
// src/logger/middleware/correlation-id.middleware.ts
|
|
2876
|
+
import { Injectable as Injectable11 } from "@nestjs/common";
|
|
2877
|
+
function _ts_decorate16(decorators, target, key, desc) {
|
|
2878
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
2879
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
2880
|
+
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;
|
|
2881
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
2882
|
+
}
|
|
2883
|
+
__name(_ts_decorate16, "_ts_decorate");
|
|
2884
|
+
function _ts_metadata9(k, v) {
|
|
2885
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
2886
|
+
}
|
|
2887
|
+
__name(_ts_metadata9, "_ts_metadata");
|
|
2888
|
+
var CorrelationIdMiddleware = class {
|
|
2889
|
+
static {
|
|
2890
|
+
__name(this, "CorrelationIdMiddleware");
|
|
2891
|
+
}
|
|
2892
|
+
includeInResponse;
|
|
2893
|
+
responseHeader;
|
|
2894
|
+
constructor(options = {}) {
|
|
2895
|
+
this.includeInResponse = options.includeInResponse ?? true;
|
|
2896
|
+
this.responseHeader = options.responseHeader ?? DEFAULT_CORRELATION_HEADER;
|
|
2897
|
+
}
|
|
2898
|
+
/**
|
|
2899
|
+
* Middleware handler for processing requests.
|
|
2900
|
+
*/
|
|
2901
|
+
use(_req, reply, next) {
|
|
2902
|
+
const correlationId = generateCorrelationId();
|
|
2903
|
+
if (this.includeInResponse) {
|
|
2904
|
+
addCorrelationIdToResponse(reply, correlationId, this.responseHeader);
|
|
2905
|
+
}
|
|
2906
|
+
runWithCorrelationContext({
|
|
2907
|
+
correlationId
|
|
2908
|
+
}, () => {
|
|
2909
|
+
next();
|
|
2910
|
+
});
|
|
2911
|
+
}
|
|
2912
|
+
/**
|
|
2913
|
+
* Fastify hook handler for onRequest.
|
|
2914
|
+
* This is an async function that returns a Promise, ensuring the AsyncLocalStorage
|
|
2915
|
+
* context persists throughout the entire request lifecycle.
|
|
2916
|
+
*/
|
|
2917
|
+
async onRequest(_req, reply) {
|
|
2918
|
+
const correlationId = generateCorrelationId();
|
|
2919
|
+
if (this.includeInResponse) {
|
|
2920
|
+
addCorrelationIdToResponse(reply, correlationId, this.responseHeader);
|
|
2921
|
+
}
|
|
2922
|
+
const store = correlationStorage.getStore();
|
|
2923
|
+
if (!store) {
|
|
2924
|
+
correlationStorage.enterWith({
|
|
2925
|
+
correlationId
|
|
2926
|
+
});
|
|
2927
|
+
}
|
|
2928
|
+
}
|
|
2929
|
+
};
|
|
2930
|
+
CorrelationIdMiddleware = _ts_decorate16([
|
|
2931
|
+
Injectable11(),
|
|
2921
2932
|
_ts_metadata9("design:type", Function),
|
|
2922
2933
|
_ts_metadata9("design:paramtypes", [
|
|
2923
|
-
typeof
|
|
2924
|
-
typeof Logger === "undefined" ? Object : Logger
|
|
2934
|
+
typeof CorrelationIdMiddlewareOptions === "undefined" ? Object : CorrelationIdMiddlewareOptions
|
|
2925
2935
|
])
|
|
2926
|
-
],
|
|
2936
|
+
], CorrelationIdMiddleware);
|
|
2927
2937
|
|
|
2928
2938
|
// src/logger/logger.module.ts
|
|
2929
2939
|
function _ts_decorate17(decorators, target, key, desc) {
|
|
@@ -3046,9 +3056,9 @@ function createLoggerProviders(options = {}) {
|
|
|
3046
3056
|
providers.push(createDefaultLoggerProvider(mergedOptions));
|
|
3047
3057
|
}
|
|
3048
3058
|
providers.push({
|
|
3049
|
-
provide:
|
|
3059
|
+
provide: LoggerService,
|
|
3050
3060
|
useFactory: /* @__PURE__ */ __name((opts, defaultLogger) => {
|
|
3051
|
-
return new
|
|
3061
|
+
return new LoggerService(opts, defaultLogger);
|
|
3052
3062
|
}, "useFactory"),
|
|
3053
3063
|
inject: [
|
|
3054
3064
|
LOGGER_MODULE_OPTIONS,
|
|
@@ -3077,7 +3087,7 @@ function createLoggerProviders(options = {}) {
|
|
|
3077
3087
|
return new HttpLoggerInterceptor(logger, httpLoggerOptions);
|
|
3078
3088
|
}, "useFactory"),
|
|
3079
3089
|
inject: [
|
|
3080
|
-
|
|
3090
|
+
LoggerService,
|
|
3081
3091
|
LOGGER_MODULE_OPTIONS
|
|
3082
3092
|
]
|
|
3083
3093
|
});
|
|
@@ -3145,7 +3155,7 @@ var LoggerModule = class _LoggerModule {
|
|
|
3145
3155
|
module: _LoggerModule,
|
|
3146
3156
|
providers,
|
|
3147
3157
|
exports: [
|
|
3148
|
-
|
|
3158
|
+
LoggerService,
|
|
3149
3159
|
CorrelationIdMiddleware,
|
|
3150
3160
|
HttpLoggerInterceptor,
|
|
3151
3161
|
LOGGER_MODULE_OPTIONS
|
|
@@ -3222,9 +3232,9 @@ var LoggerModule = class _LoggerModule {
|
|
|
3222
3232
|
},
|
|
3223
3233
|
// Unified logger service
|
|
3224
3234
|
{
|
|
3225
|
-
provide:
|
|
3235
|
+
provide: LoggerService,
|
|
3226
3236
|
useFactory: /* @__PURE__ */ __name((opts, defaultLogger) => {
|
|
3227
|
-
return new
|
|
3237
|
+
return new LoggerService(opts, defaultLogger);
|
|
3228
3238
|
}, "useFactory"),
|
|
3229
3239
|
inject: [
|
|
3230
3240
|
LOGGER_MODULE_OPTIONS,
|
|
@@ -3255,13 +3265,13 @@ var LoggerModule = class _LoggerModule {
|
|
|
3255
3265
|
return new HttpLoggerInterceptor(logger, httpLoggerOptions);
|
|
3256
3266
|
}, "useFactory"),
|
|
3257
3267
|
inject: [
|
|
3258
|
-
|
|
3268
|
+
LoggerService,
|
|
3259
3269
|
LOGGER_MODULE_OPTIONS
|
|
3260
3270
|
]
|
|
3261
3271
|
}
|
|
3262
3272
|
],
|
|
3263
3273
|
exports: [
|
|
3264
|
-
|
|
3274
|
+
LoggerService,
|
|
3265
3275
|
CorrelationIdMiddleware,
|
|
3266
3276
|
HttpLoggerInterceptor,
|
|
3267
3277
|
LOGGER_MODULE_OPTIONS
|
|
@@ -3357,7 +3367,7 @@ export {
|
|
|
3357
3367
|
InternalServerErrorException3 as InternalServerErrorException,
|
|
3358
3368
|
LOGGER_MODULE_OPTIONS,
|
|
3359
3369
|
LoggerModule,
|
|
3360
|
-
|
|
3370
|
+
LoggerService,
|
|
3361
3371
|
MethodNotAllowedException,
|
|
3362
3372
|
NotAcceptableException,
|
|
3363
3373
|
NotFoundException,
|
|
@@ -3365,13 +3375,13 @@ export {
|
|
|
3365
3375
|
Onboarding,
|
|
3366
3376
|
PayloadTooLargeException,
|
|
3367
3377
|
PrimaryBaseRepository,
|
|
3368
|
-
|
|
3378
|
+
PrimaryDatabaseService,
|
|
3369
3379
|
Public,
|
|
3370
3380
|
RequestTimeoutException,
|
|
3371
3381
|
ServiceUnavailableException,
|
|
3372
3382
|
Tenant,
|
|
3373
3383
|
TenantBaseRepository,
|
|
3374
|
-
|
|
3384
|
+
TenantContextService,
|
|
3375
3385
|
TenantDatabaseService,
|
|
3376
3386
|
TooManyRequestsException,
|
|
3377
3387
|
UnauthorizedException4 as UnauthorizedException,
|