@vritti/api-sdk 0.1.3 → 0.1.5
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 +821 -798
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +25 -1
- package/dist/index.d.ts +25 -1
- package/dist/index.js +672 -650
- 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);
|
|
@@ -486,33 +726,129 @@ var Onboarding = /* @__PURE__ */ __name(() => SetMetadata("isOnboarding", true),
|
|
|
486
726
|
import { SetMetadata as SetMetadata2 } from "@nestjs/common";
|
|
487
727
|
var Public = /* @__PURE__ */ __name(() => SetMetadata2("isPublic", true), "Public");
|
|
488
728
|
|
|
729
|
+
// src/auth/decorators/user-id.decorator.ts
|
|
730
|
+
import { createParamDecorator } from "@nestjs/common";
|
|
731
|
+
var UserId = createParamDecorator((_data, ctx) => {
|
|
732
|
+
const request = ctx.switchToHttp().getRequest();
|
|
733
|
+
const user = request.user;
|
|
734
|
+
if (!user?.id) {
|
|
735
|
+
throw new Error("User ID not found on request. Ensure route is protected by auth guard.");
|
|
736
|
+
}
|
|
737
|
+
return user.id;
|
|
738
|
+
});
|
|
739
|
+
|
|
489
740
|
// src/database/database.module.ts
|
|
490
741
|
import { Global as Global3, Module as Module3 } from "@nestjs/common";
|
|
491
742
|
import { APP_INTERCEPTOR } from "@nestjs/core";
|
|
492
743
|
|
|
493
|
-
// src/database/constants.ts
|
|
494
|
-
var DATABASE_MODULE_OPTIONS = Symbol("DATABASE_MODULE_OPTIONS");
|
|
495
|
-
|
|
496
744
|
// src/database/interceptors/message-tenant-context.interceptor.ts
|
|
497
|
-
import { Injectable as
|
|
745
|
+
import { Injectable as Injectable5, Logger as Logger4, Scope as Scope4 } from "@nestjs/common";
|
|
498
746
|
import { tap } from "rxjs/operators";
|
|
499
|
-
|
|
747
|
+
|
|
748
|
+
// src/database/services/tenant-context.service.ts
|
|
749
|
+
import { Injectable as Injectable4, Scope as Scope3, UnauthorizedException as UnauthorizedException2 } from "@nestjs/common";
|
|
750
|
+
function _ts_decorate6(decorators, target, key, desc) {
|
|
500
751
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
501
752
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
502
753
|
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
754
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
504
755
|
}
|
|
505
|
-
__name(
|
|
506
|
-
|
|
756
|
+
__name(_ts_decorate6, "_ts_decorate");
|
|
757
|
+
var TenantContextService = class {
|
|
758
|
+
static {
|
|
759
|
+
__name(this, "TenantContextService");
|
|
760
|
+
}
|
|
761
|
+
tenantInfo = null;
|
|
762
|
+
/**
|
|
763
|
+
* Set tenant information for this request/message
|
|
764
|
+
*
|
|
765
|
+
* This is typically called by:
|
|
766
|
+
* - TenantContextInterceptor (for HTTP requests in gateway)
|
|
767
|
+
* - MessageTenantContextInterceptor (for RabbitMQ messages in microservices)
|
|
768
|
+
* - Manual context setup in message handlers
|
|
769
|
+
*
|
|
770
|
+
* @param tenantInfo Complete tenant information
|
|
771
|
+
* @throws Error if tenant context is already set (prevents accidental overwrites)
|
|
772
|
+
*/
|
|
773
|
+
setTenant(tenantInfo) {
|
|
774
|
+
if (this.tenantInfo) {
|
|
775
|
+
throw new Error("Tenant context already set for this request");
|
|
776
|
+
}
|
|
777
|
+
this.tenantInfo = tenantInfo;
|
|
778
|
+
}
|
|
779
|
+
/**
|
|
780
|
+
* Get tenant information for this request/message
|
|
781
|
+
*
|
|
782
|
+
* @returns Tenant information
|
|
783
|
+
* @throws UnauthorizedException if tenant context hasn't been set
|
|
784
|
+
*/
|
|
785
|
+
getTenant() {
|
|
786
|
+
if (!this.tenantInfo) {
|
|
787
|
+
throw new UnauthorizedException2("Tenant context not set");
|
|
788
|
+
}
|
|
789
|
+
return this.tenantInfo;
|
|
790
|
+
}
|
|
791
|
+
/**
|
|
792
|
+
* Check if tenant context has been set
|
|
793
|
+
*
|
|
794
|
+
* @returns true if tenant context is available
|
|
795
|
+
*/
|
|
796
|
+
hasTenant() {
|
|
797
|
+
return this.tenantInfo !== null;
|
|
798
|
+
}
|
|
799
|
+
/**
|
|
800
|
+
* Clear tenant context
|
|
801
|
+
*
|
|
802
|
+
* This is useful for cleanup in RabbitMQ message handlers
|
|
803
|
+
* after the message has been processed.
|
|
804
|
+
*
|
|
805
|
+
* HTTP requests don't need manual cleanup as the service
|
|
806
|
+
* instance is destroyed when the request ends.
|
|
807
|
+
*/
|
|
808
|
+
clearTenant() {
|
|
809
|
+
this.tenantInfo = null;
|
|
810
|
+
}
|
|
811
|
+
/**
|
|
812
|
+
* Get tenant ID safely (returns null if not set)
|
|
813
|
+
*
|
|
814
|
+
* @returns Tenant ID or null
|
|
815
|
+
*/
|
|
816
|
+
getTenantIdSafe() {
|
|
817
|
+
return this.tenantInfo?.id ?? null;
|
|
818
|
+
}
|
|
819
|
+
/**
|
|
820
|
+
* Get tenant subdomain safely (returns null if not set)
|
|
821
|
+
*
|
|
822
|
+
* @returns Tenant subdomain or null
|
|
823
|
+
*/
|
|
824
|
+
getTenantSubdomainSafe() {
|
|
825
|
+
return this.tenantInfo?.subdomain ?? null;
|
|
826
|
+
}
|
|
827
|
+
};
|
|
828
|
+
TenantContextService = _ts_decorate6([
|
|
829
|
+
Injectable4({
|
|
830
|
+
scope: Scope3.REQUEST
|
|
831
|
+
})
|
|
832
|
+
], TenantContextService);
|
|
833
|
+
|
|
834
|
+
// src/database/interceptors/message-tenant-context.interceptor.ts
|
|
835
|
+
function _ts_decorate7(decorators, target, key, desc) {
|
|
836
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
837
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
838
|
+
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;
|
|
839
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
840
|
+
}
|
|
841
|
+
__name(_ts_decorate7, "_ts_decorate");
|
|
842
|
+
function _ts_metadata4(k, v) {
|
|
507
843
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
508
844
|
}
|
|
509
|
-
__name(
|
|
845
|
+
__name(_ts_metadata4, "_ts_metadata");
|
|
510
846
|
var MessageTenantContextInterceptor = class _MessageTenantContextInterceptor {
|
|
511
847
|
static {
|
|
512
848
|
__name(this, "MessageTenantContextInterceptor");
|
|
513
849
|
}
|
|
514
850
|
tenantContext;
|
|
515
|
-
logger = new
|
|
851
|
+
logger = new Logger4(_MessageTenantContextInterceptor.name);
|
|
516
852
|
constructor(tenantContext) {
|
|
517
853
|
this.tenantContext = tenantContext;
|
|
518
854
|
}
|
|
@@ -557,29 +893,30 @@ var MessageTenantContextInterceptor = class _MessageTenantContextInterceptor {
|
|
|
557
893
|
}
|
|
558
894
|
}
|
|
559
895
|
};
|
|
560
|
-
MessageTenantContextInterceptor =
|
|
561
|
-
|
|
562
|
-
scope:
|
|
896
|
+
MessageTenantContextInterceptor = _ts_decorate7([
|
|
897
|
+
Injectable5({
|
|
898
|
+
scope: Scope4.REQUEST
|
|
563
899
|
}),
|
|
564
|
-
|
|
565
|
-
|
|
900
|
+
_ts_metadata4("design:type", Function),
|
|
901
|
+
_ts_metadata4("design:paramtypes", [
|
|
566
902
|
typeof TenantContextService === "undefined" ? Object : TenantContextService
|
|
567
903
|
])
|
|
568
904
|
], MessageTenantContextInterceptor);
|
|
569
905
|
|
|
570
906
|
// src/database/interceptors/tenant-context.interceptor.ts
|
|
571
|
-
import { Injectable as
|
|
572
|
-
|
|
907
|
+
import { Injectable as Injectable6, Logger as Logger5, Scope as Scope5, UnauthorizedException as UnauthorizedException3 } from "@nestjs/common";
|
|
908
|
+
import { Reflector as Reflector2 } from "@nestjs/core";
|
|
909
|
+
function _ts_decorate8(decorators, target, key, desc) {
|
|
573
910
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
574
911
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
575
912
|
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
913
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
577
914
|
}
|
|
578
|
-
__name(
|
|
579
|
-
function
|
|
915
|
+
__name(_ts_decorate8, "_ts_decorate");
|
|
916
|
+
function _ts_metadata5(k, v) {
|
|
580
917
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
581
918
|
}
|
|
582
|
-
__name(
|
|
919
|
+
__name(_ts_metadata5, "_ts_metadata");
|
|
583
920
|
var TenantContextInterceptor = class _TenantContextInterceptor {
|
|
584
921
|
static {
|
|
585
922
|
__name(this, "TenantContextInterceptor");
|
|
@@ -588,7 +925,7 @@ var TenantContextInterceptor = class _TenantContextInterceptor {
|
|
|
588
925
|
tenantContext;
|
|
589
926
|
primaryDatabase;
|
|
590
927
|
requestService;
|
|
591
|
-
logger = new
|
|
928
|
+
logger = new Logger5(_TenantContextInterceptor.name);
|
|
592
929
|
constructor(reflector, tenantContext, primaryDatabase, requestService) {
|
|
593
930
|
this.reflector = reflector;
|
|
594
931
|
this.tenantContext = tenantContext;
|
|
@@ -609,7 +946,7 @@ var TenantContextInterceptor = class _TenantContextInterceptor {
|
|
|
609
946
|
return next.handle();
|
|
610
947
|
}
|
|
611
948
|
if (!tenantIdentifier) {
|
|
612
|
-
throw new
|
|
949
|
+
throw new UnauthorizedException3("Tenant identifier not found in request");
|
|
613
950
|
}
|
|
614
951
|
this.logger.debug(`Tenant identifier extracted: ${tenantIdentifier}`);
|
|
615
952
|
if (tenantIdentifier === "cloud") {
|
|
@@ -619,353 +956,35 @@ var TenantContextInterceptor = class _TenantContextInterceptor {
|
|
|
619
956
|
const tenantInfo = await this.primaryDatabase.getTenantInfo(tenantIdentifier);
|
|
620
957
|
if (!tenantInfo) {
|
|
621
958
|
this.logger.warn(`Invalid tenant: ${tenantIdentifier}`);
|
|
622
|
-
throw new
|
|
959
|
+
throw new UnauthorizedException3("Invalid tenant");
|
|
623
960
|
}
|
|
624
961
|
if (tenantInfo.status !== "ACTIVE") {
|
|
625
962
|
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;
|
|
963
|
+
throw new UnauthorizedException3(`Tenant is ${tenantInfo.status}`);
|
|
964
|
+
}
|
|
965
|
+
this.logger.debug(`Tenant config loaded: ${tenantInfo.subdomain} (${tenantInfo.type})`);
|
|
966
|
+
this.tenantContext.setTenant(tenantInfo);
|
|
967
|
+
request.tenant = tenantInfo;
|
|
968
|
+
this.logger.log(`Tenant context set: ${tenantInfo.subdomain}`);
|
|
969
|
+
} catch (error) {
|
|
970
|
+
this.logger.error("Failed to set tenant context", error);
|
|
971
|
+
throw error;
|
|
972
|
+
}
|
|
973
|
+
return next.handle();
|
|
962
974
|
}
|
|
963
975
|
};
|
|
964
|
-
|
|
976
|
+
TenantContextInterceptor = _ts_decorate8([
|
|
965
977
|
Injectable6({
|
|
966
978
|
scope: Scope5.REQUEST
|
|
967
|
-
})
|
|
968
|
-
|
|
979
|
+
}),
|
|
980
|
+
_ts_metadata5("design:type", Function),
|
|
981
|
+
_ts_metadata5("design:paramtypes", [
|
|
982
|
+
typeof Reflector2 === "undefined" ? Object : Reflector2,
|
|
983
|
+
typeof TenantContextService === "undefined" ? Object : TenantContextService,
|
|
984
|
+
typeof PrimaryDatabaseService === "undefined" ? Object : PrimaryDatabaseService,
|
|
985
|
+
typeof RequestService === "undefined" ? Object : RequestService
|
|
986
|
+
])
|
|
987
|
+
], TenantContextInterceptor);
|
|
969
988
|
|
|
970
989
|
// src/database/services/tenant-database.service.ts
|
|
971
990
|
import { Inject as Inject3, Injectable as Injectable7, InternalServerErrorException as InternalServerErrorException2, Logger as Logger6 } from "@nestjs/common";
|
|
@@ -1252,8 +1271,8 @@ var DatabaseModule = class _DatabaseModule {
|
|
|
1252
1271
|
};
|
|
1253
1272
|
const providers = [
|
|
1254
1273
|
asyncProvider,
|
|
1255
|
-
|
|
1256
|
-
|
|
1274
|
+
TenantContextService,
|
|
1275
|
+
PrimaryDatabaseService,
|
|
1257
1276
|
TenantDatabaseService
|
|
1258
1277
|
];
|
|
1259
1278
|
if (mode === "server") {
|
|
@@ -1275,8 +1294,8 @@ var DatabaseModule = class _DatabaseModule {
|
|
|
1275
1294
|
providers,
|
|
1276
1295
|
exports: [
|
|
1277
1296
|
TenantDatabaseService,
|
|
1278
|
-
|
|
1279
|
-
|
|
1297
|
+
TenantContextService,
|
|
1298
|
+
PrimaryDatabaseService,
|
|
1280
1299
|
asyncProvider
|
|
1281
1300
|
]
|
|
1282
1301
|
};
|
|
@@ -1288,10 +1307,10 @@ DatabaseModule = _ts_decorate10([
|
|
|
1288
1307
|
], DatabaseModule);
|
|
1289
1308
|
|
|
1290
1309
|
// src/database/decorators/tenant.decorator.ts
|
|
1291
|
-
import { createParamDecorator } from "@nestjs/common";
|
|
1292
|
-
var Tenant =
|
|
1310
|
+
import { createParamDecorator as createParamDecorator2 } from "@nestjs/common";
|
|
1311
|
+
var Tenant = createParamDecorator2((_data, ctx) => {
|
|
1293
1312
|
const request = ctx.switchToHttp().getRequest();
|
|
1294
|
-
const tenantContext = request.app?.get?.(
|
|
1313
|
+
const tenantContext = request.app?.get?.(TenantContextService);
|
|
1295
1314
|
if (!tenantContext) {
|
|
1296
1315
|
throw new Error("TenantContextService not found.");
|
|
1297
1316
|
}
|
|
@@ -2452,268 +2471,75 @@ var HttpModule = class {
|
|
|
2452
2471
|
HttpModule = _ts_decorate13([
|
|
2453
2472
|
Module4({
|
|
2454
2473
|
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);
|
|
2474
|
+
CsrfGuard
|
|
2475
|
+
],
|
|
2476
|
+
exports: [
|
|
2477
|
+
CsrfGuard
|
|
2478
|
+
]
|
|
2479
|
+
})
|
|
2480
|
+
], HttpModule);
|
|
2481
|
+
|
|
2482
|
+
// src/logger/interceptors/http-logger.interceptor.ts
|
|
2483
|
+
import { Injectable as Injectable10, Optional as Optional2 } from "@nestjs/common";
|
|
2484
|
+
import { catchError, tap as tap2 } from "rxjs/operators";
|
|
2694
2485
|
|
|
2695
2486
|
// src/logger/services/logger.service.ts
|
|
2696
|
-
import { Injectable as
|
|
2487
|
+
import { Injectable as Injectable9, Optional } from "@nestjs/common";
|
|
2697
2488
|
import { createLogger, format, transports } from "winston";
|
|
2698
2489
|
import DailyRotateFile from "winston-daily-rotate-file";
|
|
2699
|
-
|
|
2490
|
+
|
|
2491
|
+
// src/logger/utils/index.ts
|
|
2492
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
2493
|
+
import { randomUUID } from "crypto";
|
|
2494
|
+
var correlationStorage = new AsyncLocalStorage();
|
|
2495
|
+
function getCorrelationContext() {
|
|
2496
|
+
return correlationStorage.getStore();
|
|
2497
|
+
}
|
|
2498
|
+
__name(getCorrelationContext, "getCorrelationContext");
|
|
2499
|
+
function runWithCorrelationContext(context, callback) {
|
|
2500
|
+
return correlationStorage.run(context, callback);
|
|
2501
|
+
}
|
|
2502
|
+
__name(runWithCorrelationContext, "runWithCorrelationContext");
|
|
2503
|
+
function updateCorrelationContext(updates) {
|
|
2504
|
+
const context = correlationStorage.getStore();
|
|
2505
|
+
if (context) {
|
|
2506
|
+
Object.assign(context, updates);
|
|
2507
|
+
}
|
|
2508
|
+
}
|
|
2509
|
+
__name(updateCorrelationContext, "updateCorrelationContext");
|
|
2510
|
+
var DEFAULT_CORRELATION_HEADER = "x-correlation-id";
|
|
2511
|
+
function generateCorrelationId() {
|
|
2512
|
+
return randomUUID();
|
|
2513
|
+
}
|
|
2514
|
+
__name(generateCorrelationId, "generateCorrelationId");
|
|
2515
|
+
function addCorrelationIdToResponse(reply, correlationId, headerName = DEFAULT_CORRELATION_HEADER) {
|
|
2516
|
+
if (typeof reply.header === "function") {
|
|
2517
|
+
reply.header(headerName, correlationId);
|
|
2518
|
+
} else if (reply.raw && typeof reply.raw.setHeader === "function") {
|
|
2519
|
+
reply.raw.setHeader(headerName, correlationId);
|
|
2520
|
+
}
|
|
2521
|
+
}
|
|
2522
|
+
__name(addCorrelationIdToResponse, "addCorrelationIdToResponse");
|
|
2523
|
+
|
|
2524
|
+
// src/logger/services/logger.service.ts
|
|
2525
|
+
function _ts_decorate14(decorators, target, key, desc) {
|
|
2700
2526
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
2701
2527
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
2702
2528
|
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
2529
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
2704
2530
|
}
|
|
2705
|
-
__name(
|
|
2706
|
-
function
|
|
2531
|
+
__name(_ts_decorate14, "_ts_decorate");
|
|
2532
|
+
function _ts_metadata7(k, v) {
|
|
2707
2533
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
2708
2534
|
}
|
|
2709
|
-
__name(
|
|
2710
|
-
function
|
|
2535
|
+
__name(_ts_metadata7, "_ts_metadata");
|
|
2536
|
+
function _ts_param4(paramIndex, decorator) {
|
|
2711
2537
|
return function(target, key) {
|
|
2712
2538
|
decorator(target, key, paramIndex);
|
|
2713
2539
|
};
|
|
2714
2540
|
}
|
|
2715
|
-
__name(
|
|
2716
|
-
var
|
|
2541
|
+
__name(_ts_param4, "_ts_param");
|
|
2542
|
+
var LoggerService = class _LoggerService {
|
|
2717
2543
|
static {
|
|
2718
2544
|
__name(this, "LoggerService");
|
|
2719
2545
|
}
|
|
@@ -2914,16 +2740,211 @@ ${trace}`;
|
|
|
2914
2740
|
return childLogger;
|
|
2915
2741
|
}
|
|
2916
2742
|
};
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2743
|
+
LoggerService = _ts_decorate14([
|
|
2744
|
+
Injectable9(),
|
|
2745
|
+
_ts_param4(0, Optional()),
|
|
2746
|
+
_ts_param4(1, Optional()),
|
|
2747
|
+
_ts_metadata7("design:type", Function),
|
|
2748
|
+
_ts_metadata7("design:paramtypes", [
|
|
2749
|
+
typeof LoggerModuleOptions === "undefined" ? Object : LoggerModuleOptions,
|
|
2750
|
+
typeof Logger === "undefined" ? Object : Logger
|
|
2751
|
+
])
|
|
2752
|
+
], LoggerService);
|
|
2753
|
+
|
|
2754
|
+
// src/logger/interceptors/http-logger.interceptor.ts
|
|
2755
|
+
function _ts_decorate15(decorators, target, key, desc) {
|
|
2756
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
2757
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
2758
|
+
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;
|
|
2759
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
2760
|
+
}
|
|
2761
|
+
__name(_ts_decorate15, "_ts_decorate");
|
|
2762
|
+
function _ts_metadata8(k, v) {
|
|
2763
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
2764
|
+
}
|
|
2765
|
+
__name(_ts_metadata8, "_ts_metadata");
|
|
2766
|
+
function _ts_param5(paramIndex, decorator) {
|
|
2767
|
+
return function(target, key) {
|
|
2768
|
+
decorator(target, key, paramIndex);
|
|
2769
|
+
};
|
|
2770
|
+
}
|
|
2771
|
+
__name(_ts_param5, "_ts_param");
|
|
2772
|
+
var HttpLoggerInterceptor = class {
|
|
2773
|
+
static {
|
|
2774
|
+
__name(this, "HttpLoggerInterceptor");
|
|
2775
|
+
}
|
|
2776
|
+
logger;
|
|
2777
|
+
enableRequestLog;
|
|
2778
|
+
enableResponseLog;
|
|
2779
|
+
slowRequestThreshold;
|
|
2780
|
+
constructor(logger, options) {
|
|
2781
|
+
this.logger = logger;
|
|
2782
|
+
this.enableRequestLog = options?.enableRequestLog ?? true;
|
|
2783
|
+
this.enableResponseLog = options?.enableResponseLog ?? true;
|
|
2784
|
+
this.slowRequestThreshold = options?.slowRequestThreshold ?? 3e3;
|
|
2785
|
+
}
|
|
2786
|
+
intercept(context, next) {
|
|
2787
|
+
if (context.getType() !== "http") {
|
|
2788
|
+
return next.handle();
|
|
2789
|
+
}
|
|
2790
|
+
const httpContext = context.switchToHttp();
|
|
2791
|
+
const request = httpContext.getRequest();
|
|
2792
|
+
const response = httpContext.getResponse();
|
|
2793
|
+
const startTime = Date.now();
|
|
2794
|
+
if (this.enableRequestLog) {
|
|
2795
|
+
this.logRequest(request);
|
|
2796
|
+
}
|
|
2797
|
+
return next.handle().pipe(tap2(() => {
|
|
2798
|
+
if (this.enableResponseLog) {
|
|
2799
|
+
const duration = Date.now() - startTime;
|
|
2800
|
+
this.logResponse(request, response, duration);
|
|
2801
|
+
}
|
|
2802
|
+
}), catchError((error) => {
|
|
2803
|
+
const duration = Date.now() - startTime;
|
|
2804
|
+
this.logError(request, response, duration, error);
|
|
2805
|
+
throw error;
|
|
2806
|
+
}));
|
|
2807
|
+
}
|
|
2808
|
+
logRequest(request) {
|
|
2809
|
+
try {
|
|
2810
|
+
const correlationContext = getCorrelationContext();
|
|
2811
|
+
const metadata = {
|
|
2812
|
+
type: "http_request",
|
|
2813
|
+
method: request.method,
|
|
2814
|
+
url: request.url,
|
|
2815
|
+
correlationId: correlationContext?.correlationId,
|
|
2816
|
+
ip: request.ip,
|
|
2817
|
+
userAgent: request.headers["user-agent"]
|
|
2818
|
+
};
|
|
2819
|
+
this.logger.logWithMetadata("log", `Incoming ${request.method} ${request.url}`, metadata);
|
|
2820
|
+
} catch (error) {
|
|
2821
|
+
this.logger.error("Failed to log HTTP request", error.stack);
|
|
2822
|
+
}
|
|
2823
|
+
}
|
|
2824
|
+
logResponse(request, response, duration) {
|
|
2825
|
+
try {
|
|
2826
|
+
const correlationContext = getCorrelationContext();
|
|
2827
|
+
const statusCode = response.statusCode;
|
|
2828
|
+
const logLevel = statusCode >= 500 ? "error" : statusCode >= 400 ? "warn" : "log";
|
|
2829
|
+
const metadata = {
|
|
2830
|
+
type: "http_response",
|
|
2831
|
+
method: request.method,
|
|
2832
|
+
url: request.url,
|
|
2833
|
+
statusCode,
|
|
2834
|
+
duration,
|
|
2835
|
+
correlationId: correlationContext?.correlationId
|
|
2836
|
+
};
|
|
2837
|
+
if (duration > this.slowRequestThreshold) {
|
|
2838
|
+
metadata.slowRequest = true;
|
|
2839
|
+
}
|
|
2840
|
+
const message = metadata.slowRequest ? `SLOW ${request.method} ${request.url} ${statusCode} - ${duration}ms` : `${request.method} ${request.url} ${statusCode} - ${duration}ms`;
|
|
2841
|
+
this.logger.logWithMetadata(logLevel, message, metadata);
|
|
2842
|
+
} catch (error) {
|
|
2843
|
+
this.logger.error("Failed to log HTTP response", error.stack);
|
|
2844
|
+
}
|
|
2845
|
+
}
|
|
2846
|
+
logError(request, response, duration, error) {
|
|
2847
|
+
try {
|
|
2848
|
+
const correlationContext = getCorrelationContext();
|
|
2849
|
+
const statusCode = response.statusCode || 500;
|
|
2850
|
+
const metadata = {
|
|
2851
|
+
type: "http_error",
|
|
2852
|
+
method: request.method,
|
|
2853
|
+
url: request.url,
|
|
2854
|
+
statusCode,
|
|
2855
|
+
duration,
|
|
2856
|
+
correlationId: correlationContext?.correlationId,
|
|
2857
|
+
errorName: error?.name || "Error",
|
|
2858
|
+
errorMessage: error?.message || "Unknown error"
|
|
2859
|
+
};
|
|
2860
|
+
if (error?.stack) {
|
|
2861
|
+
metadata.trace = error.stack;
|
|
2862
|
+
}
|
|
2863
|
+
if (error?.response) {
|
|
2864
|
+
metadata.errorDetails = error.response;
|
|
2865
|
+
}
|
|
2866
|
+
const message = `ERROR ${request.method} ${request.url} ${statusCode} - ${error?.message || "Unknown error"}`;
|
|
2867
|
+
this.logger.logWithMetadata("error", message, metadata);
|
|
2868
|
+
} catch (loggingError) {
|
|
2869
|
+
this.logger.error("Failed to log HTTP error", loggingError.stack);
|
|
2870
|
+
}
|
|
2871
|
+
}
|
|
2872
|
+
};
|
|
2873
|
+
HttpLoggerInterceptor = _ts_decorate15([
|
|
2874
|
+
Injectable10(),
|
|
2920
2875
|
_ts_param5(1, Optional2()),
|
|
2876
|
+
_ts_metadata8("design:type", Function),
|
|
2877
|
+
_ts_metadata8("design:paramtypes", [
|
|
2878
|
+
typeof LoggerService === "undefined" ? Object : LoggerService,
|
|
2879
|
+
typeof HttpLoggerOptions === "undefined" ? Object : HttpLoggerOptions
|
|
2880
|
+
])
|
|
2881
|
+
], HttpLoggerInterceptor);
|
|
2882
|
+
|
|
2883
|
+
// src/logger/logger.module.ts
|
|
2884
|
+
import { Global as Global4, Logger as Logger11, Module as Module5 } from "@nestjs/common";
|
|
2885
|
+
|
|
2886
|
+
// src/logger/middleware/correlation-id.middleware.ts
|
|
2887
|
+
import { Injectable as Injectable11 } from "@nestjs/common";
|
|
2888
|
+
function _ts_decorate16(decorators, target, key, desc) {
|
|
2889
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
2890
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
2891
|
+
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;
|
|
2892
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
2893
|
+
}
|
|
2894
|
+
__name(_ts_decorate16, "_ts_decorate");
|
|
2895
|
+
function _ts_metadata9(k, v) {
|
|
2896
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
2897
|
+
}
|
|
2898
|
+
__name(_ts_metadata9, "_ts_metadata");
|
|
2899
|
+
var CorrelationIdMiddleware = class {
|
|
2900
|
+
static {
|
|
2901
|
+
__name(this, "CorrelationIdMiddleware");
|
|
2902
|
+
}
|
|
2903
|
+
includeInResponse;
|
|
2904
|
+
responseHeader;
|
|
2905
|
+
constructor(options = {}) {
|
|
2906
|
+
this.includeInResponse = options.includeInResponse ?? true;
|
|
2907
|
+
this.responseHeader = options.responseHeader ?? DEFAULT_CORRELATION_HEADER;
|
|
2908
|
+
}
|
|
2909
|
+
/**
|
|
2910
|
+
* Middleware handler for processing requests.
|
|
2911
|
+
*/
|
|
2912
|
+
use(_req, reply, next) {
|
|
2913
|
+
const correlationId = generateCorrelationId();
|
|
2914
|
+
if (this.includeInResponse) {
|
|
2915
|
+
addCorrelationIdToResponse(reply, correlationId, this.responseHeader);
|
|
2916
|
+
}
|
|
2917
|
+
runWithCorrelationContext({
|
|
2918
|
+
correlationId
|
|
2919
|
+
}, () => {
|
|
2920
|
+
next();
|
|
2921
|
+
});
|
|
2922
|
+
}
|
|
2923
|
+
/**
|
|
2924
|
+
* Fastify hook handler for onRequest.
|
|
2925
|
+
* This is an async function that returns a Promise, ensuring the AsyncLocalStorage
|
|
2926
|
+
* context persists throughout the entire request lifecycle.
|
|
2927
|
+
*/
|
|
2928
|
+
async onRequest(_req, reply) {
|
|
2929
|
+
const correlationId = generateCorrelationId();
|
|
2930
|
+
if (this.includeInResponse) {
|
|
2931
|
+
addCorrelationIdToResponse(reply, correlationId, this.responseHeader);
|
|
2932
|
+
}
|
|
2933
|
+
const store = correlationStorage.getStore();
|
|
2934
|
+
if (!store) {
|
|
2935
|
+
correlationStorage.enterWith({
|
|
2936
|
+
correlationId
|
|
2937
|
+
});
|
|
2938
|
+
}
|
|
2939
|
+
}
|
|
2940
|
+
};
|
|
2941
|
+
CorrelationIdMiddleware = _ts_decorate16([
|
|
2942
|
+
Injectable11(),
|
|
2921
2943
|
_ts_metadata9("design:type", Function),
|
|
2922
2944
|
_ts_metadata9("design:paramtypes", [
|
|
2923
|
-
typeof
|
|
2924
|
-
typeof Logger === "undefined" ? Object : Logger
|
|
2945
|
+
typeof CorrelationIdMiddlewareOptions === "undefined" ? Object : CorrelationIdMiddlewareOptions
|
|
2925
2946
|
])
|
|
2926
|
-
],
|
|
2947
|
+
], CorrelationIdMiddleware);
|
|
2927
2948
|
|
|
2928
2949
|
// src/logger/logger.module.ts
|
|
2929
2950
|
function _ts_decorate17(decorators, target, key, desc) {
|
|
@@ -3046,9 +3067,9 @@ function createLoggerProviders(options = {}) {
|
|
|
3046
3067
|
providers.push(createDefaultLoggerProvider(mergedOptions));
|
|
3047
3068
|
}
|
|
3048
3069
|
providers.push({
|
|
3049
|
-
provide:
|
|
3070
|
+
provide: LoggerService,
|
|
3050
3071
|
useFactory: /* @__PURE__ */ __name((opts, defaultLogger) => {
|
|
3051
|
-
return new
|
|
3072
|
+
return new LoggerService(opts, defaultLogger);
|
|
3052
3073
|
}, "useFactory"),
|
|
3053
3074
|
inject: [
|
|
3054
3075
|
LOGGER_MODULE_OPTIONS,
|
|
@@ -3077,7 +3098,7 @@ function createLoggerProviders(options = {}) {
|
|
|
3077
3098
|
return new HttpLoggerInterceptor(logger, httpLoggerOptions);
|
|
3078
3099
|
}, "useFactory"),
|
|
3079
3100
|
inject: [
|
|
3080
|
-
|
|
3101
|
+
LoggerService,
|
|
3081
3102
|
LOGGER_MODULE_OPTIONS
|
|
3082
3103
|
]
|
|
3083
3104
|
});
|
|
@@ -3145,7 +3166,7 @@ var LoggerModule = class _LoggerModule {
|
|
|
3145
3166
|
module: _LoggerModule,
|
|
3146
3167
|
providers,
|
|
3147
3168
|
exports: [
|
|
3148
|
-
|
|
3169
|
+
LoggerService,
|
|
3149
3170
|
CorrelationIdMiddleware,
|
|
3150
3171
|
HttpLoggerInterceptor,
|
|
3151
3172
|
LOGGER_MODULE_OPTIONS
|
|
@@ -3222,9 +3243,9 @@ var LoggerModule = class _LoggerModule {
|
|
|
3222
3243
|
},
|
|
3223
3244
|
// Unified logger service
|
|
3224
3245
|
{
|
|
3225
|
-
provide:
|
|
3246
|
+
provide: LoggerService,
|
|
3226
3247
|
useFactory: /* @__PURE__ */ __name((opts, defaultLogger) => {
|
|
3227
|
-
return new
|
|
3248
|
+
return new LoggerService(opts, defaultLogger);
|
|
3228
3249
|
}, "useFactory"),
|
|
3229
3250
|
inject: [
|
|
3230
3251
|
LOGGER_MODULE_OPTIONS,
|
|
@@ -3255,13 +3276,13 @@ var LoggerModule = class _LoggerModule {
|
|
|
3255
3276
|
return new HttpLoggerInterceptor(logger, httpLoggerOptions);
|
|
3256
3277
|
}, "useFactory"),
|
|
3257
3278
|
inject: [
|
|
3258
|
-
|
|
3279
|
+
LoggerService,
|
|
3259
3280
|
LOGGER_MODULE_OPTIONS
|
|
3260
3281
|
]
|
|
3261
3282
|
}
|
|
3262
3283
|
],
|
|
3263
3284
|
exports: [
|
|
3264
|
-
|
|
3285
|
+
LoggerService,
|
|
3265
3286
|
CorrelationIdMiddleware,
|
|
3266
3287
|
HttpLoggerInterceptor,
|
|
3267
3288
|
LOGGER_MODULE_OPTIONS
|
|
@@ -3357,7 +3378,7 @@ export {
|
|
|
3357
3378
|
InternalServerErrorException3 as InternalServerErrorException,
|
|
3358
3379
|
LOGGER_MODULE_OPTIONS,
|
|
3359
3380
|
LoggerModule,
|
|
3360
|
-
|
|
3381
|
+
LoggerService,
|
|
3361
3382
|
MethodNotAllowedException,
|
|
3362
3383
|
NotAcceptableException,
|
|
3363
3384
|
NotFoundException,
|
|
@@ -3365,18 +3386,19 @@ export {
|
|
|
3365
3386
|
Onboarding,
|
|
3366
3387
|
PayloadTooLargeException,
|
|
3367
3388
|
PrimaryBaseRepository,
|
|
3368
|
-
|
|
3389
|
+
PrimaryDatabaseService,
|
|
3369
3390
|
Public,
|
|
3370
3391
|
RequestTimeoutException,
|
|
3371
3392
|
ServiceUnavailableException,
|
|
3372
3393
|
Tenant,
|
|
3373
3394
|
TenantBaseRepository,
|
|
3374
|
-
|
|
3395
|
+
TenantContextService,
|
|
3375
3396
|
TenantDatabaseService,
|
|
3376
3397
|
TooManyRequestsException,
|
|
3377
3398
|
UnauthorizedException4 as UnauthorizedException,
|
|
3378
3399
|
UnprocessableEntityException,
|
|
3379
3400
|
UnsupportedMediaTypeException,
|
|
3401
|
+
UserId,
|
|
3380
3402
|
ValidationException,
|
|
3381
3403
|
VrittiAuthGuard,
|
|
3382
3404
|
addCorrelationIdToResponse,
|